【问题标题】:Is there any possibility that `vptr` is modified outside of constructor and destructor?有没有可能在构造函数和析构函数之外修改`vptr`?
【发布时间】:2013-10-12 21:26:17
【问题描述】:
我正在阅读Inside C++ Object Model一书中与对象销毁相关的主题,遇到了这个问题。
它表示在执行用户定义的析构函数之前,析构函数将被扩充。扩充的第一步是将vptr 指针重置为该类的虚函数表。我记得相应地,在构造函数中执行用户代码(阻塞的构造函数体中的语句)之前,vptr 已经正确设置,以防在构造过程中调用虚拟成员函数。
问题是析构函数扩充中的重置vptr 步骤是否是必须的。
如果是这样,那么对象中的vptr 肯定有可能在某处被更新。
什么时候会发生这种情况?
【问题讨论】:
标签:
c++
c++11
constructor
destructor
【解决方案1】:
它可能发生在派生类的析构函数中。假设你有:
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,该Bar 用于生成最终的Baz。在销毁时,~Baz 将其变为Bar,然后~Bar 将其变为Foo。 ~Foo 进行最后的销毁。
【解决方案2】:
不,没有这种可能性。 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 的直接非虚拟调用。