Thread overview
Re: extern(C++) classes; dtor must go in vtable
May 21, 2018
Jonathan M Davis
May 21, 2018
Jonathan M Davis
May 21, 2018
Manu
May 22, 2018
Manu
May 21, 2018
Manu
May 21, 2018
On Monday, May 21, 2018 14:33:44 Manu via Digitalmars-d wrote:
> How do virtual destructors work in normal D classes?

It is my understanding that destructors in D are never virtual but rather that the runtime handles calling them correctly. IIRC, that relates to some of the issues that make it so that you can't do stuff like call destroy in @nogc code. And a quick test with __traits seems to indicate that destructors are indeed not virtual:

    class C
    {
        void foo() {}
        ~this() {}
    }

    pragma(msg, __traits(isVirtualFunction, C.foo));
    pragma(msg, __traits(isVirtualFunction, C.__dtor));

    void main()
    {
    }

prints

true
false

- Jonathan M Davis

May 21, 2018
On Monday, May 21, 2018 14:33:44 Manu via Digitalmars-d wrote:
> extern(C++) classes.
>
> Currently, we don't add destructors to the vtable... we suggest that users need to create some dummy virtual functions to fill those vtable slots so that the vtable layout otherwise matches the C++ class.
>
> This leaves us in a position where destruction doesn't work for polymorphic types.

How are you destroying any C++ types from D at all?

It was my understanding that you had to call C++ code to make that happen. extern(C++) classes get constructed and destroyed/deleted in C++ code. They're used in D code but aren't constructed or destroyed in D code. If that's the case, then while having the virtual table line up properly definitely matters, whether D treats the destructor as virtual is irrelevant, because it's never called from D code.

I certainly have no problem with attempts to improve our integration with C++, but it sounds to me like you're talking like you're currently calling C++ destructors from D, and it's not working because they're not virtual, when it's my understanding that doing that at all is unsupported - though my understanding could certainly be outdated.

- Jonathan M Davis

May 21, 2018
On 21 May 2018 at 14:53, Jonathan M Davis via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
> On Monday, May 21, 2018 14:33:44 Manu via Digitalmars-d wrote:
>> How do virtual destructors work in normal D classes?
>
> It is my understanding that destructors in D are never virtual but rather that the runtime handles calling them correctly.

Can someone please elaborate on this?
I want to know exactly what this means in practise.
May 21, 2018
On 21 May 2018 at 15:03, Jonathan M Davis via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
> On Monday, May 21, 2018 14:33:44 Manu via Digitalmars-d wrote:
>> extern(C++) classes.
>>
>> Currently, we don't add destructors to the vtable... we suggest that users need to create some dummy virtual functions to fill those vtable slots so that the vtable layout otherwise matches the C++ class.
>>
>> This leaves us in a position where destruction doesn't work for polymorphic types.
>
> How are you destroying any C++ types from D at all?

Awkwardly... and that's the whole point here ;)
That's what I intend to fix!

DMD will need to learn to call the first vtable slot for extern(C++)
destruction.

> extern(C++) classes get constructed and destroyed/deleted in C++ code.

For now...

It is a terrible inconvenience!

> I certainly have no problem with attempts to improve our integration with C++, but it sounds to me like you're talking like you're currently calling C++ destructors from D,

Correct. But it's all bullshit, and needs to die with fire. (or, die
with a proper solution even!)

> and it's not working because they're not virtual,

You usually add `void _dtor()` at the first virtual slot and implement that function to manually call __xdtor. It's a terrible hack, but it works.

> when it's my understanding that doing that at all is unsupported - though my understanding could certainly be outdated.

Your understand is current, but hopefully it will be out of date soon! :)
May 21, 2018
On 5/21/18 6:26 PM, Manu wrote:
> On 21 May 2018 at 14:53, Jonathan M Davis via Digitalmars-d
> <digitalmars-d@puremagic.com> wrote:
>> On Monday, May 21, 2018 14:33:44 Manu via Digitalmars-d wrote:
>>> How do virtual destructors work in normal D classes?
>>
>> It is my understanding that destructors in D are never virtual but rather
>> that the runtime handles calling them correctly.
> 
> Can someone please elaborate on this?
> I want to know exactly what this means in practise.
> 

What he means by never virtual is *ALWAYS* virtual ;) At least in the sense of virtual C++ destructors, not ordinary virtual functions.

Here is what the runtime does:

