是否有可能在构造函数和析构函数之外修改`vptr`?

时间:2013-10-06 05:10:19

标签: c++ c++11 constructor destructor

我正在阅读 Inside C ++ Object Model 一书中与对象破坏相关的主题并遇到这个问题。

它表示在执行用户定义的析构函数之前,析构函数将被扩充。扩充的第一步是重置指向该类的虚函数表的vptr指针。我记得相应地,就在构造函数中执行用户代码(被阻塞的构造函数体中的语句)之前,vptr已经被正确设置,以防在构造期间调用虚拟成员函数。

问题在于是否必须在析构函数扩充中重置vptr步骤。 如果是这样,则必须有某种可能性在某个对象中更新对象中的vptr。 什么时候会发生这种情况?

2 个答案:

答案 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将其变为~BarFoo做最后的破坏。

答案 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::fooB的析构函数将调用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的直接非虚拟调用。