C++ Base constructor calling with parameter that will be constructed in the derived constructor
Asked Answered
P

2

5

QUESTION 1)

class Base {
    Base(std::string name);

    virtual std::string generateName();
}

class Derived : Base {
    Derived();

    virtual std::string generateName();
}

here comes the question :

what method will be called on generateName() ?

Derived :: Derived : Base(generateName()) {
    //what method will be called on generateName() ? 
}

QUESTION 2)

how should i make it? if the default constructor must accept a parameter, but i need to generate that parameter in the Derived constructor?

Psychopathology answered 26/1, 2011 at 12:8 Comment(0)
D
8

First, the solution: use a static member function or a nonmember function.

As for the behavior, Derived::generateName() will be called. The long sentence in the C++ Standard that defines this behavior says (C++03 12.7/3):

When a virtual function is called directly or indirectly from a constructor (including from the mem-initializer for a data member) or from a destructor, and the object to which the call applies is the object under construction or destruction, the function called is the one defined in the constructor or destructor's own class or in one of its bases, but not a function overriding it in a class derived from the constructor or destructor's class, or overriding it in one of the other base classes of the most derived object.

Because the constructor being executed at the time of the virtual call is the Derived constructor, Derived::generateName() is called.

A now-deleted answer rightly referred to an article by Scott Meyers that recommends "Never Call Virtual Functions during Construction or Destruction." The rules for what overrider gets called are complex and difficult to remember.

Delate answered 26/1, 2011 at 12:10 Comment(8)
This would be true if generateName() were being called from inside Base::Base. But here, we are calling it in the initializer list, before we enter Base::Base. Is this still true?Dulci
Ah, you've updated your answer. This feels right now, so +1.Dulci
@Oli: No. I was wrong and have corrected the answer after researching the issue and running some tests. I'm actually somewhat surprised by the specified behavior. It feels wrong to me ;-) (It breaks the model that I've generally used to reason about virtual function calls during construction and now I have to go back and find all the answers in which I've wrongly described the process...) Thank you for questioning the original answer.Delate
I don't like the "directly or indirectly from a constructor (including from the mem-initializer for a data member) or from a destructor" part. Can call to the base constructor be considered a "mem-initializer for a data member"? I think not. We call a virtual function even before the base class is constructed, which just doesn't feel right. I have tested it with GCC and the derived function is called, but as we can see from the other answer, sometimes it just breaks. Isn't it just UB?Polder
@Sergey: In Derived() : Base() { }, Base() is a mem-initializer, though it is not a "mem-initializer for a data member." The quoted sentence says "including," though, so "a mem-initializer for a data member" is just one example of where a virtual function might be called from a constructor.Delate
@Sergey: The "other answer" to which you refer has been retracted as the issue was due to a bug in the poster's test code.Delate
@James, ok, got it. I wonder how it's implemented though. Even if I replace the call to generateName() with a call to some static or global function with this as an *arg argument, which in turn returns arg->generateName(), it still works. So it looks like vtable is initialized first to the one of the Derived class then to the one of the Base class and then back?Polder
Weird. I have just tried it again, and this time it crashed. So I can only call generateName() directly, but if I call the global generateName(this) which returns this->generateName(), it just crashes. Must be a bug in the compiler or my misunderstanding of what "directly or indirectly" means in the quote above. Anyway, the conclusion is that such calls are best avoided.Polder
M
1

Take two...

I did a run with calls to generateName() in the base class initialiser and both constructors. The output left me nonplussed:

Derived (called from Derived's Base initializer)
Base    (called from Base ctor)
Derived (called from Derived ctor)

I never imagined that a class could morph from being a derived to a base, then back to a derived in a single construction sequence. You learn something new every day.

Minetta answered 26/1, 2011 at 12:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.