我正在阅读 Inside C ++ Object Model 一书中与对象破坏相关的主题并遇到这个问题。
它表示在执行用户定义的析构函数之前,析构函数将被扩充。扩充的第一步是重置指向该类的虚函数表的vptr
指针。我记得相应地,就在构造函数中执行用户代码(被阻塞的构造函数体中的语句)之前,vptr
已经被正确设置,以防在构造期间调用虚拟成员函数。
问题在于是否必须在析构函数扩充中重置vptr
步骤。
如果是这样,则必须有某种可能性在某个对象中更新对象中的vptr
。
什么时候会发生这种情况?
答案 0 :(得分:3)
它可能发生在派生类的析构函数中。说你有:
class Foo : public Bar : public Baz
现在,假设你有Foo
。在Foo::~Foo
中,它是Foo
,这是它必须使用的虚函数表。但是当Foo::~Foo
完成时,它不再是Foo
。它是Bar
,这就是它必须使用的虚函数表。当Bar::~Bar
完成时,它只是Baz
,因此在Baz::~Baz
中,它必须使用Baz
的虚拟函数表。
指向虚函数表的指针不会改变,除非在构造函数和析构函数中。
以下是一些可以使用的示例代码:
#include <string>
#include <iostream>
class Foo
{
public:
Foo() { print("Foo::Foo"); }
virtual ~Foo() { print("Foo::~Foo"); }
virtual void print(std::string j) { std::cout << j << "(Foo)" << std::endl; }
};
class Bar : public Foo
{
public:
Bar() { print("Bar::Bar"); }
virtual ~Bar() { print("Bar::~Bar"); }
virtual void print(std::string j) { std::cout << j << "(Bar)" << std::endl; }
};
class Baz : public Bar
{
public:
Baz() { print("Baz:Baz"); }
virtual ~Baz() { print("Baz::~Baz"); }
virtual void print(std::string j) { std::cout << j << "(Baz)" << std::endl; }
};
int main(void)
{
std::cout << "Constructing Baz" << std::endl;
{
Baz j;
std::cout << "Baz constructed" << std::endl;
}
std::cout << "Baz destructed" << std::endl;
}
输出是:
Constructing Baz
Foo::Foo(Foo)
Bar::Bar(Bar)
Baz:Baz(Baz)
Baz constructed
Baz::~Baz(Baz)
Bar::~Bar(Bar)
Foo::~Foo(Foo)
Baz destructed
您可以看到如何构建Foo
,然后用于制作用于制作最终Bar
的{{1}}。在销毁时,Baz
将其变为~Baz
,然后Bar
将其变为~Bar
。 Foo
做最后的破坏。
答案 1 :(得分:1)
不,没有这种可能性。 vptr
仅从构造函数和析构函数更新。
析构函数的更新是出于一个非常特殊的原因:确保从类A
的析构函数内部调用的所有虚函数都将调用层次结构中A
或更高版本中定义的虚函数,但不是位于层次结构中较低级别的类的函数。基本上,这也是为什么vptr
指针在每个构造函数中都被更新的相同(对称)原因。
例如,在此层次结构中
struct A {
virtual void foo() { std::cout << "A" << std::endl; }
~A() { foo(); }
};
struct B : A {
virtual void foo() { std::cout << "B" << std::endl; }
~B() { foo(); }
};
struct C : B {
virtual void foo() { std::cout << "C" << std::endl; }
~C() { foo(); }
};
C c;
对象c
的析构函数链中的每个析构函数都将执行对虚函数foo
的调用。 C
的析构函数将调用C::foo
,B
的析构函数将调用B::foo
(不是C::foo
),A
的析构函数将调用A::foo
(同样,不是C::foo
)。这种情况特别是因为每个析构函数显式地将vptr
指针设置为其自己类的虚拟表。
相同行为的更复杂的例子可能如下所示
struct A;
extern void (A::*fun)();
struct A {
virtual void foo() { std::cout << "A" << std::endl; }
~A() { (this->*fun)(); }
};
void (A::*fun)() = &A::foo;
struct B : A {
virtual void foo() { std::cout << "B" << std::endl; }
~B() { (this->*fun)(); }
};
struct C : B {
virtual void foo() { std::cout << "C" << std::endl; }
~C() { (this->*fun)(); }
};
C c;
不同之处在于此示例更可能在物理上使用vptr
和虚方法表来解析调用。前面的示例通常由编译器优化为对正确foo
的直接非虚拟调用。