4

I have some code where I really want to call a virtual method from a constructor. I know this is considered unsafe, and I know enough about object construction to also understand why. I also am not experiencing these problems. Currently my code is working and I think it should be fine, but I want to make sure.

Here is what I am doing:

I have some class hierarchy and there is a normal public function which just forwards to a private virtual method, as usual. However I do want to call this public method upon construction of my objects, because it is filling all data into the object. I will be absolutely sure that this virtual call comes from the leaf class, because using this virtual method from any other part of the class hierarchy simply does not make sense at all.

So in my opinion the object creation should be finished once I am doing the virtual call and everything should be fine. Is there still anything that could go wrong? I guess I'll have to mark this part of the logic with some big comments to explain why this logic should never ever be moved to any of the base clases, even though it looks like it could be moved. But other than stupidity of other programmers I should be fine, shouldn't I?

Community
  • 1
  • 1
LiKao
  • 10,408
  • 6
  • 53
  • 91
  • 3
    As long as the place where you call the function from is, as you said, a leaf class, then it will behave like you think it should – Seth Carnegie Jan 20 '12 at 21:29
  • [Never Call Virtual Functions during Construction or Destruction](http://www.artima.com/cppsource/nevercall.html) – BЈовић Jan 20 '12 at 21:49

3 Answers3

5

It is absolutely safe to call any non-abstract virtual function in the constructor or the destructor! However, its behavior may be confusing as it may not do what is expected. While the constructor of a class is executed, the static and dynamic type of the object is the type of the constructor. That is, the virtual function will never be dispatched to the override of a further derived class. Other than that, virtual dispatch actually works: e.g. when calling a virtual function via a base class pointer or reference correctly dispatches to the override in the class being currently constructor or destructed. For example (probably riddled with typos as I currently can't this code):

#include <iostream>
struct A {
    virtual ~A() {}
    virtual void f() { std::cout << "A::f()\n"; }
    void g() { this->f(); }
};
struct B: A {
    B() { this->g(); } // this prints 'B::f()'
    void f() { std::cout << "B::f()\n"; }
};
struct C: B {
    void f() { std::cout << "C::f()\n"; } // not called from B::B()
};

int main() {
    C c;
}

That is, you can call a virtual function, directly or indirectly, in the constructor or a destructor of a class if you don't want the virtual function to be dispatched to a further derived function. You can even do this is virtual function is abstract in the given class as long as it is defined. However, having an undefined abstract function being dispatched to will cause a run-time error.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • He already knows this, and it does exactly what he thinks it does because he knows how it works, as he stated in his question – Seth Carnegie Jan 20 '12 at 21:47
  • @SethCarnegie: OK. I have edited the text. However, if he knows all this the question is: why ask the question? – Dietmar Kühl Jan 20 '12 at 22:08
  • As I read it, he was just wondering if there was anything else he needed to know about: "But other than stupidity of other programmers I should be fine, shouldn't I?" – Seth Carnegie Jan 20 '12 at 22:09
  • 2
    OK. In which case he might want to consider C++2011 where he can make classes `final`. – Dietmar Kühl Jan 20 '12 at 22:20
  • @DietmarKühl: Thanks for pointing out C++11's final. I didn't realize this was finally (no pun intended) added. I'll immediately figure out if I'll be able to use it tomorrow and I'll also try to rething some more of the class hierarchy to make this more safe. – LiKao Jan 22 '12 at 20:45
3

When a constructor is called, the class is set up to be an instance of that class but not the derived class. You cannot call into a virtual function of a derived class from a base constructor. By the time you get to the constructor of the most derived class, all of the virtual functions should be safe to call.

If you wish to ensure that someone can't make an incorrect call, define the virtual function in the base class and have it assert and/or throw an exception when it is called.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • If you made the function pure virtual, would that solve it? (or, what would happen? I've never tried it before) – Seth Carnegie Jan 20 '12 at 21:50
  • @SethCarnegie, good question. I don't think there's defined behavior if you call a pure virtual function, which is what would happen if the constructor calls `Foo()` which calls virtual function `Bar()`. – Mark Ransom Jan 20 '12 at 21:59
  • @SethCarnegie: I had that problem before in some other code, and I remember it crashed the program in that case. I think there was some special error, which was produced in that case, at least for GCC, but I am not 100% sure (might have just been a SIGSEGV as well). The standard probably just defines this as UB, I would guess without looking it up. – LiKao Jan 22 '12 at 20:43
  • It's compiler/runtime-dependent, but FWIW g++ and clang++ will print 'pure virtual method called' and then call abort(). – smparkes Jan 22 '12 at 22:28
2

The rule isn't so much that you need to be in a leaf class as to realize that when you make a member call from Foo::Foo(..), the object is exactly a Foo, even if it's on its way to being a Bar (assuming Foo is derived from Bar and you're constructing a Bar instance). That's 100% reliable.

Otherwise, the fact that the member is virtual isn't all that significant. There are other pitfalls that happen just as well with non-virtual functions: if you were to call a virtual or non-virtual method that assumed the object was completely constructed but called it within the constructor before that was the case, you'd also have problems. These are just hard cases to pin down because not only must the function you call be okay, all the functions it calls must be okay.

It doesn't sound like you have a problem, it's just one of those places prone for errors to crop up.

smparkes
  • 13,807
  • 4
  • 36
  • 61