Why a virtual call to a pure virtual function from a constructor is UB and a call to a non-pure virtual function is allowed by the Standard?

后端 未结 4 393
暖寄归人
暖寄归人 2021-02-02 14:14

From 10.4 Abstract Classes parag. 6 in the Standard :

\"Member functions can be called from a constructor (or destructor) of an abstract class; the effe

4条回答
  •  温柔的废话
    2021-02-02 14:46

    Before discussing why it's undefined, let's first clarify what the question is about.

    #include
    using namespace std;
    
    struct Abstract {
            virtual void pure() = 0;
            virtual void impure() { cout << " Abstract :: impure() " << endl; }
            Abstract() {
                    impure();
                    // pure(); // would be undefined
            }
            ~Abstract() {
                    impure();
                    // pure(); // would be undefined
            }
    };
    struct X : public Abstract {
            virtual void pure() { cout << " X :: pure() " << endl; }
            virtual void impure() { cout << " X :: impure() " << endl; }
    };
    int main() {
            X x;
            x.pure();
            x.impure();
    }
    

    The output of this is:

    Abstract :: impure()  // called while x is being constructed
    X :: pure()           // x.pure();
    X :: impure()         // x.impure();
    Abstract :: impure()  // called while x is being destructed.
    

    The second and third lines are easy to understand; the methods were originally defined in Abstract, but the overrides in X take over. This result would have been the same even if x had been a reference or pointer of Abstract type instead of X type.

    But this interesting thing is what happens inside the constructor and destructor of X. The call to impure() in the constructor calls Abstract::impure(), not X::impure(), even though the object being constructed is of type X. The same happens in the destructor.

    When an object of type X is being constructed, the first thing that is constructed is merely an Abstract object and, crucially, it is ignorant of the fact that it will ultimately be an X object. The same process happens in reverse for the destruction.

    Now, assuming you understand that, it is clear why the behaviour must be undefined. There is no method Abstract :: pure which could be called by the constructor or destructor, and hence it wouldn't be meaningful to try to define this behaviour (except possibly as a compilation error.)

    Update: I've just discovered that is possible to give an implementation, in the virtual class, of a pure virtual method. The question is: Is this meaningful?

    struct Abstract {
        virtual void pure() = 0;
    };
    void Abstract :: pure() { cout << "How can I be called?!" << endl; }
    

    There will never be an object whose dynamic type is Abstract, hence you'll never be able to execute this code with a normal call to abs.pure(); or anything like that. So, what is the point of allowing such a definition?

    See this demo. The compiler gives warnings, but now the Abstract::pure() method is callable from the constructor. This is the only route by which Abstract::pure() can be called.

    But, this is technically undefined. Another compiler is entitled to ignore the implementation of Abstract::pure, or even to do other crazy things. I'm not aware of why this isn't defined - but I wrote this up to try to help clear up the question.

提交回复
热议问题