-1

I have a class with a vector as below:

#include <vector>

class Base{};

class Derived: public Base{};

class Foo{
   private:
        std::vector<Base*> vec;
   public:
        Foo() = default;
        void addObject(const Base* b){
        // vec.push_back(new Base(*b));
        // vec.push_back(new Derived(*b));
        }
};

int main(){
        Derived* d = new Derived();
        Base* b = new Base();
        Foo f;
        f.addObject(d);
        f.addObject(b);
        
        delete d;
        delete b;
        return 0;
}

The function addBase may receive Derived's pointers. The line vec.push_back(new Base(b)); is expected to use b's copy to initialize a new object whose pointer will be pushed_back. If I don't use new, I will have resources shared between b and the vector(This is a sin).

I want to maintain polymorphism. How do I ensure objects that are pushed back maintain the type they were assigned during their creation without forcing everything into a Base object.

Shwalala
  • 25
  • 5
  • I have absolutely no clue, what you're asking about. Do you have any problems with that code you posed? – πάντα ῥεῖ Nov 04 '21 at 02:13
  • [Mcve] might clarify, no idea otherwise – πάντα ῥεῖ Nov 04 '21 at 02:16
  • 3
    If you want `addBase` to add a pointer to an existing object, just drop the `const` and `new` from everything. If you want `addBase` to actually create a copy of the pointed-to object before adding it to `vec`, take a look at [this idiom for copying polymorphic objects](https://stackoverflow.com/questions/5148706/copying-a-polymorphic-object-in-c) – Nathan Pierson Nov 04 '21 at 02:17
  • _"the code above just about captures the main issue"_ -- no it doesn't. There is zero polymorphism in your code. Additionally, you cannot push a `const Base*` into a vector that stores non-const `Base*`. The compiler should rightfully be emitting an error regarding the loss of const type qualifier if you try to do it. You can convert from non-const to const, but not the other way. – paddy Nov 04 '21 at 02:24
  • @NathanPierson I think your solution may work. If I am getting it right, I should implement a virtual function that returns a new object of the same type as the calling object. – Shwalala Nov 04 '21 at 03:02
  • No, the solution is to determine whether `Foo` should be storing `const Base*` values or `Base*` values. We have no idea of the required semantics for your program. If it should store const values, then change the vector to `std::vector`. If it should store non-const values, then change the function to `void addObject(Base*)`. In both scenarios, I assume you want the "add" function to simply store the pointer: `vec.push_back(b);` ... – paddy Nov 04 '21 at 03:17
  • ... If instead you need it to store a _copy_, that is more complex and requires that your base class provides `virtual Base* copy()` which then every subclass overrides. And then of course `Foo` would need to manage that memory with an appropriate destructor (and Rule of Three considerations), or perhaps use `std::unique_ptr` to manage these copies. – paddy Nov 04 '21 at 03:18
  • It needs to store a copy. As noted in my previous comment to @NathanPierson, I accepted the solution to have a virtual clone function in every derived class. I already have a destructor for the necessary clean up. – Shwalala Nov 04 '21 at 03:23
  • 1
    Side note: You most likely want a `std::vector>`. Otherwise the code will leak memory all over the place. There is rarely a case in which a container of raw pointers makes sense. (It only makes sense when the container doesn’t *own* the pointed-at objects.) – Andrej Podzimek Nov 04 '21 at 05:50
  • I agree with using ```std::unique_ptr```. I had used raw since I had a destructor for the cleanup. – Shwalala Nov 04 '21 at 05:57

1 Answers1

0

If you want to create a clone of the object passed to addObject, you have essentially two options. 1) Have a virtual function clone in Base and Derived, or better 2) let the copy constructor handle it.

If you go for the second option, addObject need to the know the actual type of the passed object. This could be done with a template:

template <class T>
void addObject(const T * t)
{
  Base *b = new T(*t);
}

In a robust program we don't want to use new/delete but rather std::unique_ptr or std::shared_ptr. Passing t as a reference, rater than pointer, is also considered a better practice.

#include <vector>
#include <memory>
#include <iostream>


class Base
{
public:
  virtual void cname() { std::cout << "Base" << std::endl;}
};


class Derived: public Base
{
public:
  virtual void cname() { std::cout << "Derived" << std::endl;}
};
 

class Foo
{
private:
  // You may switch to `std::shared_ptr` if that better fits your use case
  std::vector<std::unique_ptr<Base> > vec;
public:
  template <class T>
  void addObject(const T & t)
  {
    vec.push_back(std::make_unique<T> (t)); 
  }

  void dump()
  {
    for (auto &bp: vec)
      bp->cname();
  }
};

int main(){
  Derived d;
  Base b;

  Foo f;
  f.addObject(d);
  f.addObject(b);

  f.dump();
  
  return 0;
}

Naturally for this to work, the reference passed to addObject must be of the correct type. This will create a clone of type Base, not Derived:

Derived d;
Base &e = d;
f.addObject(e);
HAL9000
  • 2,138
  • 1
  • 9
  • 20
  • The second approach is interesting. I am using raw pointers to avoid the overhead of shared_ptr. Unique_ptr won't work since the vector elements will be passed around quite a bit. Besides, there isn't confusion about ownership since the pointers are all owned by the class objects hence can be deleted by the destructor. Does this logic make sense to you? – Shwalala Nov 04 '21 at 04:16
  • I have also noticed that the second approach works for a function but will not work for the copy constructor of Foo – Shwalala Nov 04 '21 at 04:27
  • 1
    @Shwalala In general, if your vector owns the pointed-to objects then it should hold some kind of smart pointer. If you later pass those pointers to something that doesn't take ownership then you pass a raw pointer. If you maintain the "smart pointer == owner, raw pointer == non-owner" dynamic you'll be much less likely to have leaks and/or double-free issues. That also makes this whole question more-or-less moot, since you can change `addObject` to accept a `unique_ptr` and be sure that it won't ever share ownership with the caller. – Miles Budnek Nov 04 '21 at 04:35
  • I get your point. The real problem I was having was in a Copy Constructor and I used this question to remodel it. The second approach doesn't seem to work in the CC. Either way, Unique_ptr may is a good suggestion! – Shwalala Nov 04 '21 at 04:40
  • Note that the behaviour of the example program is undefined. To fix it, make the destructor of `Base` virtual. – eerorika Mar 26 '23 at 02:16