在构造过程中更改的对象类型

Type of an object changing during construction

本文关键字:对象 类型 过程中      更新时间:2023-10-16

我刚刚发现了以下行为:有一个从类型A派生的B类型的对象,构造A过程中的最终类型是A而不是B。这可以通过以下示例观察到:

#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

我们可以看到 typeid 在A::A()中返回的类型是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
};

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

听起来你要找的是CRTP

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

请注意,在调用notifyCreation时,MyResource尚未完成构造。可以获取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
}
};

没有像您的示例那样在构造函数中做到这一点的好方法,但是您可以为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"中覆盖它。