Call a virtual function inside the constructor using an object-expression

前端 未结 2 1982
借酒劲吻你
借酒劲吻你 2021-01-13 05:44

Code:

#include 

using std::cout;
using std::endl;

struct A
{
    virtual void foo()
    {
        cout << \"A\" << endl;
    }
         


        
相关标签:
2条回答
  • 2021-01-13 05:58

    §1.8 [intro.object]/p2-3:

    Objects can contain other objects, called subobjects. A subobject can be a member subobject (9.2), a base class subobject (Clause 10), or an array element. An object that is not a subobject of any other object is called a complete object.

    For every object x, there is some object called the complete object of x, determined as follows:

    • If x is a complete object, then x is the complete object of x.
    • Otherwise, the complete object of x is the complete object of the (unique) object that contains x.

    In essence, the sentence you cited makes doing static_cast<C*>(this)->foo(); in B's constructor undefined behavior in your code, even if the complete object being constructed is a C. The standard actually provides a pretty good example here:

    struct V {
        virtual void f();
        virtual void g();
    };
    struct A : virtual V {
        virtual void f();
    };
    struct B : virtual V {
        virtual void g();
        B(V*, A*);
    };
    
    struct D : A, B {
        virtual void f();
        virtual void g();
        D() : B((A*)this, this) { }
    };
    
    B::B(V* v, A* a) {
        f();    // calls V::f, not A::f
        g();    // calls B::g, not D::g
        v->g(); // v is base of B, the call is well-defined, calls B::g
        a->f(); // undefined behavior, a’s type not a base of B
    }
    

    In fact, you can already see the undefined behavior show up in this example if you run it: Ideone's compiler (GCC) actually calls V::f() on the a->f(); line, even though the pointer is referring to a fully constructed A subobject.

    0 讨论(0)
  • 2021-01-13 06:17

    This is kind of tricky and I had to edit the post several times (thanks to the guys who helped me out), I'll try to get it simple and referring to N3690:

    §12.7.4 states

    Member functions, including virtual functions (10.3), can be called during construction or destruction (12.6.2).

    and this is what you've been doing in B's constructor

    B::B()
    {
        b.foo(); // virtual
        foo(); // virtual
    }  
    

    This is perfectly legit for now. The this pointer (implicitly used in the second function call) always points to the object being constructed.

    Then the standard also says:

    When a virtual function is called directly or indirectly from a constructor and the object to which the call applies is the object (call it x) under construction or destruction the function called is the final overrider in the constructor’s or destructor’s class and not one overriding it in a more-derived class (thus ignore the more-derived versions of the function)

    so the vtable is not completely walked as you might think but stops to the constructor's class' version of the virtual function (see http://www.parashift.com/c%2B%2B-faq-lite/calling-virtuals-from-ctors.html).

    Still legit.

    Finally to your point:

    If the virtual function call uses an explicit class member access e.g. (object.vfunction() or object->vfunction()) and the object expression refers to the complete object of x or one of that object’s base class subobjects but not x or one of its base class subobjects, (i.e. not the object under construction or one of its base class subobjects), the behavior is undefined.

    to understand this sentence we first need to understand what a complete object of x means:

    §1.8.2

    Objects can contain other objects, called subobjects. A subobject can be a member subobject (9.2), a base class subobject (Clause 10), or an array element. An object that is not a subobject of any other object is called a complete object.

    For every object x, there is some object called the complete object of x, determined as follows:

    — If x is a complete object, then x is the complete object of x.

    — Otherwise, the complete object of x is the complete object of the (unique) object that contains x

    if you put together the passage above with the previous one you get that you can't call a virtual function referring to "the complete type" of a base class (i.e. the derived object not yet constructed) or an object which owns that member or array element.

    If you were to explicitly refer to C in B's constructor:

    B::B() {
        static_cast<C*>(this)->foo(); // Refers to the complete object of B, i.e. C
    }
    
    struct C : B
    {
        C() : B(){ }
    }
    

    then you would have had undefined behavior.

    The intuitive (more or less) reason is that

    • Calling a virtual or member function in a constructor is allowed and in case of virtual functions it "stops the virtual hierarchy walk" to that object and calls its version of the function (see http://www.parashift.com/c%2B%2B-faq-lite/calling-virtuals-from-ctors.html)

    • Anyway if you do that from a subobject referring to a complete object of that subobject (re-read the standard passage), then it's undefined behavior

    Rule of thumb: don't call virtual functions in your constructors/destructors if you're not really sure you can.

    In case I got something wrong please let me know in the comments below and I'll fix the post. Thanks!

    0 讨论(0)
提交回复
热议问题