20

在 C++ 中,有没有办法查询对象的类型,然后使用该信息动态创建相同类型的新对象?

例如,假设我有一个简单的 3 类层次结构:

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

现在假设我给你一个转换为 Base 类型的对象——它实际上是 Foo 类型。有没有办法查询类型并使用该信息稍后创建 Foo 类型的新对象?

4

8 回答 8

11

克隆方法

查询类型并允许您从该信息构造的语言没有提供任何内容,但是您可以通过各种方式为您的类层次结构提供功能,其中最简单的是使用虚拟方法:

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 ::functionboost::bind使其易于使用)。

于 2010-01-09T09:15:33.500 回答
6

通过基类创建对象副本的常用方法是添加一个克隆方法,该方法本质上是一个多态复制构造函数。这个虚函数通常需要在每个派生类中定义,但是您可以通过使用奇怪的循环模板模式来避免一些复制和粘贴:

// 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() 之外,它还有许多其他用途,例如双重分派。

于 2010-01-09T10:28:40.157 回答
3

只有一些 hacky 方法可以做到这一点。

第一个,恕我直言,最丑的是:

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

请注意,这仅在您启用了 RTTI 并且 Base 包含一些虚拟功能时才有效。

第二个更简洁的版本是在基类中添加一个纯虚拟克隆函数

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*。您可能希望这会破坏事情,但由于 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();
}
于 2010-01-09T09:17:57.137 回答
1

我在我的项目中使用宏来综合这些方法。我现在只是在研究这种方法,所以我可能错了,但这是我的 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;
    }
 }

希望能帮助到你。

于 2012-07-20T05:39:19.213 回答
1

您可以使用 egtypeid来查询对象的动态类型,但我不知道有一种方法可以直接从类型信息中实例化一个新对象。

但是,除了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++ 已经有点生锈了。工厂方法可能不应该是静态的,再想一想。)

于 2010-01-09T09:26:00.397 回答
0

在 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 异常

阅读更多......

于 2010-01-09T09:08:38.303 回答
0
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);
}
于 2010-01-09T10:50:47.653 回答
0

当有非常多的类派生自同一个基类时,此代码将使您不必在每个类中都包含克隆方法。这是一种更方便的克隆方式,涉及模板和中间子类。如果层次结构足够浅,这是可行的。

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)
}
于 2017-08-11T21:58:37.800 回答