https://github.com/dlang/druntime/blob/38d784a8acd9cfe6ff4dadac6883a40f392f7353/src/rt/lifetime.d#L1380

In essence, each classinfo has it's "local" destructor, which is the code you put inside the ~this() function. It runs them in the correct order (most derived first).

But it's always virtual, because it uses the classinfo stored in the object reference, not the concrete type.

I think what Jonathan meant is that you can't override the base destructor like an ordinary virtual function, but I think this is par for the course on virtual destructors.

-Steve
May 21, 2018
On 21 May 2018 at 15:39, Steven Schveighoffer via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
> On 5/21/18 6:26 PM, Manu wrote:
>>
>> On 21 May 2018 at 14:53, Jonathan M Davis via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>>>
>>> On Monday, May 21, 2018 14:33:44 Manu via Digitalmars-d wrote:
>>>>
>>>> How do virtual destructors work in normal D classes?
>>>
>>>
>>> It is my understanding that destructors in D are never virtual but rather that the runtime handles calling them correctly.
>>
>>
>> Can someone please elaborate on this?
>> I want to know exactly what this means in practise.
>>
>
> What he means by never virtual is *ALWAYS* virtual ;) At least in the sense of virtual C++ destructors, not ordinary virtual functions.
>
> Here is what the runtime does:
>
> https://github.com/dlang/druntime/blob/38d784a8acd9cfe6ff4dadac6883a40f392f7353/src/rt/lifetime.d#L1380
>
> In essence, each classinfo has it's "local" destructor,

Oooooohhh! It's in the classinfo (and not in the vtable!).
I knew that actually, I have run into that code, I just forgot!

Okay, so it's not directly in the vtable, it's special-case... (it's actually an additional indirection over C++ to reach the dtor) Proper extern(C++) handling will need to take that same function and jam it in the first slot of the C++ vtable instead, but it should be functionally identical I think.
May 22, 2018
On 5/22/18 12:57 AM, Manu wrote:
> On 21 May 2018 at 15:39, Steven Schveighoffer via Digitalmars-d
> <digitalmars-d@puremagic.com> wrote:
>> On 5/21/18 6:26 PM, Manu wrote:
>>>
>>> On 21 May 2018 at 14:53, Jonathan M Davis via Digitalmars-d
>>> <digitalmars-d@puremagic.com> wrote:
>>>>
>>>> On Monday, May 21, 2018 14:33:44 Manu via Digitalmars-d wrote:
>>>>>
>>>>> How do virtual destructors work in normal D classes?
>>>>
>>>>
>>>> It is my understanding that destructors in D are never virtual but rather
>>>> that the runtime handles calling them correctly.
>>>
>>>
>>> Can someone please elaborate on this?
>>> I want to know exactly what this means in practise.
>>>
>>
>> What he means by never virtual is *ALWAYS* virtual ;) At least in the sense
>> of virtual C++ destructors, not ordinary virtual functions.
>>
>> Here is what the runtime does:
>>
>> https://github.com/dlang/druntime/blob/38d784a8acd9cfe6ff4dadac6883a40f392f7353/src/rt/lifetime.d#L1380
>>
>> In essence, each classinfo has it's "local" destructor,
> 
> Oooooohhh! It's in the classinfo (and not in the vtable!).
> I knew that actually, I have run into that code, I just forgot!

It's in the classinfo, but so is the vtable. It's not really any different, except the "slot" for the destructor is not in the vtable array, it's just a separate member. See here: https://github.com/dlang/druntime/blob/38d784a8acd9cfe6ff4dadac6883a40f392f7353/src/object.d#L957

> Okay, so it's not directly in the vtable, it's special-case... (it's
> actually an additional indirection over C++ to reach the dtor)

Not sure how C++ works. I think it's an extra indirection for any virtual function in D, since D only stores the classinfo pointer. In fact, the destructor is one less indirection, because it's not in an array.

> Proper extern(C++) handling will need to take that same function and
> jam it in the first slot of the C++ vtable instead, but it should be
> functionally identical I think.

I don't know if that's true. The destructor itself does NOT call the base class destructor automatically. That's why the machinery in lifetime.d exists. It's actually, come to think about it, a LOT of indirections (2 per class in the hierarchy).

It all depends on how C++ implements virtual destructors.

...Rereading...

Oh wait, you mean jam the *lifetime* function in the slot? Yeah, that would probably work.

-Steve