1

我正在阅读《 Inside C++ Object Model》一书中与对象销毁相关的主题,并遇到了这个问题。

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

问题是vptr析构函数扩充中的重置步骤是否是必须的。如果是这样,那么对象中的 in 一定有可能vptr在某处被更新。这什么时候会发生?

4

2 回答 2

3

它可能发生在派生类的析构函数中。假设你有:

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做最后的毁灭。

于 2013-10-06T05:14:21.633 回答
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都会调用虚函数fooCwill 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

于 2013-10-06T05:19:54.550 回答