我刚刚发现了以下行为:如果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
};
我知道我可以在子节点的每个构造函数/析构函数中执行通知,但它不太健壮(如果资源在没有通知管理器的情况下实例化,则可能出现错误)。 您对解决方法有任何想法吗?
答案 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'中覆盖它。