1

I got a vector of pointers to a base class foo which got a couple of child classes and what I would like to do is based on which child class it is, create a new class of the same instance.

I've solved it earlier by having a gigantic for loop which uses typeid to find out what class it is but is there no way to solve it in a more general way?

Basically, something like this is what I'm looking for:

std::vector<foo*> a;
std::vector<foo*> b;

//Store a couple of classes in a and b

b[0] = new typeid(a[0]).name();
David S
  • 195
  • 5
  • 19

3 Answers3

6

One approach is to have a virtual clone() method that returns a pointer to an object of the right type:

struct base 
{
  virtual base* clone()=0;
  virtual ~base() {}
};

struct foo : public base
{
  virtual base* clone() { return new foo(*this); }
};

struct bar : public base
{
  virtual base* clone() { return new bar(*this); }
};

Then your code would be

b[0] = a[0].clone();

In real life code you would return a smart pointer, e.g. an std::unique_ptr<base>.

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
  • +1 with a note that `std::unique_ptr` is since C++11 only. – LihO Feb 19 '13 at 21:45
  • Hm, wouldn't actually any kind of smart pointer clash with the usual implementation of standard containers (which are assumed in the original question)? – peterph Feb 20 '13 at 11:43
  • @peterph no, it would work fine, as long as the smart pointer is not the deprecated `std::auto_ptr`. – juanchopanza Feb 20 '13 at 11:45
2

Whenever you have a giant switch based on type, you should probably use virtual functions instead.

You should introduce some sort of virtual clone() function:

#include <iostream>
#include <memory>

struct base
{
    virtual ~base() {};

    virtual void do_stuff() = 0;

    // cloning interface
    std::unique_ptr<base> clone() const
    {
        return std::unique_ptr<base>(do_clone());
    }

private:
    // actual clone implementation; uses a raw pointer to support covariance
    virtual base* do_clone() const = 0;
};

struct derived_A : base
{
    void do_stuff() override { std::cout << "stuff in derived_A" << std::endl; }

    // purposefully hide the base implementation,
    // since we know we'll be returning a derived_A
    std::unique_ptr<derived_A> clone() const
    {
        return std::unique_ptr<derived_A>(do_clone());
    }

private:
    derived_A* do_clone() const override
    {
        return new derived_A(*this);
    }
};

struct derived_B : base
{
    void do_stuff() override { std::cout << "stuff in derived_B" << std::endl; }

    // purposefully hide the base implementation,
    // since we know we'll be returning a derived_B
    std::unique_ptr<derived_B> clone() const
    {
        return std::unique_ptr<derived_B>(do_clone());
    }

private:
    derived_B* do_clone() const override
    {
        return new derived_B(*this);
    }
};

#include <vector>

int main()
{
    std::vector<std::unique_ptr<base>> v1;
    std::vector<std::unique_ptr<base>> v2;

    std::unique_ptr<base> x(new derived_A);
    v1.push_back(std::move(x));

    std::unique_ptr<base> y(new derived_B);
    v1.push_back(std::move(y));

    v1[0]->do_stuff();
    v1[1]->do_stuff();

    // clone
    v2.push_back(v1[0]->clone());
    v2.push_back(v1[1]->clone());

    v2[0]->do_stuff();
    v2[1]->do_stuff();
}

We want covariance on the return type (if you hold a pointer to a statically-typed derived_A, cloning it should yield a derived_A to avoid redundant casts), which is why the cloning interface is split into two pieces. It could be done in one if std::unique_ptr<base> was covariant to std::unique_ptr<derived>, but that's only the case for raw pointers.

I'm sure there's a way to hide the repeated boilerplate, that's an exercise for the reader.


EDIT: Actually, here ya go; not too hard:

#include <memory>

// Note: leaves with a public: access specifier
#define DEFINE_ABSTRACT_CLONEABLE(selfType)         \
        DEFINE_CLONEABLE_DETAIL(selfType)           \
        private:                                    \
            virtual selfType* do_clone() const = 0; \
                                                    \
        public:

