Search code examples
c++oopmemory-management

managing memory of nested classes


Say I have some nested classes called A, B and C. Objects of type C hold a member of type B. Objects of type B hold a member of type A.

Object of type C is instantiated on the heap. This class object and it's sub-objects are one, so everything ends up on the heap.

In the implementation for C and B, is there any advantage to explicitly holding pointers? Something like the code below?

On the one hand, it seems inefficient to follow a chain of pointers to get access to data. On the other, perhaps it's "safer" for inner classes to hold pointers if it isn't guaranteed that outer classes are declared on the heap? Is there anything else?

#include <iostream>
#include <memory> // std::unique_ptr

class A;
class B;
class C;

class A {
public:
    A() {
        std::cout << "Constructor of A" << std::endl;
    }
    ~A() {
        std::cout << "Destructor of A" << std::endl;
    }
};

class B {
public:
    std::unique_ptr<A> a_member; 

    B() : a_member(std::make_unique<A>()) { 
        std::cout << "Constructor of B" << std::endl;
    }
    ~B() {
        std::cout << "Destructor of B" << std::endl;
    }
};

class C {
public:
    std::unique_ptr<B> b_member; 

    C() : b_member(std::make_unique<B>()) { 
        std::cout << "Constructor of C" << std::endl;
    }
    ~C() {
        std::cout << "Destructor of C" << std::endl;
    }
};

int main() {
    auto c_ptr = std::make_unique<C>(); 
    return 0;
}

Solution

  • is there any advantage to explicitly holding pointers?

    By default you should aim for member variables to be part of the parent, not held by unique pointers, as this will be better in terms of storage and performance. Hitting a stack overflow is very rare if you don't have anything suspicious in the code (like an infinite recursion function, or implementing a linked list class yourself). but there are multiple scenarios where pointers will be needed.

    1. Pimpl idiom, where you are not exposing the details of the child objects, or you are using type-erasure where the details of the child object are not even known. (std::any or std::function)
    2. polymorphism, where you are holding a pointer to some base, and a pointer to a derived type is passed instead (dependency injection)

    Since the code mentioned makes no use of either of these functionalities then holding children as members directly (instead of using unique_ptr) will be better in terms of storage and performance, (your allocator still has to keep meta-data for each allocation, so 1 allocation is smaller than 3 even if they have the same size)


    If you are a library designer and you are going to force "heap" allocation then you could use a customizable allocator, which defaults to the heap allocator, this way the user can substitute it for an allocator that uses the stack if he knows he can afford it. (like vector and pmr::vector do)