Invoking virtual method in constructor: difference between Java and C++
Asked Answered
M

7

20

In Java:

class Base {
    public Base() { System.out.println("Base::Base()"); virt(); }
    void virt()   { System.out.println("Base::virt()"); }
}

class Derived extends Base {
    public Derived() { System.out.println("Derived::Derived()"); virt(); }
    void virt()      { System.out.println("Derived::virt()"); }
}

public class Main {
    public static void main(String[] args) {
        new Derived();
    }
}

This will output

Base::Base()
Derived::virt()
Derived::Derived()
Derived::virt()

However, in C++ the result is different:

Base::Base()
Base::virt() // ← Not Derived::virt()
Derived::Derived()
Derived::virt()

(See http://www.parashift.com/c++-faq-lite/calling-virtuals-from-ctors.html for C++ code)

What causes such a difference between Java and C++? Is it the time when vtable is initialized?

EDIT: I do understand Java and C++ mechanisms. What I want to know is the insights behind this design decision.

Mihrab answered 18/11, 2012 at 13:7 Comment(6)
The FAQ you point to explains what happens. What else do you want to know?Derwin
@Derwin I want to know why Java is different in this sense..Mihrab
Because they're two different languages that have different semantics? Why should they do the same thing?Derwin
@Derwin Actually I want to know what's the insight behind this design decisionMihrab
"Is it the time when vtable is initialized?" You probably mean the vtable pointer, or vptr, not the vtable.Genethlialogy
I can't believe no one mentioned that calling virtual functions from constructor and destructor is undefined in C++. You could get any result.Omission
F
16

Both approaches clearly have disadvatages:

  • In Java, the call goes to a method which cannot use this properly because its members haven’t been initialised yet.
  • In C++, an unintuitive method (i.e. not the one in the derived class) is called if you don’t know how C++ constructs classes.

Why each language does what it does is an open question but both probably claim to be the “safer” option: C++’s way prevents the use of uninitialsed members; Java’s approach allows polymorphic semantics (to some extent) inside a class’ constructor (which is a perfectly valid use-case).

Frere answered 18/11, 2012 at 13:24 Comment(4)
C++ calling of the base::virtual method is quite intuitive once you accept that C++ instances are constructed like onions (from inside to outside, step by step); so, when base is constructed, there is no overriding of its methods. it happens inly later, when the onion grows...Odonnell
Re "both probably claim to be the “safer” option", no I wouldn't think so, since this is reportedly one of the most common causes of bugs in Java.Chilcote
@Cheersandhth.-Alf I can’t give you a source now but I’m sure I’ve read this. I vaguely remember that it was in a book/talk by Josh Bloch but I agree with your assessment and I can’t imagine Josh making such a blunder so maybe I misremember after all.Frere
One more thing to fix: the phrase "i.e. not the virtual one" misleadingly indicates that calls from a constructor are not virtual, but are resolved statically. But the virtual call mechanism works exactly the same in a constructor as elsewhere. In particular, if there is a call to a base class method that in turn calls a virtual method v, and v has been overridden in this class, then it's the overrider of v that's called.Chilcote
C
14

Well you have already linked to the FAQ's discussion, but that’s mainly problem-oriented, not going into the rationales, the why.

In short, it’s for type safety.

This is one of the few cases where C++ beats Java and C# on type safety. ;-)

When you create a class A, in C++ you can let each A constructor initialize the new instance so that all common assumptions about its state, called the class invariant, hold. For example, part of a class invariant can be that a pointer member points to some dynamically allocated memory. When each publicly available method preserves the class invariant, then it’s guaranteed to hold also on entry to each method, which greatly simplifies things – at least for a well-chosen class invariant!

No further checking is then necessary in each method.

In contrast, using two-phase initialization such as in Microsoft's MFC and ATL libraries you can never be quite sure whether everything has been properly initialized when a method (non-static member function) is called. This is very similar to Java and C#, except that in those languages the lack of class invariant guarantees comes from these languages merely enabling but not actively supporting the concept of a class invariant. In short, Java and C# virtual methods called from a base class constructor can be called down on a derived instance that has not yet been initialized, where the (derived) class invariant has not yet been established!

So, this C++ language support for class invariants is really great, helping do away with a lot of checking and a lot of frustrating perplexing bugs.

However, it makes a bit difficult to do derived class specific initialization in a base class constructor, e.g. doing general things in a topmost GUI Widget class’ constructor.

The FAQ item “Okay, but is there a way to simulate that behavior as if dynamic binding worked on the this object within my base class's constructor?” goes a little into that.

For a more full treatment of the most common case, see also my blog article “How to avoid post-construction by using Parts Factories”.

Chilcote answered 18/11, 2012 at 13:27 Comment(4)
"However, it makes a bit difficult to do derived class specific initialization in a base class constructor, e.g. doing general things in a topmost GUI Widget class’ constructor." What a qutote. It is the first time that I see a disadvantage in C++ approach. Many thanks.Odonnell
"difficult to do derived class specific initialization in a base class constructor" - nonsense. base class can NEVER do anything in derived classes as derived simply do not exist when base class constructor is executing...Hoar
@Califf: You could have read on to the next paragraph, which links to the FAQ item about it. Then you could have learned something, including that this link was now stale, leading to the original no-longer existing FAQ location, and where the FAQ has moved. Even though you were not aware of that and it was not your intention, thanks for making /me/ aware of this issue here. I thought all such links had been updated. Anyway, it's always/often a good idea to check the FAQ. Please do so now, and do feel free to ask more if there are then any remaining questions.Chilcote
@Califf: Please do not take the FAQ item as an authority argument, however. It's just technical information. As an authority argument it would be rather circular since it was I who convinced Marshall Cline to include it in the original FAQ. It's Marshall Cline's wording though. Including the acronym DBDI, which never caught on.Chilcote
T
7

Regardless of how it's implemented, it's a difference in what the language definition says should happen. Java allows you to call functions on a derived object that hasn't been fully initialized (it has been zero-initialized, but its constructor has not run). C++ doesn't allow that; until the derived class's constructor has run, there is no derived class.

Township answered 18/11, 2012 at 13:13 Comment(0)
A
2

Hopefully this will help:

When your line new Derived() executes, the first thing that happens is the memory allocation. The program will allocate a chunk of memory big enough to hold both the members of Base and Derrived. At this point, there is no object. It's just uninitialized memory.

When Base's constructor has completed, the memory will contain an object of type Base, and the class invariant for Base should hold. There is still no Derived object in that memory.

During the construction of base, the Base object is in a partially-constructed state, but the language rules trust you enough to let you call your own member functions on a partially-constructed object. The Derived object isn't partially constructed. It doesn't exist.

Your call to the virtual function ends up calling the base class's version because at that point in time, Base is the most derived type of the object. If it were to call Derived::virt, it would be invoking a member function of Derived with a this-pointer that is not of type Derrived, breaking type safety.

Logically, a class is something that gets constructed, has functions called on it, and then gets destroyed. You can't call member functions on an object that hasn't been constructed, and you can't call member functions on an object after it's been destroyed. This is fairly fundamental to OOP, the C++ language rules are just helping you avoid doing things that break this model.

Antiquated answered 18/11, 2012 at 14:28 Comment(0)
T
0

In Java, method invocation is based on object type, which is why it is behaving like that (I don't know much about c++).

Here your object is of type Derived, so jvm invokes method on Derived object.

If understand Virtual concept clearly, equivalent in java is abstract, your code right now is not really virtual code in java terms.

Happy to update my answer if something wrong.

Transom answered 18/11, 2012 at 13:11 Comment(1)
From my understanding, abstract in Java is virtual Type func() = 0 in C++.Mihrab
B
0

Actually I want to know what's the insight behind this design decision

It may be that in Java, every type derives from Object, every Object is some kind of leaf type, and there's a single JVM in which all objects are constructed.

In C++, many types aren't virtual at all. Furthermore in C++, the base class and the subclass can be compiled to machine code separately: so the base class does what it does without whether it's a superclass of something else.

Brawner answered 18/11, 2012 at 13:22 Comment(0)
I
0

Constructors are not polymorphic in case of both C++ and Java languages, whereas a method could be polymorphic in both languages. This means, when a polymorphic method appears inside a constructor, the designers would be left with two choices.

  • Either strictly conform to the semantics on non-polymorphic constructor and thus consider any polymorphic method invoked within a constructor as non-polymorphic. This is how C++ does§.
  • Or, compromise the strict semantics of non-polymorphic constructor and adhere to the strict semantics of a polymorphic method. Thus polymorphic methods from constructors are always polymorphic. This is how Java does.

Since none of the strategies offers or compromises any real benefits compared to other and yet Java way of doing it reduces lots of overhead (no need to differentiate polymorphism based on the context of constructors), and since Java was designed after C++, I would presume, the designer of Java opted for the 2nd option seeing the benefit of less implementation overhead.

Added on 21-Dec-2016


§Lest the statement “method invoked within a constructor as non-polymorphic...This is how C++ does” might be confusing without careful scrutiny of the context, I’m adding a formalization to precisely qualify what I meant.

If class C has a direct definition of some virtual function F and its ctor has an invocation to F, then any (indirect) invocation of C’s ctor on an instance of child class T will not influence the choice of F; and in fact, C::F will always be invoked from C’s ctor. In this sense, invocation of virtual F is less-polymorphic (compared to say, Java which will choose F based on T)
Further, it is important to note that, if C inherits definition of F from some parent P and has not overriden F, then C’s ctor will invoke P::F and even this, IMHO, can be determined statically.
Isabellaisabelle answered 22/9, 2016 at 8:6 Comment(22)
A "method" (virtual function) invoked from inside a C++ constructor is polymorphic.Genethlialogy
@Curiousguy - Say, class Base has functions ‘V1’, and ‘Disp’ as: virtual void v1(){disp();} and virtual void disp(){cout <<"Base";}. The functions in Derived class which inherits Base are as: virtual void disp(){cout <<"Derived";} and void test(){v1();}. Now for a statement like Derived d;d.test(); – conceptually we’ve two possibilities of output –(A) “Base” and (B)”Derived” get printed. Let me ask you, under what condition you’ll assert the behavior of ‘Disp’ is polymorphic – for (A), for (B), or bothIsabellaisabelle
The behavior is polymorphic when dispatching is done based on the real time; when dispatching is done based on the declared type, it is static dispatch.Genethlialogy
The polymorphic behavior of virtual function calls means the function called is the final overrider of the real type of the object, it means the type of object constructed. Inside a constructor body, the real type of *this is always the same the declared type of *this, the same as the constructor: inside Base::Base(), the real type of *this is Base.Genethlialogy
@Genethlialogy - First, if you put yourself in a language designer's shoe, you'll realize that when calling a function F (embellished with key word virtual in C++) in relation to an object of real-time type T from a constructor of a class C , it is very trivial to enforce polymorphic F "as long as" T and C are NOT related by inheritance (excluding corner cases if any). I guess, this is the case you had in mind when you've posted your 1st comment. However, my statement (1st bullet in the answer) was for the case when C and T are related. Hope this clarify the confusion.Isabellaisabelle
@Genethlialogy - Btw, wud u pls elaborate your 3rd comment - sorry didn't get it.Isabellaisabelle
Inside a ctor body C::C(), the compile type of *this is C by definition and the real type is also C, so any polymorphic call on this will call the function defined in class C.Genethlialogy
@Genethlialogy - The catch is that, the grammar (of realtime type inside a constructor) you've mentioned above is the outcome of some architectural decision pertaining to the concern -how to treat an intended polymorphic function F on type T when invoked from a constructor of type C given that C and T are related by inheritance. The aforesaid grammar may be the best for certain language but needn’t to be the only possible decision. In fact, Java has taken a different decision for the same architectural concern. Before I proceed further, pls let me know if you agree and otherwise why not!Isabellaisabelle
All polymorphic function calls are treated the same: the final overrider is invoked, always. It doesn't matter that the call is from a ctor, from a function called from inside a ctor, etc. It's a general rule. It doesn't matter what the relation between C and T is, during construction, the type of the object being constructed is the class of the constructor.Genethlialogy
@Genethlialogy - The above statement is true for all languages or any specific language? Can u pls specify!Isabellaisabelle
Yes, in both Java and C++, the final overrider is called. But in C++, the type of the object being constructed is always the same as the running ctor. In Java, the real type is the one created by new, even during construction.Genethlialogy
@Genethlialogy - So we see that the 'final overrider' would be called in both the languages, however, the rule to determine one such overrider varies. Now, if I rephrase your comment using the formalization (I mentioned earlier) using types C and T, and virtual function F, then we see that the final overrider in case of C++ would be C::F whereas for Java would be T::F. Do you agree!Isabellaisabelle
The final overrider is the one of the dynamic type of the object.Genethlialogy
@Genethlialogy - Do you agree that the final overrider (for our context) would be C::F in case of C++ and T::F in case of Java?Isabellaisabelle
If C is the constructor running and T the class that is created with new, then yes.Genethlialogy
@Genethlialogy - You got the context right..( it is also formalized, in bold letters, in my comment on 7th Dec)Isabellaisabelle
You see, the difference in final-overrider is because the this variable inside C’s ctor is evaluated as C for C++; but for Java, it is T. Further, for such context, since the runtime-type (Cin this case) of this always matches its compile-time type for C++, the final-overrider can also be decided at compile time. This is not the case for Java. So, pertaining to such context, we can now infer that an invocation to virtual F is less polymorphic in C++ than in Java, as unlike Java, the final-overrider can be decided statically in C++. This was precisely my point in my postIsabellaisabelle
@Genethlialogy – This post received a down vote almost at the same time when you’ve challenged the post via your comment. So presumably, the down-vote seems to be from you. If this is the case, then it would be nice of you to respond to the further clarifications from the author of the post, and to conclude/justify the down-vote. Thanks in advance for your cooperation.Isabellaisabelle
@curiousguy, The definition is: "polymorphism refers to a programming language's ability to process objects differently depending on their data type or class". In C++ the constructor does not obey the definition, so it is absolutely right to say that any polymorphic method invoked within a constructor is processed as non-polymorphic. As KGhatak stated.Redcoat
@joro Just no. In a constructor, the type is known, by definition. Just as the value of 0 is known to be 0. An integer variable can have different values and 0 is still 0. A language with arithmetic capabilities can deal with different integer values but 0 is still a language construct that does not have variability, you can't even discuss the variability of 0 as it would be a meaningless discussion. The constructor T::T() constructs an object of type T, by definition.Genethlialogy
""polymorphism refers to a programming language's ability (...)" A constructor is a programming language construct, not a programming language, so the definition does not apply (to constructor, or constants, or code blocks, or for loops, or any other programming language construct that does not deal directly with polymorphism). It doesn't make sense to ask if constructors support polymorphism, period. "any polymorphic method invoked within a constructor is processed as non-polymorphic" Define "processed as polymorphic" please.Genethlialogy
@Genethlialogy 1. I won't define what is "processed/invoked as polymorphic" , because KGhatak did that. 2. No one has stated that a constructor is NOT a programming language construct. I have stated that the definition of polymorphism is not obeyed by constructors. 3. General methods are also programming language construct also but they obey the definition and provide ability to process objects differently, in contrast of constructors. 4. The definition of polymorphism does not concerns implementation details as whether constructor T::T() produces T objects or Apples.Redcoat

© 2022 - 2024 — McMap. All rights reserved.