Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Smart pointer polymorphism in collections

I am trying to initialize a collection of pointers to class A through an initializer list. However, the initializer list cannot use reference as a template type.

I have the following code.

#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>
#include <initializer_list>
#include <memory>

struct A
{
    virtual void f() const noexcept { std::cout << "A"; }
};

struct B : public A
{
    virtual void f() const noexcept override { std::cout << "B"; }
};

class Test
{
    std::vector<std::shared_ptr<A>> vec;
public:
    Test(const std::initializer_list<A>& list)
    //Test(const std::initializer_list<A&>& list) <------ Solution?
    {
        for (auto& x : list)
            vec.push_back(std::make_shared<A>(x));
    }
    void print()
    {
        std::for_each(vec.begin(), vec.end(), [](auto x) { x->f(); });
    }
};

int main()
{
    Test test = { A(), B() };
    test.print();
}

The code prints:

AA

It should print:

AB

Is there a simple way to do this, without having to create pointers in the calling method?

The related article (How do I implement polymorphism with std::shared_ptr?) did not provide much help with this problem.

like image 820
Igor Ševo Avatar asked Mar 01 '17 18:03

Igor Ševo


3 Answers

std::initializer_list<T> holds the passed objects by value, so there can be no polymorhism. Also std::make_shared<A> always makes an object of type A, not some type that derives from A. You need another approach.

Since you have an arbitrary number of arguments with arbitrary types, you'll probably need a variadic template constructor.

Edit: My code suggestion was way too complicated. The comment by WhozCraig is much better:

template<class... Args>
Test(Args&&... args) :
    vec { std::make_shared<std::remove_reference_t<Args>>(std::forward<Args>(args))... }
{
}
like image 64
eerorika Avatar answered Nov 05 '22 13:11

eerorika


You are inserting to the vector using std::make_shared<A>(). You are copying a bunch of B and A into a bunch of A.

If you want the syntax in your main to work, the simplest way to do that is a template :

struct Foo {
    template<typename... Args>
    Foo(Args... args) :
        vec{std::make_shared<Args>(std::move(args))...}
    {}

private:
    std::vector<std::shared_ptr<A>> vec;
};
like image 27
Guillaume Racicot Avatar answered Nov 05 '22 12:11

Guillaume Racicot


You're making new shared_ptrs on the following line

        vec.push_back(std::make_shared<A>(x));

It doesn't matter what x was, you made a new A with its values.

You probably want to create a shared_ptr<A> and pass the pointer from x.get() to the new empty shared_ptr<A>

like image 1
TankorSmash Avatar answered Nov 05 '22 13:11

TankorSmash