我正在阅读《 Inside C++ Object Model》一书中与对象销毁相关的主题,并遇到了这个问题。
它说在执行用户定义的析构函数之前,析构函数将被扩充。扩充的第一步是重置vptr指向该类的虚函数表的指针。我记得相应地,就在构造函数中执行用户代码(阻塞构造函数体中的语句)之前,vptr已经正确设置了 ,以防在构造过程中调用虚拟成员函数。
问题是vptr析构函数扩充中的重置步骤是否是必须的。如果是这样,那么对象中的 in 一定有可能vptr在某处被更新。这什么时候会发生?
我正在阅读《 Inside C++ Object Model》一书中与对象销毁相关的主题,并遇到了这个问题。
它说在执行用户定义的析构函数之前,析构函数将被扩充。扩充的第一步是重置vptr指向该类的虚函数表的指针。我记得相应地,就在构造函数中执行用户代码(阻塞构造函数体中的语句)之前,vptr已经正确设置了 ,以防在构造过程中调用虚拟成员函数。
问题是vptr析构函数扩充中的重置步骤是否是必须的。如果是这样,那么对象中的 in 一定有可能vptr在某处被更新。这什么时候会发生?
它可能发生在派生类的析构函数中。假设你有:
class Foo : public Bar : public Baz
现在,假设你有一个Foo. 在Foo::~Foo中,它是一个Foo,这是它必须使用的虚函数表。但是当Foo::~Foo完成时,它不再是aFoo了。它是一个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
您可以看到 aFoo是如何构造的,然后用于制作Bar用于制作 final 的a Baz。在销毁时,~Baz将其变为 aBar然后~Bar将其变为 a Foo。~Foo做最后的毁灭。
不,没有这种可能性。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。Cwill callC::foo的析构函数,will Bcall B::foo(not C::foo) 的析构函数和Awill call A::foo(再次,not 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。