11

The clone paradigm is used to make a copy of a derived class without casting down to the base class type. Unfortunately, clone must be implemented in each subclass (or with a mixin with CRTP).

Is there any chance that C++11's decltype makes this unnecessary?

I don't think the code below actually copies original, but simply points a reference to it. When I tried to use new decltype(*original), I get an error: error: new cannot be applied to a reference type.

Is clone still the way to go in C++11? Or is there some new way to use RTTI to copy a derived class object from a base class pointer?

#include <iostream>

struct Base
{
  virtual void print()
  {
    std::cout << "Base" << std::endl;
  }
};

struct Derived : public Base
{
  int val;
  Derived() {val=0;}
  Derived(int val_param): val(val_param) {}
  virtual void print()
  {
    std::cout << "Derived " << val << std::endl;
  }
};

int main() {
  Base * original = new Derived(1);
  original->print();

  // copies by casting down to Base: you need to know the type of *original
  Base * unworking_copy = new Base(*original);
  unworking_copy->print();

  decltype(*original) on_stack = *original;
  on_stack.print();
  return 0;
}
Community
  • 1
  • 1
user
  • 6,723
  • 6
  • 41
  • 86

4 Answers4

21

decltype is a static construct. Like all C++ typing constructs, it cannot deduce the runtime type of an object. decltype(*original) is just Base&.

Nicol Bolas
  • 378,677
  • 53
  • 635
  • 829
  • 2
    This answer is (along with the previous version of my answer) dangerously wrong. `decltype(*original)` is actually `Base&`. – Mankarse May 03 '12 at 03:00
  • @Mankarse Yes, that's why `onstack.print()` uses `Derived::print`. But @NicolBolas' point is well-taken: `decltype` cannot give me runtime type info. – user May 03 '12 at 03:09
  • 4
    You can use std::decay to turn Base& into Base. – Andrew Tomazos May 03 '12 at 03:09
  • @AndrewTomazos-Fathomling +1 Good to know. – user May 03 '12 at 04:05
  • @AndrewTomazos-Fathomling: interesting, why would one use `std::decay` here rather than `std::remove_reference` ? – Matthieu M. May 03 '12 at 06:09
  • std::decay is more general. It is what std::bind/std::function uses to take a parameter type and figure out what the appropriate "decayed" type is to store it in the functor with. – Andrew Tomazos May 03 '12 at 06:17
3

decltype (as its name suggests) gives the declared type (static type) of the expression that it is applied to.

decltype(*original) is Base&, so your code will print

Derived 1
Base
Derived 1

but no copy will be made in the third case.

Clone (or some variant of the pattern) is still the way to go in C++11.

Mankarse
  • 37,343
  • 9
  • 88
  • 138
3

decltype cannot and does not recover dynamic object type. It is a purely static construct.

There's no magic way to copy an object. You must call a constructor of its exact final dynamic type somewhere.

didierc
  • 14,109
  • 3
  • 30
  • 51
n. 'pronouns' m.
  • 95,181
  • 13
  • 111
  • 206
2

Is clone still the way to go in C++11? Or is there some new way to use RTTI to copy a derived class object from a base class pointer?

In case anybody is interested in a non-invasive cloning, C++11's lambdas seem to provide some new cloning facilities: Thinking about the issue of cloning, I came to admit that the one who manufactured the original instance of an object should also be the one who can help build a replica. Consider the case where all of your objects are manufactured by some Factory. In this case, additionally to the usual create interface, your factory could be equipped with a clone interface, e.g. like so:

#include <iostream>
#include <functional>
#include <memory>
#include <unordered_map>

template <typename BASE>
struct
Factory {
    private: using
    TCloneFn = std::function<std::shared_ptr<BASE>(BASE const * const)>;

    private:
    static std::unordered_map<BASE const*,TCloneFn> cloneFnMap;

    public: template <typename DERIVED_TYPE, typename...TS>
    static std::shared_ptr<BASE>
    create(TS...args) {
        BASE* obj = new DERIVED_TYPE(args...);
        const std::shared_ptr<BASE> pNewObj =
            std::shared_ptr<BASE>(
                obj,
                [&](BASE* p){
                    cloneFnMap.erase(p);
                    delete p;
                }
            );

        cloneFnMap[obj] = [&](BASE const * const orig){
            std::shared_ptr<BASE> pClone = create<DERIVED_TYPE>(std::ref(static_cast<DERIVED_TYPE const &>(*orig)));
            return pClone;
        };
        return pNewObj;
    }

    public: static std::shared_ptr<BASE>
    clone(std::shared_ptr<BASE const> original) {
        return cloneFnMap[original.get()](original.get());
    }
};

template <typename BASE> std::unordered_map<BASE const*,typename Factory<BASE>::TCloneFn> Factory<BASE>::cloneFnMap;

class Base {
    public: virtual ~Base() throw() {}
    public: virtual void whoAmI() const {
        std::cout << "I am Base instance " << this << "\n";
    }
};


class Derived : public Base {
    std::string name;
    public: Derived(std::string name) : name(name) {}
    public: Derived(const Derived&other) : name("copy of "+other.name) {
    }
    private: virtual void whoAmI() const {
        std::cout << "I am Derived instance " << this << " " << name << "\n";
    }
};

int main() {
    std::shared_ptr<Base> a = Factory<Base>::create<Derived>("Original");
    a->whoAmI();
    std::shared_ptr<Base> copy_of_a = Factory<Base>::clone(a);
    copy_of_a->whoAmI();
    std::shared_ptr<Base> copy_of_a_copy = Factory<Base>::clone(copy_of_a);
    copy_of_a_copy->whoAmI();
    return 0;
}

The trick is to remember how the original was constructed in the Factory::create method (by associating a pointer to the object with a lambda that will invoke the copy-constructor). Never throw away old blueprints, you may need them later-on;-)

Frankly, I still prefer the old clone solution with CRTP etc, but this may trigger some new ideas or come otherwise in handy.

The code above is also at http://ideone.com/kIPFt2

EDIT: As wjl commented, the first version of the code would cause cloneFnMapto steadily grow. After thinking it over, I decided to fix the code by introducing a custom deleter to the shared_ptrs being returned such that the cloneFnMap will also get cleaned up. Still, this should just be considered an experimental proof-of-concept code.

Julian
  • 1,242
  • 1
  • 10
  • 19
  • If someone were to *actually* use this technique, make sure to create the shared_ptr with a custom deleter that removes the pointer from the `cloneFnMap` when the object is destroyed, otherwise it will grow forever. – wjl Feb 21 '15 at 15:39
  • sure thing, I just tried to keep things as minimal as possible. – Julian Feb 21 '15 at 15:45