5.6 Dynamic Compilation & Multiple Inheritance

Dynamic Creation of Objects
Section titled “Dynamic Creation of Objects”C++ allows objects to be created dynamically at runtime using pointers in combination with the new and delete keywords. This gives the programmer direct control over memory allocation and deallocation. Dynamic memory is extremely powerful: it allows programs to create objects whenever they are needed (for example, allocating space for an image buffer, or creating temporary records during program execution).
However, with this flexibility comes risk. If memory is not managed correctly, it can lead to serious problems such as memory leaks, dangling pointers, or double-deletes.
Example: Memory Leak (Full source code: accountDynamicCreation.cpp)
int main() { Account* a = new Account("John", 10.50, 123456); Account* b = new Account("Derek", 12.07, 123457);
a->display(); b->display();
b = a; // problem here
a->display(); b->display();}Explanation: When this program runs:
- Two
Accountobjects are created dynamically usingnew, and their addresses are stored in the pointersaandb. - Calling
display()on each pointer correctly prints John’s and Derek’s account details. - The assignment
b = a;causesbto point to the same object asa.
At this point, the pointer to Derek’s account is lost. Since there are no references left to that object, the memory it occupies cannot be released — this is a memory leak.
Now both a and b point to the same object (John’s account), so calling display() on either pointer shows the same details. Derek’s account still exists in memory, but the program can no longer access or free it. This is the output:
account number: 123456 has balance: 10.5 EuroThis account is owned by: Johnaccount number: 123457 has balance: 12.07 EuroThis account is owned by: Derekaccount number: 123456 has balance: 10.5 EuroThis account is owned by: Johnaccount number: 123456 has balance: 10.5 EuroThis account is owned by: JohnThis can be further illustrated as in this reference figure:
Memory Leak from Reassignment
One assignment, one stranded object — and no way to ever delete it.
After the assignment b = a;, the pointer b now points to the first Account object (John’s account). The second object (Derek’s account) no longer has any references and is effectively lost in memory. Its destructor will never be called, and the memory it occupies will only be released when the operating system cleans up after the program terminates. This is a classic memory leak.
Deallocation and Destructors
Section titled “Deallocation and Destructors”A further complication of dynamic memory management in C++ is that the programmer is responsible for calling delete to deallocate memory. When delete is called on a pointer, the destructor of the object it points to is executed, allowing for any clean-up code to run (e.g., writing a record to disk, closing a file, releasing network connections).
For example, if we add a destructor to the Account class that simply announces when an account is being destroyed, we can observe this behaviour (Full source code: accountDynamicCreationTest.cpp):
int main() { Account* a = new Account("John", 10.50, 123456); Account* b = new Account("Derek", 12.07, 123457);
a->display(); b->display();
b = a; // Derek's account is now lost (memory leak)
delete a; // destroys John's account (and calls its destructor)}Explanation of Behaviour:
- Both accounts are created on the heap using
new. - The assignment
b = a;causes the pointer to Derek’s account to be lost — the object remains in memory, but its destructor is never called. - When
delete a;executes, John’s account is destroyed and its destructor runs. - Derek’s account is leaked and will only be reclaimed when the operating system ends the process.
This highlights a key danger of manual memory management in C++:
- If
deleteis forgotten, memory is leaked. - If
deleteis called twice on the same pointer, the program will likely crash (undefined behaviour). - If
deleteis called on an invalid pointer, the program may behave unpredictably.
account number: 123456 has balance: 10.5 EuroThis account is owned by: Johnaccount number: 123457 has balance: 12.07 EuroThis account is owned by: DerekSo, in the previous example, the destructor is called only on the Account object containing John’s details, while the destructor for Derek’s account is never called because its pointer was lost.
Now, suppose we alter the program to explicitly delete both pointers a and b:
int main() { Account* a = new Account("John", 10.50, 123456); Account* b = new Account("Derek", 12.07, 123457);
a->display(); b->display();
b = a;
delete a; // deletes John's account delete b; // attempts to delete John's account again return 0;}(Full source code: accountDynamicCreationTest2.cpp – Caution: running this code will likely cause your program to crash with a segmentation fault or a memory allocation error.)
What Happens?
- Both accounts are created dynamically with
new. b = a;makes both pointers refer to the same object (John’s account).delete a;correctly destroys John’s account.delete b;then attempts to destroy the same object again.
This results in undefined behaviour. On many systems the program will crash immediately, but the actual behaviour is unpredictable — it might appear to run, might corrupt memory, or might terminate with a runtime error.
This example illustrates two key dangers of manual memory management in C++:
- Memory leaks — when an object is lost without being deleted (as in the previous example).
- Double deletion — when the same object is deleted twice, leading to undefined behaviour.
Both errors are notoriously difficult to track down in large programs. Smart pointers are discussed in the next chapter that solve many of these issues.
🧩 Knowledge Check
Section titled “🧩 Knowledge Check”Match Dynamic Memory Errors
If you assign pointer 'b' to pointer 'a' (b = a) where both point to dynamically allocated objects, what immediately happens to the object previously pointed to by 'b'?
Proper Heap Allocation and Deallocation
Arrays of Objects
Section titled “Arrays of Objects”An array is an indexed collection of elements, where all elements share the same type. In C++ you can create arrays of objects just as you would arrays of fundamental types:
ElementType theArray[numberOfElements];Account allTheAccounts[2000];Each element in the array is initialised using the class’s default constructor. If the class does not have a default constructor, you must explicitly initialise each element.
Example: Array of Objects (Full source code: accountArrayExample.cpp)
int main() { Account someAccounts[3] = { Account("Tom", 20.00, 123456), Account("Richard", 55.00, 123457), Account("Harry", 99.00, 123458) // 1 };
Account* p = &someAccounts[0]; // 2
someAccounts[2].display(); // 3: displays Harry p->display(); // 4: displays Tom (++p)->display(); // 5: displays Richard (p+1)->display(); // 6: displays Harry
// Warning! p now points at Richard, not Tom! (line 7)}This code has the output:
Account number: 123458 has balance: 99 Euro This account is owned by: HarryAccount number: 123456 has balance: 20 Euro This account is owned by: TomAccount number: 123457 has balance: 55 Euro This account is owned by: RichardAccount number: 123458 has balance: 99 Euro This account is owned by: HarryStep-by-Step Behaviour:
- The array
someAccountshas three elements ([0]to[2]), each initialised by calling the constructor. pis set to point at the first element (someAccounts[0]).someAccounts[2]accesses the third element (Harry’s account).p->display()shows Tom’s account becauseppoints tosomeAccounts[0].
(++p)->display()pre-incrementsp, so it now points tosomeAccounts[1](Richard).- If
p++had been used, the increment would occur after the call, and Tom’s account would have been displayed.
- If
(p+1)->display()offsetspby one, so it displays Harry’s account (someAccounts[2]). Importantly,pitself remains pointing to Richard.
Correctly distinguishing between pointer incrementing (++p) and offsetting (p+1) is crucial when working with arrays and pointers.
Dynamically Allocated Arrays
Pointers can also be used to allocate arrays of objects at runtime using new:
Account* ptr = new Account[10]; // allocate 10 Account objectsA pointer to an array of objects looks identical to a pointer to a single object.
To deallocate, you must use delete[]:
delete[] ptr; // correctdelete ptr; // undefined behaviourIf you use delete ptr; instead of delete[] ptr;, the destructors of the array elements will not be called correctly, leading to resource leaks or undefined behaviour. See the example using_delete.cpp.
Important Points
- Arrays of objects are initialised by calling their constructors.
- Array elements can be accessed directly (
someAccounts[i]) or via pointers. - Be careful with pointer arithmetic (
++pvsp+1). - For dynamically allocated arrays, always use
delete[]to deallocate.
🧩 Knowledge Check
Section titled “🧩 Knowledge Check”If you allocate an array of objects dynamically using 'new Account[10]', what is the correct way to deallocate it?
Pointer Arithmetic vs Incrementing
Multiple Inheritance
Section titled “Multiple Inheritance”C++ allows a class to inherit from more than one base class. This is known as multiple inheritance.
Example: FundTransfer Class
Section titled “Example: FundTransfer Class”Think back to the earlier Transaction, Lodgement, and Withdrawal classes. Suppose we want to create a FundTransfer class that combines the behaviour of both a lodgement and a withdrawal. This structure is shown in the first option “1. Without Virtual” of the Diamond Problem Example below.
This is valid because:
- A
Lodgementis aTransaction. - A
Withdrawalis aTransaction. - At different points during execution, a
FundTransferis both aLodgementand aWithdrawal.
Here’s how it might look in code:
class FundTransfer : public Lodgement, public Withdrawal {private: // additional states...
public: virtual int getTimeTaken() { return (Lodgement::timeOfTransaction - Withdrawal::timeOfTransaction); }};- Multiple parents are listed, separated by commas.
- Both
LodgementandWithdrawalcontain atimeOfTransactionstate, so ambiguity must be resolved by explicitly qualifying with the parent class name (Lodgement::timeOfTransaction,Withdrawal::timeOfTransaction).
If the base class Transaction also had a display() method, calling it directly would be ambiguous:
FundTransfer* fundPtr;fundPtr->display(); // ambiguous (not allowed)fundPtr->Lodgement::display(); // OKfundPtr->Withdrawal::display(); // OKAlthough this resolves the ambiguity, it is awkward. A better design would be to declare a new display() method in FundTransfer that calls or adapts the appropriate base class methods.
The Diamond Problem
What happens when a class inherits the same base twice — and how virtual inheritance fixes it.
When Two Base States Should Not Be Duplicated
Section titled “When Two Base States Should Not Be Duplicated”In the FundTransfer example, it was correct to have two separate Transaction objects (one for lodgement and one for withdrawal). However, there are cases where duplication is undesirable.
For example, suppose we want to design a new type of account — let’s call it CashSave — that combines the benefits of a Deposit account and a Current account. See the second example “2. With Virtual” in the Diamond Problem Example above.
Here:
- Both
DepositandCurrentinherit fromAccount. - If we inherit from both directly, we would end up with two copies of
AccountinsideCashSave— and thus two separatebalancestates.
Solution: Virtual Base Classes
To avoid duplication, we declare the common base (Account) as a virtual base class:
class Current : public virtual Account { // ...};
class Deposit : public virtual Account { // ...};
class CashSave : public Current, public Deposit { // no need to repeat "virtual" here};This ensures that CashSave contains only one shared instance of Account.
The downside: this decision must be made when designing the intermediate classes (Current and Deposit). If they are not declared with virtual inheritance at design time, later derived classes like CashSave may suffer from duplicated base states.
Important Points on Multiple Inheritance:
- Multiple inheritance allows a class to inherit from more than one base.
- Ambiguities (e.g., duplicate states or methods) must be resolved explicitly.
- Virtual inheritance avoids duplication of a common base class.
- Constructors for virtual bases must be handled carefully by the ultimate child class.
Multiple Inheritance in Other Languages
Section titled “Multiple Inheritance in Other Languages”Unlike C++, some modern languages such as Java and Rust do not support multiple inheritance of classes. This decision was made to avoid the ambiguity and complexity associated with the “diamond problem.”
- Java allows a class to inherit from only one base class, but it can implement multiple interfaces to achieve similar flexibility.
- Rust also avoids multiple inheritance but provides traits, which can be combined to give classes (structs) multiple sets of behaviours without inheriting from several parent classes.
🧩 Knowledge Check
Section titled “🧩 Knowledge Check”When a class multiply inherits from two base classes that both have a method with the exact same name, how does C++ handle a direct call to that method?
Match Multiple Inheritance Concepts
In a C++ diamond inheritance structure using virtual base classes, which class is responsible for calling the constructor of the shared virtual base class?
© 2026 Derek Molloy, Dublin City University. All rights reserved.