digitalmars.D.learn - Virtual method call from constructor
- Chris Katko (39/39) Apr 04 2023 dscanner reports this as a warning:
- Richard (Rikki) Andrew Cattermole (12/15) Apr 04 2023 In D structs are always value types without a vtable or inheritance.
- =?UTF-8?Q?Ali_=c3=87ehreli?= (54/75) Apr 04 2023 I can understand that error for a class. Is that really a struct? If so,...
- Johan (8/23) Apr 04 2023 Regarding this warning, the big difference between C++ and D
- Steven Schveighoffer (27/31) Apr 05 2023 An example of a problem:
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
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 problemoccurs? 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 anyinternal, 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
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
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
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