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 394
暖寄归人
暖寄归人 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:41

    I think this code is an example of the undefined behaviour referenced by the standard. In particular, it is not easy for the compiler to notice that this is undefined.

    (BTW, when I say 'compiler', I really mean 'compiler and linker'. Apologies for any confusion.)

    struct Abstract {
        virtual void pure() = 0;
        virtual void foo() {
            pure();
        }
        Abstract() {
            foo();
        }
        ~Abstract() {
            foo();
        }
    };
    
    struct X : public Abstract {
        virtual void pure() { cout << " X :: pure() " << endl; }
        virtual void impure() { cout << " X :: impure() " << endl; }
    };
    int main() {
        X x;
    }
    

    If the constructor of Abstract directly called pure(), this would obviously be a problem and a compiler can easily see that there is no Abstract::pure() to be called, and g++ gives a warning. But in this example, the constructor calls foo(), and foo() is a non-pure virtual function. Therefore, there is no straightforward basis for the compiler or linker to give a warning or error.

    As onlookers, we can see that foo is a problem if called from the constructor of Abstract. Abstract::foo() itself is defined, but it tries to call Abstract::pure and this doesn't exist.

    At this stage, you might think that the compiler should issue a warning/error about foo on the grounds that it calls a pure virtual function. But instead you should consider the derived non-abstract class where pure has been given an implementation. If you call foo on that class after construction (and assuming you haven't overriden foo), then you will get well-defined behaviour. So again, there is no basis for a warning about foo. foo is well-defined as long as it isn't called in the constructor of Abstract.

    Therefore, each method (the constructor and foo) are each relatively OK if you look on them on their own. The only reason we know there is a problem is because we can see the big picture. A very smart compiler would put each particular implementation/non-implementation into one of three categories:

    • Fully-defined: It, and all the methods it calls are fully-defined at every level in the object hierarchy
    • Defined-after-construction. A function like foo that has an implementation but which might backfire depending on the status of the methods it calls.
    • Pure virtual.

    It's a lot of work to expect a compiler and linker to track all this, and hence the standard allows compilers to compile it cleanly but give undefined behaviour.

    (I haven't mentioned the fact that it is possible to give implementations to pure-virtual methods. This is new to me. Is it defined properly, or is it just a compiler-specific extension? void Abstract :: pure() { })

    So, it's not merely undefined 'because the standard says so`. You have to ask yourself 'what behaviour would you define for the above code?'. The only sensible answer is either to leave it undefined or to mandate a run-time error. The compiler and linker won't find it easy to analyse all these dependencies.

    And to make matters worse, consider pointers-to-member-functions! The compiler or linker can't really tell if the 'problematic' methods will ever be called - it might depend on a whole load of other things that happen at runtime. If the compiler sees (this->*mem_fun)() in the constructor, it can't be expected to know how well-defined mem_fun is.

提交回复
热议问题