// Note: leaves with a public: access specifier
#define DEFINE_CLONEABLE(selfType)              \
        DEFINE_CLONEABLE_DETAIL(selfType)       \
        private:                                \
            selfType* do_clone() const override \
            {                                   \
                return new selfType(*this);     \
            }                                   \
                                                \
        public:

#define DEFINE_CLONEABLE_DETAIL(selfType)                                   \
        public:                                                             \
            std::unique_ptr<selfType> clone() const                         \
            {                                                               \
                static_assert(std::is_same<selfType,                        \
                                          std::decay<decltype(*this)>::type \
                                          >::value,                         \
                             "Must specify current class name.");           \
                                                                            \
                return std::unique_ptr<selfType>(do_clone());               \
            }                                                               \

And the test (note the smaller size):

#include <iostream>

#include "cloneable.hpp" // or whatever 

struct base
{
    // readable error: DEFINE_ABSTRACT_CLONEABLE(int);
    DEFINE_ABSTRACT_CLONEABLE(base);

    virtual ~base() {};

    virtual void do_stuff() = 0;
};

struct derived_A : base
{
    DEFINE_CLONEABLE(derived_A);

    void do_stuff() override { std::cout << "stuff in derived_A" << std::endl; }
};

struct derived_B : base
{
    // error: DEFINE_CLONEABLE(derived_B);
    DEFINE_ABSTRACT_CLONEABLE(derived_B);

    void do_stuff() override { std::cout << "stuff in derived_B" << std::endl; }
    virtual void do_thing() = 0; // abstract again
};

struct derived_AA : derived_A
{
    DEFINE_CLONEABLE(derived_AA);

    void do_stuff() override { std::cout << "stuff in derived_AA" << std::endl; }
};

struct derived_BB : derived_B
{
    DEFINE_CLONEABLE(derived_BB);

    void do_stuff() override { std::cout << "doing stuff in derived_BB" << std::endl; }
    void do_thing() override { std::cout << "doing thing" << std::endl; }
};

int main()
{
    std::unique_ptr<derived_AA> x(new derived_AA());
    x->do_stuff();

    auto xx = x->clone();
    xx->do_stuff();

    std::unique_ptr<derived_A> xxx = xx->clone();
    xxx->do_stuff();

    std::unique_ptr<base> xxxx = xxx->clone();
    xxxx->do_stuff();

    xxxx->clone()->do_stuff();

    std::unique_ptr<derived_BB> y(new derived_BB());
    y->do_stuff();
    y->do_thing();

    auto yy = y->clone();
    yy->do_stuff();
    yy->do_thing();

    std::unique_ptr<derived_B> yyy = yy->clone();
    yyy->do_stuff();
    yyy->do_thing();

    std::unique_ptr<base> yyyy = yyy->clone();
    yyyy->do_stuff();
    // error, lost derived information: yyyy->do_thing(); 

    yyyy->clone()->do_stuff();
}

One more improvement would be to make each new declaration of do_clone pure virtual to force further deriving classes to implement it, but this is left to the reader.

GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • `I'm sure there's a way to hide the repeated boilerplate, that's an exercise for the reader.`: Is there some hint for the interested reader that preserves the current syntax from a class user's point of view? – ipc Feb 19 '13 at 22:01
  • @ipc: Edited. I tried several variations of mixin classes but in the end a simple macro was easiest for the user. – GManNickG Feb 20 '13 at 03:53
  • Ok, it's only possible with macros. By the way, in C++11 you can deduce the `selfType` with `std::decay::type` inside a trailing return type. But unfortunately it isn't implemented in current compilers. – ipc Feb 20 '13 at 18:04
0

The ever so slightly better way to do this is using dynamic_cast. You can try dynamic_casting and then calling the appropriate copy constructor. Having said that, I would go with the clone solution or try to reengineer my way out of the problem.

For example,

child *c = dynamic_cast<child*>(base);
if(c != NULL) {
    return new child(c);
}
Alex Chamberlain
  • 4,147
  • 2
  • 22
  • 49