Virtual method call from constructor

Ali Çehreli acehreli at yahoo.com
Tue Apr 4 21:54:18 UTC 2023


On 4/4/23 00:08, Chris Katko wrote:
 > dscanner reports this as a warning:
 >
 > ```D
 > struct foo{
 > this()
 >    {
 >    /* some initial setup */
 >    refresh();
 >    }
 > void refresh() { /* setup some more stuff */}
 > // [warn] a virtual call inside a constructor may lead to unexpected
 > results in the derived classes
 >
 > }
 > ```

I can understand that error for a class. Is that really a struct? If so, 
then it looks like a dscanner bug to me.

 > Firstly, are all calls virtual in a struct/class in D?

All functions are by-default virtual only for classes.

To note, not the "call" but the function can be virtual. When calls are 
involved, there can be static binding and dynamic binding. Static 
binding is when a call is resolved at compile time. Dynamic binding is 
resolved at run time through the vtbl pointer.

 > Is this any
 > different from C++?

Yes. In C++, all functions are non-virtual by-default.

 > IIRC, in C++, a struct cannot have virtual calls,

No, structs and classes are functionally exactly the same in C++. The 
only difference is their default member accessibility: public for 
structs and private for classes.

 > and virtual methods are explicit keywords in classes.

Yes.

 > Second, could you give me some case examples where this problem occurs?
 > Is the issue if I override refresh in a derived class, and the base
 > class will accidentally use child.refresh()?

Yes. :) Here is what I had learned in my C++ days: The vtbl pointer is 
stamped before the constructor is entered. However, an object is not yet 
complete without its constructor exiting. The base class's constructor 
calling a virtual function of its derived part might be disastrous 
because the derived part is not fully constructed yet. (Well, it is not 
as disastrous in D because all members have default values by-default.)

import std;

class Base {
     void foo() {
         writeln(__FUNCTION__);
     }

     this(int i) {
         writeln("Entered ", __FUNCTION__);
         foo();                              // <-- THE PROBLEM
         writeln("Exiting ", __FUNCTION__);
     }
}

class Derived : Base {
     override void foo() {
         writeln(__FUNCTION__);
     }

     this(int i) {
         writeln("Entered ", __FUNCTION__);
         super(i);
         writeln("Exiting ", __FUNCTION__);
     }
}

void main() {
     auto d = new Derived(42);
}

Here is the (annotated) output:

Entered deneme.Derived.this
Entered deneme.Base.this
deneme.Derived.foo            <-- NO!
Exiting deneme.Base.this
Exiting deneme.Derived.this

The highlighted line is a call to a derived function but even the base 
part of the object is not finished its construction yet. The weird thing 
is Base is initiating the call but it has no idea even whether it's a 
part of an inheritance hierarchy, what other types are involved, etc.

Ali



More information about the Digitalmars-d-learn mailing list