Search code examples
c++stdvector

std::vector of Derived class instances whose Bases contain a (raw) pointer


Say I had the following structure

class BaseKernel {
   // .......
}

class DerivedKernel : public BaseKernel {
   // .......
}

class A {
  public:
    A(BaseKernel* kernel) : kernel(kernel) {}
    A(const A& a) : kernel(a.kernel) {}
   ~A() {delete kernel;}

    BaseKernel* kernel;
}

class B : public A {
  public:
    B(BaseKernel* kernel) : A(kernel) {}
    B(const B& b) : A(b) {}
   ~B() = default;
}

class C : public B {
  public:
    C() = default;
    C(BaseKernel* kernel) : B(kernel) {}
    C(const C& c) : B(c) {}
   ~C() = default;
}

class D {
  public:
    D() = default;
    D(std::vector<C> collection) : collection(collection) {}

    std::vector<C> collection;
}

class E {
  public:
    E(std::vector<D> dollection) : dollection(dollection) {} // lmfao, dollection for the lack of a better word
    
    std::vector<D> dollection;
}

With the following usage

BaseKernel* kernel1 = new DerivedKernel(...);
BaseKernel* kernel2 = new DerivedKernel(...);

C c_obj1(kernel1);
C c_obj2(kernel2);

std::vector<C> collection1(1, c_obj1);
std::vector<C> collection2(1, c_obj2);

std::vector<D> dollections = {collection1, collection2};
// At this point kernel1 and kernel2 are deleted.
E e_obj(dollections) // Useless now

As im relatively new to C++ and therefore unfamiliar with smart pointers (i believe the solution is with the latter, however), how would I handle such a case with raw pointers? (Better yet, a solution using the appropriate smart pointer would be great)

The initialization of c_obj1 and c_obj2 were fine (kernel1 and kernel2 still lives), but specifically for dollections I would need to initialize the vector with brackets (as I would have different collections) and after debugging it first goes through the copy constructors then ultimately the destructor ~A()?


Solution

  • I see some problems. I'll see if I can understand your real question. Let's start with this:

    class A {
      public:
        A(BaseKernel* kernel) : kernel(kernel) {}
        A(const A& a) : kernel(a.kernel) {}
       ~A() {delete kernel;}
    
        BaseKernel* kernel;
    }
    

    This is bad. If you use your copy constructor, you'll end up deleting the same data twice. Imagine this:

     BaseKernel * k = new BaseKernel();
     A first(k);
     A second(first);
    

    This this point, both first.kernel and second.kernel point to k. Now, when they go out of scope, both destructors will attempt to delete their kernel pointer, so k will get deleted twice.

    I don't quite understand the question you're asking. However, I discourage the use of raw pointers and encourage smart pointers. std::unique_ptr is more efficient, but I tend to always use std::shared_ptr as follows:

    class A {
      public:
        A(std:: shared_ptr <BaseKernel> kernelIn) : kernel(kernelIn) {}
        A(const A& a) : kernel(a.kernel) {}
       ~A() { /* Nothing needed */ }
    
        std:: shared_ptr <BaseKernel> kernel;
    };
    

    Then later...

    std::shared_ptr<BaseKernel> kernel1 = std::make_shared<DerivedKernel>(...);
    

    You'll probably need to do a dynamic_pointer_cast, though. I usually don't like using the datatype I'm pointing to so would normally not do it here but instead:

    std::shared_ptr< DerivedKernel> kernel1 = std::make_shared<DerivedKernel>(...);
    

    And then dynamic_pointer_cast it as necessary later.

    I'm not sure if this gets you further.