Posts: 409 / Nickname: bv / Registered: January 17, 2002 4:28 PM
Never Call Virtual Functions during Construction or Destruction
June 6, 2005 8:00 PM
|
The third edition of Scott Meyers' popular book, Effective C++, was recently published. In this excerpt from the new edition, Meyers explains why you should never call virtual functions during object construction and destruction.
http://www.artima.com/cppsource/nevercall.html What do you think of the way in which C++ treats virtual calls in constructors and destructors? Java, by contrast, doesn't do anything special with virtual calls during construction. Although it seems C++ would be safer because of this special treatment, Scott Meyers suggests you avoid virtual calls anyway. What's your opinion? |
Posts: 8 / Nickname: essennell / Registered: September 7, 2004 10:19 PM
Re: Never Call Virtual Functions during Construction or Destruction
June 7, 2005 11:22 PM
|
> What do you think of the way in which C++ treats virtual
> calls in constructors and destructors? Java, by contrast, > doesn't do anything special with virtual calls during > construction. Although it seems C++ would be safer because > of this special treatment, Scott Meyers suggests you avoid > virtual calls anyway. What's your opinion? I don't know what Java does, but I think C# does something similar, i.e. calls to a virtual in a constructor call the most derived version. IMO C++ does the right thing here, C# (and Java?) don't. The danger is in the data members, as Scott highlights. Taking Scott's example and trivialising it :-) namespace VirtualConstruction { public abstract class Base { public Base() { } public abstract void Log(); } public class Derived : Base { public Derived() { buf = new StringBuilder(); Log(); } public override void Log() { buf.Append( "Derived" ); Console.WriteLine( buf.ToString() ); } private StringBuilder buf; } public class MoreDerived : Derived { public MoreDerived() { buf = new StringBuilder(); Log(); } public override void Log() { buf.Append( "MoreDerived" ); Console.WriteLine( buf.ToString() ); } private StringBuilder buf; } class VirtualTester { static void Main(string[] args) { Base d = new Derived(); Base md = new MoreDerived(); } } } This is C#, and it crashes when MoreDerived is created. Note that in all cases, the StringBuilder object is created before Log() is called. Except it isn't because the base class CTR gets called before that, and in turn tries to call the more derived virtual function. Can anyone indicate what Java does with the equivalent code? Steve |
Posts: 7 / Nickname: hxa7241 / Registered: April 10, 2005 7:41 AM
java virtual methods
June 8, 2005 4:48 AM
|
In Java, virtual methods do just work in their normal way in constructors (as Mr Meyers implied). But any member variables used wouldn't be fully initialized.
Object construction in Java has an extra stage, compared to C++: member variable storage is zeroed before any constructors are executed. So those zero values are what a virtual method would encounter, since its corresponding constructor won't have been called yet. You could say Java has default construction. A Java version of Mr Love's code would throw a null-object-reference exception when attempting to do the buf.Append in MoreDerived::Log. |
Posts: 8 / Nickname: essennell / Registered: September 7, 2004 10:19 PM
Re: java virtual methods
June 8, 2005 4:59 AM
|
> In Java, virtual methods do just work in their normal way
> in constructors (as Mr Meyers implied). But any member > variables used wouldn't be fully initialized. Which is the problem - in C++, C# and Java, then. This is the heart of why I think C++ is right, C# and Java wrong in this regard. Of course, we can argue the fine points of whether (possibly) throwing an exception, or calling the "wrong" member is more, er, surprising :-) > You could say Java has default construction. Yes C# does too. > A Java version of Mr Love's code would throw a > null-object-reference exception when attempting to do the > buf.Append in MoreDerived::Log. Which is what the C# code does. The buf variable gets init'ed to null .I found this in the MSDN C# Language Spec (10.10.1, Constructor Initializers): <paraphrase> An instance constructor initializer cannot access the instance being created. Therefore it is a compile-time error to reference this in an argument expression of the constructor initializer, as is it a compile-time error for an argument expression to reference any instance member through a simple-name. </paraphrase> Surely having a base class constructor (which is part of the initialiser list for the derived class) call a virtual method is exactly the same as an initailiser list referencing "this"? |
Posts: 28 / Nickname: greggwon / Registered: April 6, 2003 1:36 PM
Re: java virtual methods
June 11, 2005 5:50 AM
|
> > In Java, virtual methods do just work in their normal
> way > > in constructors (as Mr Meyers implied). But any member > > variables used wouldn't be fully initialized. > > Which is the problem - in C++, C# and Java, then. This is > the heart of why I think C++ is right, C# and Java wrong > in this regard. In Java, there is the notation, "<ClassName>.this." that can be used to qualify an inner class reference to an outer class instance. Unfortunately, this notation is not defined for class hierarchy references related to inheritance. If it was, then you'd be able to code class Derived extends Base { public Derived() { buf = new StringBuffer(); Derived.this.Log(); } public void Log() { buf.append( "Derived"); System.out.println(buf); } } and get the desired behavior. The flexibility of explicitly being able to designate the desired behavior would be a plus for me. However, once you know the rules, Java's and C#'s behaviors are not nearly destructive as C++ would be if it acted like Java and C#. C++'s behavior is an acknowledgement of the executing environment and the hazards of a NULL reference where there is no language protection from the side effects of such, as Java and C# provide. |
Posts: 1 / Nickname: boone / Registered: June 21, 2005 7:37 PM
Re: Never Call Virtual Functions during Construction or Destruction
June 21, 2005 11:56 PM
|
Don't call virtual functions during construction or destruction, because such calls will never go to a more derived class than that of the currently executing constructor or destructor.
I usually phrase this as Virtual functions aren't virtual during construction and destruction which is much easier to understand and memorize. |
Posts: 1 / Nickname: freon / Registered: May 24, 2006 8:27 AM
Re: Never Call Virtual Functions during Construction or Destruction
May 24, 2006 0:43 PM
|
The C++ language design decision to make virtual functions non-virtual when called from ctors/dtors is a mistake that simply leads to incorrect program behavior that is difficult to debug. It would be better for C++ to prohibit calls to virtual functions from ctors/dtors and enforce this behavior with a runtime error. The programmer can easily find the source of the error and correct it by creating a non-virtual function that is called from both the virtual function and the ctor/dtor, if the intent is to achieve the existing behavior. I think that the current C++ design was simply easier to implement; my proposal would probably require initializing the vtable to zeros before construction and not adding any function addresses until after all ctors have been called.
|
Posts: 1 / Nickname: emq / Registered: June 14, 2006 11:43 PM
Re: Never Call Virtual Functions during Construction or Destruction
June 15, 2006 4:04 AM
|
All of these languages implement the virtual functions from the constructor issue in the correct way given the context of the language. It all depends on how each language initialises its data members.
For C# and Java, an object's data members are always initialised - either explicitly by the user or implicitly (to zero) by the runtime. Therefore, calling virtual methods will always have predictable results even if they're not exactly what the programmer expects in some cases. On the other hand, the data members in a C++ object are *not necessarily* initialised until *all* of the constructors in the class hierarchy have returned. Calling virtual methods that access data members from derived classes will therefore have unpredictable results and that is why it is not supported in C++. |
Posts: 1 / Nickname: herk / Registered: August 12, 2006 4:45 AM
Re: Never Call Virtual Functions during Construction or Destruction
August 12, 2006 8:49 AM
|
Calling virtual functions from constructors works for me.
(Accessing data is a separate issue!) // Re: http://www.artima.com/cppsource/nevercall.html // "Never call virtual functions during construction / destruction" // #include <iostream> using std::cout; using std::endl; class Base1 { public: Base1(){ cout << "Constructor: Base1 calling virtual Base1::vf1()..." <<endl; vf1(); } virtual ~Base1(){ cout << "Destructor : Base1" << endl;} virtual void vf1 () { cout << "virtual Base1::vf1 called!" << endl; } virtual void vf2 () = 0; }; class Derived1 : public Base1 { public: Derived1() { cout << "Constructor: Derived1 calling virtual Derived1::vf1()..." <<endl; vf1(); cout << "Constructor: Derived1 calling virtual Derived1::vf2()..." <<endl; vf2(); } ~Derived1(){ cout << "Destructor : Derived1" << endl;} virtual void vf1 () {cout << "virtual Derived1::vf1 called!" << endl;} virtual void vf2 () {cout << "virtual Derived1::vf2 called!" << endl;} }; void main () { bool use_base_class_ptr = false; if (use_base_class_ptr) { Base1 *pb1 = new Derived1; delete pb1; /** Produces the following output: **/ // Constructor: Base1 calling virtual Base1::vf2()... // virtual Base1::vf2 called! // Constructor: Derived1 calling virtual Derived1::vf1()... // virtual Derived1::vf1 called! // Constructor: Derived1 calling virtual Derived1::vf2()... // virtual Derived1::vf2 called! // Destructor : Derived1 // Destructor : Base1 // Press any key to continue } else if (!use_base_class_ptr) { Derived1 d1; /** Produces the following output: **/ // Constructor: Base1 calling virtual Base1::vf1()... // virtual Base1::vf1 called! // Constructor: Derived1 calling virtual Derived1::vf1()... // virtual Derived1::vf1 called! // Constructor: Derived1 calling virtual Derived1::vf2()... // virtual Derived1::vf2 called! // Destructor : Derived1 // Destructor : Base1 // Press any key to continue } } |
Posts: 1 / Nickname: ajlegz / Registered: October 22, 2006 3:54 PM
Re: Never Call Virtual Functions during Construction or Destruction
October 22, 2006 9:05 PM
|
Hi Herk. I googled your name to find you because our family has lost track of you! Barry and Marianne Roby, their daughters Anjaline and Sharlae are wondering where you are!! Anjaline is getting married, is 24, and Sharlae, 21 is having a baby. We'd like you to share our news!! I, Anjaline, would like for you to attend my wedding! I hope you are doing well. This is the only way I could think of to find you. Please email me if you get this. aj_legz@yahoo.com We hope to hear from you!!
|
Posts: 7 / Nickname: rgamarra79 / Registered: September 26, 2008 3:00 AM
Boost’s shared_ptr trick and copying.
July 12, 2009 9:59 AM
|
Hi,
I have been understanding that nice trick and trying to use judiciously. Some doubts came to me while thinking about the copiability of the (root) objects implementing it. To make this post short, I'll write directly the following observations (hope I'm clear and not making any mistakes). To keep this clear I'll use the typenames Scott used formerly; tough I fear confusion because, I think, that some of the issues are solved with the specific use of a class like Shared_ptr (ie a reference counting pointer)! - In Shared_ptr there's a pointer that needs to be managed (ie deleted in due time). - At first, it seems reasonable to have Shared_ptr own it. - So, on destruction Shared_ptr should delete the pointer accordingly. So, what if we want to copy these Shared_ptr objects? - In the copy constructor the actual pointed-to type isn't available: so we cannot just allocate another sp_counted_impl_p (or _pd) What I did was to add another virtual function in sp_counted_base:
and so as to make a call to this function while copy-constructing. Is this tha-way-to-go? At last, my original concern came from this: - I created my Shared_ptr-like objects with a factory function and returned them by-value. - Due to Named Return Value Optimization hardly copies will be made. - Nevertheless, one cannot just "privatize" the copy constructor. So copies will be still allowed to be made, so I thought about providing a good copy; I mean, I ruled out the choice of zeroing the pointer in the source object of a copy. Thanks a lot for your comments. Rodolfo. |
Posts: 5 / Nickname: bugmenot / Registered: July 12, 2004 11:21 PM
Re: Never Call Virtual Functions during Construction or Destruction
October 21, 2009 3:02 PM
|
If you want to call virtual functions in a constructor in C++, it might be useful to explicitly scope the function call with the class name. e.g.:
class A { public: A() { A::func(); } virtual void func() { } }; Even though the code will run the same with or without the scope, this will help future readers of the code to see that A's version of func will always be called, regardless of the type of end-object being constructed. |
Posts: 1 / Nickname: claws / Registered: April 28, 2010 7:12 PM
Re: Never Call Virtual Functions during Construction or Destruction
April 29, 2010 0:25 AM
|
I have some problems with the Scott Meyers's example. The example (if my interpretation below is correct) does not compile at all!!! In MS VS 2005 I have the following link error:
error LNK2001: unresolved external symbol "public: virtual void __thiscall Transaction::logTransaction(void)const " (?logTransaction@Transaction@@UBEXXZ) If I didn't make any mistake it turns aout that somebody forgot the first lesson is teaching: Test your code! And why he didn't call the log from the derive class constructor in order to have the expected result? class Transaction { public: Transaction(); virtual ~Transaction(void); virtual void logTransaction() const = 0; }; Transaction::Transaction() { logTransaction(); } Transaction::~Transaction() { } class BuyTransaction: public Transaction { public: virtual void logTransaction() const; }; void BuyTransaction::logTransaction() const { printf("BuyTransaction::logTransaction"); } class SellTransaction: public Transaction { public: virtual void logTransaction() const; }; void SellTransaction::logTransaction() const { printf("SellTransaction::logTransaction"); } |