views:

916

answers:

7

In C++ when a virtual function is called from within a constructor it doesn't behave like a virtual function. I think everyone who encountered for the first time was surprised but on second thought it makes sense, if the derived constructor hasn't yet run the object is not yet a derived so how can a derived function be called? The preconditions haven't had the chance to be set up. Example:

class base {
public:
    base()
    {
        std::cout << "foo is " << foo() << std::endl;
    }
    virtual int foo() { return 42; }
};

class derived : public foo {
    int* ptr_;
public:
    derived(int i) : ptr_(new int(i*i)) { }
    // The following cannot be called before derived::derived due to how C++ behaves, 
    // if it was possible... Kaboom!
    virtual int foo()   { return *ptr_; } 
};

It's exactly the same for Java and .NET yet they chose to go the other way, was the only reason the principal of least surprise?

Which do you think is the correct choice?

+6  A: 

Both ways can lead to unexpected results. Your best bet is to not call a virtual function in your constructor at all.

The C++ way I think makes more sense, but leads to expectation problems when someone reviews your code. If you are aware of this situation, you should purposely not put your code in this situation for later debugging's sake.

Brian R. Bondy
+1  A: 

I think C++ offers the best semantics in terms of having the 'most correct' behavior ... however it is more work for the compiler and the code is definitiely non-intuitive to someone reading it later.

With the .NET approach the function must be very limited not to rely on any derived object state.

Rob Walker
+1  A: 

Virtual functions in constructors, why do languages differ?

Because there's no one good behaviour. I find the C++ behaviour makes more sense (since base class c-tors are called first, it stands to reason that they should call base class virtual functions--after all, the derived class c-tor hasn't run yet, so it may not have set up the right preconditions for the derived class virtual function).

But sometimes, where I want to use the virtual functions to initialize state (so it doesn't matter that they're being called with the state uninitialized) the C#/Java behaviour is nicer.

DrPizza
A: 

The comment in your code is misleading. There is no normal way for the derived class' virtual function to be called before the class' constructor is called, and I don't think that surprises you.

I wracked my brain trying to understand what the problem was here. Turns out it's just misdirection caused by the comment.

I can't edit your post. Can you fix it to say something more helpful like, "Suprise! The derived class' virtual function isn't invoked by the base class on construction"?

Thanks.

Thanks curious, I tried to clarify the comment.
Motti
A: 

Delphi makes good use of virtual constructors in the VCL GUI framework:

type
  TComponent = class
  public
    constructor Create(AOwner: TComponent); virtual; // virtual constructor
  end;

  TMyEdit = class(TComponent)
  public
    constructor Create(AOwner: TComponent); override; // override virtual constructor
  end;

  TMyButton = class(TComponent)
  public
    constructor Create(AOwner: TComponent); override; // override virtual constructor
  end;

  TComponentClass = class of TComponent;

function CreateAComponent(ComponentClass: TComponentClass; AOwner: TComponent): TComponent;
begin
  Result := ComponentClass.Create(AOwner);
end;

var
  MyEdit: TMyEdit;
  MyButton: TMyButton;
begin
  MyEdit := CreateAComponent(TMyEdit, Form) as TMyEdit;
  MyButton := CreateAComponent(TMyButton, Form) as TMyButton;
end;
Lars Truijens
+4  A: 

There's a fundamental difference in how the languages define an object's life time. In Java and .Net the object members are zero/null initialized before any constructor is run and is at this point that the object life time begins. So when you enter the constructor you've already got an initialized object.

In C++ the object life time only begins when the constructor finishes (although member variables and base classes are fully constructed before it starts). This explains the behaviour when virtual functions are called and also why the destructor isn't run if there's an exception in the constructor's body.

The problem with the Java/.Net definition of object lifetime is that it's harder to make sure the object always meets its invariant without having to put in special cases for when the object is initialized but the constructor hasn't run. The problem with the C++ definition is that you have this odd period where the object is in limbo and not fully constructed.

Daniel James
A: 

I have found the C++ behavior very annoying. You cannot write virtual functions to, for instance, return the desired size of the object, and have the default constructor initialize each item. For instance it would be nice to do:

BaseClass() { for (int i=0; i<virtualSize(); i++) initialize_stuff_for_index(i); }

Then again the advantage of C++ behavior is that it discourages constuctors like the above from being written.

I don't think the problem of calling methods that assume the constructor has been finished is a good excuse for C++. If this really was a problem then the constructor would not be allowed to call any methods, since the same problem can apply to methods for the base class.

Another point against C++ is that the behavior is much less efficient. Although the constructor knows directly what it calls, the vtab pointer has to be changed for every single class from base to final, because the constructor might call other methods that will call virtual functions. From my experience this wastes far more time than is saved by making virtual functions calls in the constructor more efficient.

Far more annoying is that this is also true of destructors. If you write a virtual cleanup() function, and the base class destructor does cleanup(), it certainly does not do what you expect.

This and the fact that C++ calls destructors on static objects on exit have really pissed me off for a long time.