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

Reply via email to