www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Virtual method call from constructor

reply Chris Katko <ckatko gmail.com> writes:
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

}
```

Firstly, are all calls virtual in a struct/class in D? Is this 
any different from C++? IIRC, in C++, a struct cannot have 
virtual calls, and virtual methods are explicit keywords in 
classes.

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()?

Third, is there the expectation that you should _never_ call any 
internal, private, methods inside a constructor? Or do I just 
call/structure it a different way?

For a concrete example: I have a particle struct. It makes sense 
to me, to have initial setup code (placed in the refresh() 
function) called by this(), that later I can then call again; to 
reset the struct to an initial state in-memory without 
re-allocations.

I imagine in D that there's probably something like:

```D
particles[235] = foo.init;
```

but that blows up in a scenario where I'm only _partially_ 
resetting the struct data. For example, if I had a bunch of 
pointers to system modules, those values don't need to be nulled 
and re-set every time in this(), whereas the physical data like 
position, velocity, angle, need reset in refresh(). You could 
architect around that, but I'm trying to learn the language 
mechanics.
Apr 04 2023
next sibling parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
 Firstly, are all calls virtual in a struct/class in D?
In D structs are always value types without a vtable or inheritance. Classes are always reference types with a vtable, but some of the methods may be final and hence not in vtable.
 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. It'll call the entry in the vtable, not the one declared in the class if its overridden.
 Third, is there the expectation that you should _never_ call any 
internal, private, methods inside a constructor? Or do I just call/structure it a different way? No. Do what you need to do. Dscanner is a linter that may be a bit sensitive just in case you didn't realize that the behavior is doing something that you probably haven't considered, which could be problematic.
Apr 04 2023
prev sibling next sibling parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
Apr 04 2023
prev sibling next sibling parent Johan <j j.nl> writes:
On Tuesday, 4 April 2023 at 07:08:52 UTC, 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

 }
 ```

 Firstly, are all calls virtual in a struct/class in D? Is this 
 any different from C++?
Regarding this warning, the big difference between C++ and D classes is that a D class object is of type Child its whole life including during the Base class constructor execution. In C++ a class object starts as Base, Base::constructor is executed, then type is changed to Child (implemented by overwriting the vtable pointer), then Child::constructor is executed. -Johan
Apr 04 2023
prev sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 4/4/23 3:08 AM, Chris Katko wrote:
 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()?
 
An example of a problem: ```d class Base { this() { virtualCall(); } void virtualCall() {} } class Derived : Base { int *data; this() { data = new int; } override void virtualCall() { *data = 5; } } ``` A derived class constructor without an explicit `super()` call, is injected with a `super()` call at the beginning of the constructor. So in this case, the `Base` constructor runs before the `Derived` constructor. The `Base` ctor calls `virtualCall` before `Derived` is ready for it. To fix this, you can explicitly call `super()` after initializing the data: ```d this() {data = new int; super(); } ``` So there are ways to do this in a reasonable way, but that is why the warning exists. -Steve
Apr 05 2023