【问题标题】: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 的析构函数链中的每个析构函数都会调用虚函数fooC 的析构函数将调用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 的直接非虚拟调用。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-08-16
        • 1970-01-01
        • 2012-04-13
        • 2011-11-05
        • 1970-01-01
        • 2013-07-07
        • 2020-05-02
        • 2013-07-14
        相关资源
        最近更新 更多