Создание нового объекта из динамической информации о типе

В C++ есть ли способ запросить тип объекта и затем использовать эту информацию для динамического создания нового объекта того же типа?

Например, скажем, у меня есть простая иерархия 3 классов:

class Base
class Foo : public Base
class Bar : public Base

Теперь предположим, что я даю вам объект, приведенный к типу Base, который на самом деле относится к типу Foo. Есть ли способ запросить тип и использовать эту информацию для последующего создания новых объектов типа Foo?

8 ответов

Метод клонирования

Язык, который запрашивает тип и не позволяет создавать данные из этой информации, не предоставляет ничего, но вы можете обеспечить возможность иерархии классов различными способами, самым простым из которых является использование виртуального метода:

struct Base {
  virtual ~Base();
  virtual std::auto_ptr<Base> clone(/*desired parameters, if any*/) const = 0;
};

Это делает что-то немного другое: клонировать текущий объект. Это часто то, что вам нужно, и позволяет вам сохранять объекты в виде шаблонов, которые вы затем клонируете и модифицируете по своему усмотрению.

Расширяя Tronic, вы даже можете сгенерировать функцию клонирования.

Почему auto_ptr? Таким образом, вы можете использовать new для выделения объекта, сделать передачу прав собственности явной, и вызывающая сторона не сомневается, что delete должна освободить его. Например:

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();
}

Объектная фабрика

Если вы предпочитаете делать именно так, как вы просили, и получить фабрику, которую можно использовать независимо от экземпляров:

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 таким образом, чтобы ваш объект фабрики (возвращаемый get_factory) содержал ссылку на глобальную фабрику плюс параметры, которые необходимо передать для создания (например, зарегистрированное имя класса - подумайте, как применять boost::function и boost:: bind to сделать это простым в использовании).

Обычно используемый способ создания копий объектов по базовому классу - это добавление метода clone, который по сути является полиморфным конструктором копирования. Эта виртуальная функция обычно должна быть определена в каждом производном классе, но вы можете избежать копирования и вставки, используя шаблон Curly Recurring Template Pattern:

// 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(), например, двойная отправка.

Есть только несколько хакерских способов сделать это.

Первое и ИМХО самое уродливое это:

Base * newObjectOfSameType( Base * b )
{
  if( dynamic_cast<Foo*>( b ) ) return new Foo;
  if( dynamic_cast<Bar*>( b ) ) return new Bar;
}

Обратите внимание, что это будет работать только в том случае, если у вас включен RTTI, а база содержит некоторые виртуальные функции.

Вторая более аккуратная версия - добавить в базовый класс чисто виртуальную функцию клонирования.

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 возвращает Foo*, в то время как Bar::clone возвращает Bar*, Вы можете ожидать, что это сломает вещи, но все работает благодаря функции C++, называемой ковариантными типами возврата.

К сожалению, ковариантные типы возврата не работают для умных указателей, поэтому использование 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();
}

В C++ есть ли способ запросить тип объекта...

Да использовать 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

Читать дальше.....

Вы можете использовать, например, typeid запросить динамический тип объекта, но я не знаю, как напрямую создать экземпляр нового объекта из информации о типе.

Однако, кроме clone Подход, упомянутый выше, можно использовать на фабрике:

#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;
}

PS: основная часть этого примера кода, конечно, Factory::createFrom метод. (Вероятно, это не самый красивый код C++, поскольку мой C++ стал немного ржавым. Заводской метод, вероятно, не должен быть статичным, если подумать.)

Я использовал макросы в своем проекте для синтеза таких методов. Сейчас я просто исследую этот подход, поэтому я могу ошибаться, но вот ответ на ваш вопрос в моем коде 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;
    }
 }

Надеюсь, поможет.

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);
}

Когда существует очень много классов, производных от одного и того же базового класса, этот код избавит вас от необходимости включать методы клонирования в каждый класс. Это более удобный способ клонирования, включающий шаблоны и промежуточный подкласс. Это выполнимо, если иерархия достаточно мелкая.

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)
}
Другие вопросы по тегам