2

I need to add elements of derived classes into list of shared pointers to abstract class.

I've been trying this. I know that I am trying to create instance of abstract class in the example below, but I have no idea how to make it work.

Simplified classes look like this:


using namespace std;

class Abstract
{
  public:
    virtual string toString() const = 0;
};


class A : public Abstract
{
  public:
    A(int a, int b) : a(a), b(b)
    {}
    string toString() const { return "A"; }
  private:
    int a;
    int b;

};
class B : public Abstract
{
  public:
    B(int b) : b(b)
    {}
    string toString() const { return "B"; }
  private:
    int b;
};

The problem is in the following class:

class Data
{
  public:
    Data(const string & name) : name (name) 
    {}
    Data AddData ( const Abstract & a )
    {
        //Need to fix next line
        l1.push_back(make_shared<Abstract>(a));
        return (*this);   
    }
  private:
    list<shared_ptr<Abstract>> l1;
    string name;
};

I want to avoid RTII and dynamic_cast. I am open to different solutions. I just need to somehow store elements of different types into single container.

I need to use class Data like this:

    Data test("Random Name");

    test.AddData(A(1,2)) 
        .AddData(B(3))
        .AddData(B(4));
Christophe
  • 68,716
  • 7
  • 72
  • 138

2 Answers2

2

What's the problem ?

The problem is that make_shared<X> will create a shared object by invoking the constructor of X. In your case this is not possible, since it's an abstract type. So it won't even compile.

If X would not be abstract, it would compile and appear to work. But it would result in creating a sliced shared object.

How to solve it ?

You need to use a virtual cloning function in the spirit of the prototype design pattern:

class Abstract
{
  public:
    virtual string toString() const = 0;
    virtual shared_ptr<Abstract> clone() const= 0; 
};

You'll have to override it in the derived classes, for example:

class B : public Abstract
{
  public:
    ...
    shared_ptr<Abstract> clone() const override { return make_shared<B>(*this); } 
  ...
};

You may then populate the list taking advantage of the polymorphism:

Data& AddData ( const Abstract & a )   // return preferably a reference
{
    //Need to fix next line
    l1.push_back(a.clone());
    return (*this);   
}

Online demo

Additional remark

If you're an adept of method chaining, I think you'd want AddData() to return a reference. If not, then each time you'd invoke AddData(), you would create a copy of Data, which would create an awful lot of unnecessary copies.

Community
  • 1
  • 1
Christophe
  • 68,716
  • 7
  • 72
  • 138
0

You can't push an abstract class instance to a list. I'd either overloaded AddData() function or used templates.

class Data
{
  public:
    Data(const string & name) : name (name) {}
    Data& AddData (const A &a)
    {
        l1.push_back(make_shared<A>(a));
        return (*this);   
    }

    Data& AddData (const B &b)
    {
        l1.push_back(make_shared<B>(b));
        return (*this);   
    }
  private:
    list<shared_ptr<Abstract>> l1;
    string name;
};

or

class Data
{
  public:
    Data(const string & name) : name (name) {}

    template<typename T>
    Data& AddData (const T &a)
    {
       l1.push_back(make_shared<T>(a));
       return (*this);   
    }

  private:
    list<shared_ptr<Abstract>> l1;
    string name;
};
  • 1
    While both of these approaches could be considered, they both rely on the type of object known at compile time. So it will work for the OP's example where the type of the added object is known at compile time. However it would fail in a polymorphic use case, for example if you'd try to add a reference c defined like this: `B b(5); Abstract &c=b;` ([demo](https://ideone.com/W7W4q6)) – Christophe Apr 27 '19 at 10:16