Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Virtual calls during construction / destruction

C++ Standard 12.7/4 says:

When a virtual function is called directly or indirectly from a constructor or from a destructor, including during the construction or destruction of the class's non-static data members, 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. If the virtual function call uses an explicit class member access (5.2.5) 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, the behavior is undefined.

This text is the same in all versions I checked (though in C++03 it was paragraph 12.7/3).

My question is about the phrase "uses an explicit class member access". Possibly the point of that phrase is to point out that in the constructor/destructor body, virtual calls that use the implicit this-> are safe since the object expression does refer to the object x:

struct A;
A* p;

struct A {
    A() { p = this; }
    virtual ~A() { if (p == this) p = nullptr; }
    virtual void f() {}
};

struct B {
    B();
    virtual ~B();
    virtual void g() {}
};

struct C : public A, public B {
    virtual void f() {}
    virtual void g() {}
};

B::B() {
    if (p) p->f(); // UB if `p` and `this` point at same complete object
    g();           // Definitely safe, calls B::g().
}

B::~B() {
    if (p) p->f(); // UB if `p` and `this` point at same complete object
    g();           // Definitely safe, calls B::g().
}

int main() {
    C c;      // UB in B::B() and B::~B()!
}

But what if the virtual function call is not syntactically in the definition of the constructor or destructor, but is called indirectly? What is the behavior of this program?

#include <iostream>

struct A {
    virtual void f() { std::cout << "A::f()\n"; }
    void h() { f(); }
};

struct B {
    explicit B(A& a) { a.h(); }
};

struct C : public A, public B {
    C() : A(), B(static_cast<A&>(*this)) {}
    virtual void f() { std::cout << "C::f()\n"; }
};

int main() {
    C c;
}

I would expect that in B::B(A&), calling a.h() is just as undefined as calling a.f(). But we can't say the last sentence in 12.7/4 applies, since the virtual function call does not use an explicit class member access. Have I missed something? Are a.f() and a.h() really supposed to act differently in this context? Is there a Defect Report related to this? Should there be?

like image 657
aschepler Avatar asked Dec 11 '25 10:12

aschepler


1 Answers

9.3.1/3 (in N3485) says

When an id-expression (5.1) that is not part of a class member access syntax (5.2.5) and not used to form a pointer to member (5.3.1) is used in a member of class X in a context where this can be used (5.1.1), if name lookup (3.4) resolves the name in the id-expression to a non-static non-type member of some class C, and if either the id-expression is potentially evaluated or C is X or a base class of X, the id-expression is transformed into a class member access expression (5.2.5) using (*this) (9.3.2) as the postfix-expression to the left of the . operator.

In your second example, this means the body of A::h() gets transformed into (*this).f(), making the call an explicit class member access. Thus the last line of 12.7/4 applies; the behavior is undefined.

like image 193
Falias Avatar answered Dec 14 '25 01:12

Falias



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!