Создание нового объекта из динамической информации типа
В С++ существует ли способ запросить тип объекта, а затем использовать эту информацию для динамического создания нового объекта того же типа?
Например, скажем, у меня простая иерархия 3-х классов:
class Base
class Foo : public Base
class Bar : public Base
Теперь предположим, что я даю вам объект, отлитый как тип Base, который в действительности имеет тип Foo.
Есть ли способ запросить тип и использовать эту информацию для последующего создания новых объектов типа Foo?
Ответы
Ответ 1
Метод клонирования
Нет ничего, предоставляемого языком, который запрашивает тип и позволяет вам строить из этой информации, но вы можете предоставить возможность для своей иерархии классов различными способами, самым простым из которых является использование виртуального метода:
struct Base {
virtual ~Base();
virtual std::auto_ptr<Base> clone(/*desired parameters, if any*/) const = 0;
};
Это немного другое: клонировать текущий объект. Это часто то, что вы хотите, и позволяет сохранять объекты в виде шаблонов, которые затем клонируются и изменяются по желанию.
Расширяясь Tronic, вы можете генерировать функция клонирования.
Почему auto_ptr? Таким образом, вы можете использовать новое для выделения объекта, сделать передачу права собственности явным, а вызывающий абонент не сомневается, что удаление должно освободить его. Например:
Base& obj = *ptr_to_some_derived;
{ // since you can get a raw pointer, you have not committed to anything
// except that you might have to type ".release()"
Base* must_free_me = obj.clone().release();
delete must_free_me;
}
{ // smart pointer types can automatically work with auto_ptr
// (of course not all do, you can still use release() for them)
boost::shared_ptr<Base> p1 (obj.clone());
auto_ptr<Base> p2 (obj.clone());
other_smart_ptr<Base> p3 (obj.clone().release());
}
{ // automatically clean up temporary clones
// not needed often, but impossible without returning a smart pointer
obj.clone()->do_something();
}
Объект factory
Если вы предпочитаете делать именно так, как вы просили, и получите factory, который может использоваться независимо от экземпляров:
struct Factory {}; // give this type an ability to make your objects
struct Base {
virtual ~Base();
virtual Factory get_factory() const = 0; // implement in each derived class
// to return a factory that can make the derived class
// you may want to use a return type of std::auto_ptr<Factory> too, and
// then use Factory as a base class
};
Большая часть той же логики и функциональности может использоваться как для метода клона, так как get_factory выполняет половину одной и той же роли, а тип возврата (и его значение) - единственная разница.
Я уже рассматривал заводы пару раз. Вы можете адаптировать мой класс SimpleFactory, чтобы ваш объект factory (возвращаемый get_factory) содержал ссылку на глобальный factory плюс параметры для передать (например, зарегистрированное имя класса — рассмотрите, как применять boost:: function и boost:: bind, чтобы сделать это простым в использовании).
Ответ 2
Обычно используемый способ создания копий объектов по базовому классу - это добавление метода клона, который по существу является полиморфным конструктором копии. Эта виртуальная функция обычно должна быть определена в каждом производном классе, но вы можете избежать копирования и вставки с помощью Любопытно повторяющийся шаблон шаблон:
// Base class has a pure virtual function for cloning
class Shape {
public:
virtual ~Shape() {} // Polymorphic destructor to allow deletion via Shape*
virtual Shape* clone() const = 0; // Polymorphic copy constructor
};
// This CRTP class implements clone() for Derived
template <typename Derived> class Shape_CRTP: public Shape {
public:
Shape* clone() const {
return new Derived(dynamic_cast<Derived const&>(*this));
}
};
// Every derived class inherits from Shape_CRTP instead of Shape
// Note: clone() needs not to be defined in each
class Square: public Shape_CRTP<Square> {};
class Circle: public Shape_CRTP<Circle> {};
// Now you can clone shapes:
int main() {
Shape* s = new Square();
Shape* s2 = s->clone();
delete s2;
delete s;
}
Обратите внимание, что вы можете использовать один и тот же класс CRTP для любой функциональности, которая будет одинаковой для каждого производного класса, но для этого требуется знание производного типа. Для этого существует много других применений, кроме clone(), например. двойная отправка.
Ответ 3
Есть только некоторые хакерские способы сделать это.
Первый и IMHO самый уродливый:
Base * newObjectOfSameType( Base * b )
{
if( dynamic_cast<Foo*>( b ) ) return new Foo;
if( dynamic_cast<Bar*>( b ) ) return new Bar;
}
Обратите внимание, что это будет работать, только если у вас включен RTTI, а Base содержит некоторую виртуальную функцию.
Вторая версия neater - это добавление функции чистого виртуального клона в базовый класс
struct Base { virtual Base* clone() const=0; }
struct Foo : public Base { Foo* clone() const { return new Foo(*this); }
struct Bar : public Base { Bar* clone() const { return new Bar(*this); }
Base * newObjectOfSameType( Base * b )
{
return b->clone();
}
Это намного опрятно.
Одна интересная/интересная вещь в том, что
Foo::clone
возвращает a Foo*
, а Bar::clone
возвращает a Bar*
. Вы можете ожидать, что это нарушит все, но все работает из-за особенностей С++, называемых ковариантными типами возврата.
К сожалению, ковариантные типы возвращаемых данных не работают для интеллектуальных указателей, поэтому с помощью sharted_ptrs
ваш код будет выглядеть так.
struct Base { virtual shared_ptr<Base> clone() const=0; }
struct Foo : public Base { shared_ptr<Base> clone() const { return shared_ptr<Base>(new Foo(*this) ); }
struct Bar : public Base { shared_ptr<Base> clone() const { return shared_ptr<Base>(new Bar(*this)); }
shared_ptr<Base> newObjectOfSameType( shared_ptr<Base> b )
{
return b->clone();
}
Ответ 4
В С++ существует ли способ запросить тип объекта...
Да, используйте оператор typeid()
Например:
// typeid, polymorphic class
#include <iostream>
#include <typeinfo>
#include <exception>
using namespace std;
class CBase { virtual void f(){} };
class CDerived : public CBase {};
int main () {
try {
CBase* a = new CBase;
CBase* b = new CDerived;
cout << "a is: " << typeid(a).name() << '\n';
cout << "b is: " << typeid(b).name() << '\n';
cout << "*a is: " << typeid(*a).name() << '\n';
cout << "*b is: " << typeid(*b).name() << '\n';
} catch (exception& e) { cout << "Exception: " << e.what() << endl; }
return 0;
}
Выход
a is: class CBase *
b is: class CBase *
*a is: class CBase
*b is: class CDerived
Если тип typeid оценивает это указатель, которому предшествует оператор разметки (*), и этот указатель имеет нулевое значение, typeid выдает исключение bad_typeid
Прочитайте подробнее.....
Ответ 5
Вы можете использовать, например. typeid
для запроса динамического типа объекта, но я не знаю, как напрямую создать экземпляр нового объекта из информации о типе.
Однако, помимо упомянутого выше подхода clone
, вы можете использовать factory:
#include <typeinfo>
#include <iostream>
class Base
{
public:
virtual void foo() const
{
std::cout << "Base object instantiated." << std::endl;
}
};
class Derived : public Base
{
public:
virtual void foo() const
{
std::cout << "Derived object instantiated." << std::endl;
}
};
class Factory
{
public:
static Base* createFrom( const Base* x )
{
if ( typeid(*x) == typeid(Base) )
{
return new Base;
}
else if ( typeid(*x) == typeid(Derived) )
{
return new Derived;
}
else
{
return 0;
}
}
};
int main( int argc, char* argv[] )
{
Base* X = new Derived;
if ( X != 0 )
{
std::cout << "X says: " << std::endl;
X->foo();
}
Base* Y = Factory::createFrom( X );
if ( Y != 0 )
{
std::cout << "Y says: " << std::endl;
Y->foo();
}
return 0;
}
P.S.. Существенной частью этого примера кода является, конечно, метод Factory::createFrom
. (Вероятно, это не самый красивый код на С++, так как мой С++ немного ржавый. Метод factory, вероятно, не должен быть статичным, на секунду подумал.)
Ответ 6
Я использовал макросы в своем проекте для синтеза таких методов.
Сейчас я просто изучаю этот подход, поэтому могу ошибаться, но вот ответ на ваш вопрос в моем коде IAllocable.hh. Обратите внимание, что я использую GCC 4.8, но я надеюсь, что 4.7 костюмы.
#define SYNTHESIZE_I_ALLOCABLE \
public: \
auto alloc() -> __typeof__(this) { return new (__typeof__(*this))(); } \
IAllocable * __IAllocable_alloc() { return new (__typeof__(*this))(); } \
private:
class IAllocable {
public:
IAllocable * alloc() {
return __IAllocable_alloc();
}
protected:
virtual IAllocable * __IAllocable_alloc() = 0;
};
Использование:
class Usage : public virtual IAllocable {
SYNTHESIZE_I_ALLOCABLE
public:
void print() {
printf("Hello, world!\n");
}
};
int main() {
{
Usage *a = new Usage;
Usage *b = a->alloc();
b->print();
delete a;
delete b;
}
{
IAllocable *a = new Usage;
Usage *b = dynamic_cast<Usage *>(a->alloc());
b->print();
delete a;
delete b;
}
}
Надеюсь, что это поможет.
Ответ 7
class Base
{
public:
virtual ~Base() { }
};
class Foo : public Base
{
};
class Bar : public Base
{
};
template<typename T1, typename T2>
T1* fun(T1* obj)
{
T2* temp = new T2();
return temp;
}
int main()
{
Base* b = new Foo();
fun<Base,Foo>(b);
}
Ответ 8
Когда существует очень много классов, происходящих из одного и того же базового класса, этот код избавит вас от необходимости включать методы клонирования в каждый класс. Это более удобный способ клонирования, который включает в себя шаблоны и промежуточный подкласс. Это возможно, если иерархия достаточно мелкая.
struct PureBase {
virtual Base* Clone() {
return nullptr;
};
};
template<typename T>
struct Base : PureBase {
virtual Base* Clone() {
return new T();
}
};
struct Derived : Base<Derived> {};
int main() {
PureBase* a = new Derived();
PureBase* b = a->Clone(); // typeid(*b) == typeid(Derived)
}