调用构造函数中的虚拟函数
Calling virtual functions in constructors
考虑以下程序:
class Base
{
private:
int m_nID;
public:
Base()
{
m_nID = ClassID();
}
// ClassID returns a class-specific ID number
virtual int ClassID() { return 1; }
int GetID() { return m_nID; }
};
class Derived: public Base
{
public:
Derived()
{
}
virtual int ClassID() { return 2; }
};
int main()
{
Derived cDerived;
cout << cDerived.GetID();
return 0;
}
在上面的例子中,派生的id出人意料地是1而不是2。我已经在这里发现了关于同一个问题的类似问题。但我不明白的是,如果这是错误的,那么我们应该如何识别(派生的)类成员并使用它?我的意思是,假设我想为每个类(基类、第一个派生类、第二个派生类,它要么是基类的派生,要么是第二个衍生类,等等)指定一个唯一的id或typename,在这方面,我该如何继续行动?我认为正确的方法是,在实例化任何类对象时,在构造函数中分配一个id/name,以便立即知道类型。上述方法失败了,在这方面我还有什么其他选择?
要详细说明Karoly所说的内容,也许你可以避免在构造函数中使用类ID,但在构造之后,你可以执行以下操作:
cout << cDerived.ClassID();
没有理由让两个函数返回相同的东西,也没有理由在每个对象中存储int m_nID;
来浪费内存。
此外,您应该更改基类,使其显示:
virtual int ClassID() = 0;
如果你试图在Base构造函数中调用ClassID,尽管我没有尝试过,但这个应该会使它成为编译器错误。此外,它会使Base成为一个抽象类,所以你不能创建它的新实例(这很好)。
答案很简单:在构建任何C++对象之前,都不能使用它。如果您仍在构造基类子对象的过程中,则不能使用派生类对象,因为它尚未构造(当前正在构建的对象的vptr仍然指向基类vtable,只要你在基类构造函数内;它不会更新,直到你到达派生类构造函数。)
但是,基类构造函数是如何确定它是为常规对象还是子对象调用的呢?好吧,就像它告诉任何其他关于世界的随机信息一样:你必须明确地告诉它。例如:
struct Base {
Base() { puts("I am a whole object!"); }
protected:
Base(bool is_subobject) { assert(is_subobject); puts("I'm only part of an object."); }
};
struct Derived : Base {
Derived(): Base(/*is_subobject=*/true) { }
};
如果你想做一个真正聪明的人,你可以使用模板元编程:
struct Base {
int m_nID;
template<typename T>
Base(T *)
{
#ifdef UNSAFE
// This breaks a lot of rules, but it will work in your case.
// "this" is a Base, not a Derived, so the cast is undefined;
// and if ClassID tried to use any non-static data members,
// you'd be hosed anyway.
m_nID = reinterpret_cast<T*>(this)->T::ClassID();
#else
// This version is guaranteed to work in standard C++,
// but you lose that nice virtual-ness that you worked
// so hard for.
m_nID = T::staticClassID();
#endif
}
virtual int ClassID() { return 1; }
static int staticClassID() { return 1; }
int GetID() { return m_nID; }
};
struct Derived : Base {
Derived(): Base(this) { }
virtual int ClassID() { return 2; }
static int staticClassID() { return 2; }
};
int main() {
Derived cDerived;
std::cout << cDerived.GetID() << std::endl; // prints "2"
}
但正如Dave S在我写这个答案时所说的。。。如果您的示例是all,那么您可以只使用一个受保护的以int nID
为参数的Base构造函数,而完全忘记虚拟方法。
Derived
对象包含类型为Base
的子对象,Base
对象存在于Derived
对象的"内部"。如果你取对象的地址和它的Base
子对象的地址,Base
将在Derived
对象占用的内存区域内。
当你构造一个Derived
时,它的构造函数会运行,首先要做的是构造它的每个基类,然后构造它的每一个成员。因此,当Derived::Derived()
开始执行时,发生的第一件事就是Base::Base()
执行。在该构造函数中,对象的动态类型还不是Derived
,因为它还没有构造Derived
部分。因此,当您在Base
构造函数期间调用虚拟函数时,它会找到迄今为止唯一构造的对象的最终覆盖者:Base
部分。
在后台,当Base
构造函数启动时,它将对象的vptr设置为指向Base
的vtable,因此它指向Base
的虚拟函数。完成并运行Derived
对象构造函数之后,它将更新vptr以指向Derived
的vtable,因此它引用Derived
覆盖的函数。因此,在Base
构造函数完成之前,对象的vptr只指向Base
定义的虚拟函数的指针。
一旦Derived
构造函数更新了vptr,调用虚拟函数将调用Derived
重写,因此问题的答案是在派生构造函数中重新分配m_nId
,此时它将调用重写函数并为您提供派生类ID。
- C++无法定义虚拟函数 OUTER 类和头文件
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 尝试将unique_ptrs推送到向量时使用纯虚拟函数错误
- 有没有比在库中添加一个并非由所有派生类实现的新虚拟函数更好的设计实践
- 类型擦除的std::function与虚拟函数调用的开销
- 重写虚拟函数和继承
- 用纯虚拟函数兜圈子
- 为什么使用存储在虚拟方法表中的地址调用虚拟函数的函数会返回垃圾?
- 禁止子函数调用父级的抽象(或虚拟)函数
- 无法在子类中使用虚拟函数C++
- 无法在派生对象上运行虚拟函数
- 我可以调用从 main() 覆盖的虚拟函数吗?
- 在 C++ 中将函数获取和设置为虚拟函数
- 使用在堆栈上创建的对象调用虚拟函数
- 为什么在这种情况下不调用我的虚拟函数实现?
- 在C++中使虚拟函数私有化
- 模板继承类中的虚拟函数
- 为什么构造函数的虚拟函数调用有时有效,但其他调用却无效
- doxygenc++虚拟函数和实现
- 如何从派生类函数中调用虚拟函数