简体   繁体   中英

C++ - polymorphism: initialization of a container class with derived classes

I have a question regarding polymorphism and maybe other techniques. Consider the following scheme:

#include <vector>
#include <memory>

using namespace std;

struct Base
{ };

struct Derived : Base
{ };

class group
{
    private:
        vector< unique_ptr<Base> > V;
    public:
        group( /* what to put here? */ )
        : V( /* again: how to construct V? */ )
        { }
}

Here, I have three classes: a base class, called Base ; a derived class, called Derived ; and a last class, group , that would act as a wrapper to hold Base pointers. But I'd like to achieve the following:

  • Use move semantics . There should be no copies involved. The caller should be able to give the constructor several temporaries , as the constructor would steal them:

     group my_wonderful_group( Base( /* parameters */ ) , Derived ( /* ... */ ) , Derived ( /* ... */ ) ); 
  • Initialize V in the initialization list . That way, V could be const -qualified (despite all the other benefits initializing member objects in initialization lists carries).

I've tried several things, but they either do not seem appropiate, or are simply conceptually far from my goals:

  • initializer_list s' elements are non-movable ; unique_ptr s are movable-only .
  • Variadic templates do not seem capable to fit the goal of making V const . As initializer_list s can't hold objects of different types (just like vector s), I first thought about them, but, how? After reading many examples, I still can't figure out how to make a variadic template constructor , or even if it's possible to do so without a workaround (ie: creating some init() function that takes a Base , a Derived ,... and plays with it).
  • Create a function that returns unique_ptr<Base> and that forwards its arguments to the constructor of Derived , thus acting as an user-friendly wrapper to the group class. This does, however, make little sense, as I illustrate with the code attached below.

This is what I have:

#include <string>
#include <vector>
#include <memory>
#include <initializer_list>
#include <iostream>

using namespace std;

struct Base
{
    string s;
    Base(Base && b) : s(move(b.s)) { }
    Base(const string & S) : s( S ) { }
};

struct Derived : Base
{
    Derived(const string & S) : Base( S ) { }
};

unique_ptr<Base>
BASE ( const string & S )
{
    return unique_ptr<Base>( new Base(S) );
}

unique_ptr<Base>
DERIVED ( const string & S )
{
    return unique_ptr<Base>( new Derived(S) );
}

class C
{
    private:
        vector< unique_ptr<Base> > V;
    public:
        template<typename ... T>
            C
            ( T ... t ) : V({ t... })
            { }

        void
            print
            ( void )
            {
                for ( const auto & x : this->V )
                    cout << x->s << endl;
            }
            ;
};


int main(void)
{
    C c( BASE("hola") , DERIVED("der1") , DERIVED("bor3") );
    c.print();
    return 0;
}

It complains, however, about the usage of the [deleted] copy constructor of unique_ptr being called, when the argument pack in the constructor of group is expanded and comma-separated put into the initializer_list that would be used to initialize V .

I guess that my question goes down to a vector , and, in fact, could be applied to one: vector<unique_ptr<Base>>( /* initialize with derived classes */ ) . I think this must have been solved before, as polymorphism is basic C++, and holding objects of derived classes seems a very common usage of it. As a note, I use g++ 4.8.1 .

Thanks in advance. Best regards, Kalrish

PS: I've just read this question which seems to partly cover my problem.

Following should solve all your issues:

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

using namespace std;

struct Base
{
    string s;
    Base(const Base& b) = delete;
    Base(Base && b) : s(move(b.s)) { }
    Base(const string & S) : s( S ) { }
};

struct Derived : Base
{
    Derived(const string & S) : Base( S ) { }
};

#if 1 // not in C++11
template <typename T, typename ... Ts>
std::unique_ptr<T> make_unique(Ts&&...args)
{
    return std::unique_ptr<T>(new T{std::forward<Ts>(args)...});
}
#endif

// vector<move_only> cannot be construct from initializer list :-/
// Use this work around
template <typename Base, typename ... Ts>
std::vector<std::unique_ptr<Base>> make_vector_of_unique(Ts&&... ts)
{
    std::unique_ptr<Base> init[] = {make_unique<Ts>(std::forward<Ts>(ts))...};
    return std::vector<std::unique_ptr<Base>> {
        std::make_move_iterator(std::begin(init)),
        std::make_move_iterator(std::end(init))};
}

class C
{
private:
    const std::vector< std::unique_ptr<Base> > V;
public:
    template<typename ... Ts>
    C(Ts&& ... t) : V(make_vector_of_unique<Base>(std::forward<Ts>(t)...))
    {}

    void print()
    {
        for (const auto & x : this->V)
            std::cout << x->s << std::endl;
    }
};

int main() {
    C c( Base("hola") , Derived("der1") , Derived("bor3") );
    c.print();
    return 0;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM