Ответ 1
decltype
- статическая конструкция. Как и все конструкторы ввода С++, он не может вывести тип среды выполнения объекта. decltype(*original)
- это просто Base&
.
Парадокс clone используется для создания копии производного класса без кавычек до типа базового класса. К сожалению, clone
должен быть реализован в каждом подклассе (или с mixin с CRTP).
Есть ли вероятность, что С++ 11 decltype
делает это ненужным?
Я не думаю, что приведенный ниже код действительно копирует original
, но просто указывает на него ссылку. Когда я пытался использовать new decltype(*original)
, я получаю сообщение об ошибке:
error: new cannot be applied to a reference type
.
Есть ли еще clone
путь в С++ 11? Или существует какой-то новый способ использования RTTI для копирования объекта производного класса из указателя базового класса?
#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;
}
decltype
- статическая конструкция. Как и все конструкторы ввода С++, он не может вывести тип среды выполнения объекта. decltype(*original)
- это просто Base&
.
decltype
(как следует из названия) дает объявленный тип (статический тип) выражения, к которому он применяется.
decltype(*original)
Base&
, поэтому ваш код будет печатать
Derived 1
Base
Derived 1
но в третьем случае копия не будет выполнена.
Клон (или некоторый вариант шаблона) по-прежнему остается в С++ 11.
decltype
не может и не восстанавливает динамический тип объекта. Это чисто статическая конструкция.
Нет никакого волшебного способа скопировать объект. Вы должны вызвать конструктор своего точного окончательного динамического типа где-нибудь.
Достаточно ли клонировать путь к С++ 11? Или существует какой-то новый способ использования RTTI для копирования объекта производного класса из указателя базового класса?
В случае, если кто-то заинтересован в неинвазивном клонировании, С++ 11 lambdas, похоже, предоставляют некоторые новые возможности клонирования: размышляя о проблеме клонирования, я пришел к выводу, что тот, кто изготовил оригинальный экземпляр объекта также должен быть тот, кто может помочь построить реплику. Рассмотрим случай, когда все ваши объекты производятся некоторым Factory
. В этом случае, помимо обычного интерфейса create
, ваш factory может быть оснащен интерфейсом clone
, например. так:
#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;
}
Трюк заключается в том, чтобы запомнить, как оригинал был создан в методе Factory::create
(путем сопоставления указателя на объект с лямбдой, который будет вызывать экземпляр-конструктор). Никогда не выбрасывайте старые чертежи, вам может понадобиться их позже; -)
Честно говоря, я по-прежнему предпочитаю старое решение clone
с CRTP и т.д., но это может вызвать некоторые новые идеи или пригодиться в другом случае.
Вышеприведенный код также находится в http://ideone.com/kIPFt2
EDIT: Как прокомментировал wjl, первая версия кода приведет к постоянному росту cloneFnMap
. Подумав об этом, я решил исправить код, введя пользовательский отладчик в возвращаемый shared_ptr
так, чтобы cloneFnMap
также был очищен. Тем не менее, это следует просто рассматривать как экспериментальный код концептуальной концепции.