在施工期间更改的对象类型

时间:2017-07-14 13:01:36

标签: c++ inheritance typeid

我刚刚发现了以下行为:如果B类型的对象派生自A类型,那么构建A期间的最终类型为A而不是B {1}}。通过以下示例可以观察到这一点:

#include <iostream>
#include <typeinfo>

class A
{
    public:
        A() { std::cout << &typeid(*this) << std::endl; }
};

class B : public A
{
    public:
        B() : A() { std::cout << &typeid(*this) << std::endl; }
};

int main()
{
    A a;
    B b;
    return 0;
}

此代码的运行(使用gcc 4.8.5编译)如下:

0x400ae0
0x400ae0
0x400ac0

我们可以看到A::A()中typeid返回的类型是A而不是B,然后最终类型更改为B

为什么?

在构建父类时是否可以知道“真实”的最终类型?

我的上下文如下:

我有一个父类Resource和几个继承自它的类。我还通过每次创建资源通知ResourceManager,并且必须知道所创建资源的最终类型。我正在做的是为了避免重复的代码如下,但它不起作用:

class Resource
{
  public:
    Resource() { ResourceManager::notifyCreation(*this); }
    ~Resource() { ResourceManager::notifyDestruction(*this); }
};
class MyResource : public Resource
{
  // I don't have to care to the manager here
};

我知道我可以在子节点的每个构造函数/析构函数中执行通知,但它不太健壮(如果资源在没有通知管理器的情况下实例化,则可能出现错误)。 您对解决方法有任何想法吗?

2 个答案:

答案 0 :(得分:5)

听起来像你正在寻找的是CRTP

template<typename Concrete>
struct Resource
{
    Resource() { ResourceManager::notifyCreation(*static_cast<Concrete*>(this)); }
    ~Resource() { ResourceManager::notifyDestruction(*static_cast<Concrete*>(this)); }
};

struct MyResource : Resource<MyResource>
{

};

请注意,在调用MyResource时,notifyCreation尚未完成构建。可以采用MyResource实例的地址,但这是关于可以对实例执行的所有操作。 (感谢Caleth指出这一点)

尤其来自[class.cdtor]

  

如果typeid的操作数引用正在构造或销毁的对象,并且操作数的静态类型既不是构造函数或析构函数的类,也不是其基础之一,则行为是未定义的。

因此,必须在某种程度上实施ResourceManager才能启用typeid

struct ResourceManager
{
    template<typename T>
    void notifyCreation(T&&)
    {
        add(typeid(T));  // can't apply to an expression
    }
    template<typename T>
    void notifyDestruction(T&&)
    {
        remove(typeid(T));  // can't apply to an expression
    }
};

答案 1 :(得分:1)

在构造函数中没有像在示例中那样做的好方法,但是你可以为A提供一个特殊的构造函数,即

 A(const std::type_info &info) {
    std::cout << info.name() << std::endl;
 }

并在B

B() : A(typeid(*this)) {
    std::cout << typeid(*this).name() std::endl;
}

如果你在构造函数之外执行它,你也可以在'A'中提供一个虚函数,并在'B'中覆盖它。