www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Problem with dtor behavior

reply SrMordred <patric.dexheimer gmail.com> writes:
//D-CODE
struct MyStruct{
     int id;
     this(int id){
         writeln("ctor");
     }
     ~this(){
         writeln("dtor");
     }
}

MyStruct* obj;
void push(T)(auto ref T value){
     obj[0] = value;
}

void main()
{
     obj = cast(MyStruct*)malloc( MyStruct.sizeof );
     push(MyStruct(1));
}

OUTPUT:
ctor
dtor
dtor


//C++ CODE
#include <iostream>
#include <string>
using namespace std;
void writeln(string s){ cout << s << '\n'; }

struct MyStruct{
     int id;
     MyStruct(int id){
         writeln("ctor");
     }
     ~MyStruct(){
         writeln("dtor");
     }
};

MyStruct* obj;
template<class T>
void push(T&& value){
     obj[0] = value;
}


int main()
{
     obj = (MyStruct*)malloc( sizeof(MyStruct) );
     push(MyStruct(1));
     return 0;
}

OUTPUT:
ctor
dtor


I didnt expected to see two dtors in D (this destroy any attempt 
to free resources properly on the destructor).
Can someone explain why is this happening and how to achieve the 
same behavior as c++?
Thanks :)
Jul 27
next sibling parent reply Moritz Maxeiner <moritz ucworks.org> writes:
On Thursday, 27 July 2017 at 19:19:27 UTC, SrMordred wrote:
 //D-CODE
 struct MyStruct{
     int id;
     this(int id){
         writeln("ctor");
     }
     ~this(){
         writeln("dtor");
     }
 }

 MyStruct* obj;
 void push(T)(auto ref T value){
     obj[0] = value;
 }

 void main()
 {
     obj = cast(MyStruct*)malloc( MyStruct.sizeof );
     push(MyStruct(1));
 }

 OUTPUT:
 ctor
 dtor
 dtor

 I didnt expected to see two dtors in D (this destroy any 
 attempt to free resources properly on the destructor).
AFAICT it's because opAssign (`obj[0] = value` is an opAssign) creates a temporary struct object (you can see it being destroyed by printing the value of `cast(void*) &this` in the destructor).
 Can someone explain why is this happening and how to achieve 
 the same behavior as c++?
Use std.conv.emplace: --- import std.conv : emplace; void push(T)(auto ref T value){ emplace(obj, value); } ---
Jul 27
parent reply SrMordred <patric.dexheimer gmail.com> writes:
On Thursday, 27 July 2017 at 20:28:47 UTC, Moritz Maxeiner wrote:
 On Thursday, 27 July 2017 at 19:19:27 UTC, SrMordred wrote:
 //D-CODE
 struct MyStruct{
     int id;
     this(int id){
         writeln("ctor");
     }
     ~this(){
         writeln("dtor");
     }
 }

 MyStruct* obj;
 void push(T)(auto ref T value){
     obj[0] = value;
 }

 void main()
 {
     obj = cast(MyStruct*)malloc( MyStruct.sizeof );
     push(MyStruct(1));
 }

 OUTPUT:
 ctor
 dtor
 dtor

 I didnt expected to see two dtors in D (this destroy any 
 attempt to free resources properly on the destructor).
AFAICT it's because opAssign (`obj[0] = value` is an opAssign) creates a temporary struct object (you can see it being destroyed by printing the value of `cast(void*) &this` in the destructor).
 Can someone explain why is this happening and how to achieve 
 the same behavior as c++?
Use std.conv.emplace: --- import std.conv : emplace; void push(T)(auto ref T value){ emplace(obj, value); } ---
It worked but isnt this odd? like, if I change the push(MyStruct(1)) for obj[0] = MyStruct(1); (which is what I expected in case of compiler inlining for example) the behavior change: OUTPUT: ctor dtor I´m having the feeling that this "auto ref T" don´t have the same behavior that the "T&&" on c++. I find this very strange because if i copy/paste/tweak code from c/c++ on D, and have some kind of malloc/free on the ctor/dtor the code will blow in my face without warning.
Jul 28
parent reply Moritz Maxeiner <moritz ucworks.org> writes:
On Friday, 28 July 2017 at 11:39:56 UTC, SrMordred wrote:
 On Thursday, 27 July 2017 at 20:28:47 UTC, Moritz Maxeiner 
 wrote:
 On Thursday, 27 July 2017 at 19:19:27 UTC, SrMordred wrote:
 //D-CODE
 struct MyStruct{
     int id;
     this(int id){
         writeln("ctor");
     }
     ~this(){
         writeln("dtor");
     }
 }

 MyStruct* obj;
 void push(T)(auto ref T value){
     obj[0] = value;
 }

 void main()
 {
     obj = cast(MyStruct*)malloc( MyStruct.sizeof );
     push(MyStruct(1));
 }

 OUTPUT:
 ctor
 dtor
 dtor

 I didnt expected to see two dtors in D (this destroy any 
 attempt to free resources properly on the destructor).
AFAICT it's because opAssign (`obj[0] = value` is an opAssign) creates a temporary struct object (you can see it being destroyed by printing the value of `cast(void*) &this` in the destructor).
 Can someone explain why is this happening and how to achieve 
 the same behavior as c++?
Use std.conv.emplace: --- import std.conv : emplace; void push(T)(auto ref T value){ emplace(obj, value); } ---
It worked but isnt this odd?
Here's the summary: Because D uses default initialization opAssign assumes its destination is an initialized (live) object (in this case located at `obj[0]`) and destructs this object before copying the source over it. Emplace is designed to get around this by assuming that its destination is an uninitialized memory chunk (not a live object). `MyStruct(1)` is a struct literal, not a struct object, i.e. (in contrast to struct objects) it's never destroyed. When passing the struct literal into `push`, a new struct object is created and initialized from the struct literal; this struct object is then passed into `push` instead of the struct literal, used as the source for the opAssign, and then finally destroyed after `push` returns. When assigning the struct literal directly to `obj[0]` no such extra struct object gets created, `obj[0]` still gets destroyed by opAssign and then overwritten by the struct literal. W.r.t to `auto ref`: To paraphrase the spec [1], an auto ref parameter is passed by reference if and only if it's an lvalue (i.e. if it has an accessible address). (Struct) literals are not lvalues (they do not have an address) and as such cannot be passed by reference. [1] https://dlang.org/spec/template.html#auto-ref-parameters
Jul 28
parent SrMordred <patric.dexheimer gmail.com> writes:
On Friday, 28 July 2017 at 15:49:42 UTC, Moritz Maxeiner wrote:
 [...]
Nice, a bit more clear now, thank you!
Jul 28
prev sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 27 July 2017 at 19:19:27 UTC, SrMordred wrote:
 void push(T)(auto ref T value){
     push(MyStruct(1));
 template<class T>
 void push(T&& value){
     push(MyStruct(1));
Those aren't the same... the D one will pass by value, the C++ one won't. D's auto ref means ref for lvalues, value for rvalues. C++ will do rvalue by ref too.
 I didnt expected to see two dtors in D (this destroy any 
 attempt to free resources properly on the destructor).
In D, you must write destructors such that they can be called on a default-initialized object. (This also means your malloc is wrong, since it doesn't perform the default initialization.)
 Can someone explain why is this happening and how to achieve 
 the same behavior as c++?
I would blit it over as raw memory then call the ctor instead of using the assign operator. That's what std.conv.emplace does..
Jul 28
parent SrMordred <patric.dexheimer gmail.com> writes:
On Friday, 28 July 2017 at 16:25:01 UTC, Adam D. Ruppe wrote:
 On Thursday, 27 July 2017 at 19:19:27 UTC, SrMordred wrote:
"auto ref means ref for lvalues, value for rvalues." Iep, my confusion was there. My mind is still wrapped around the rvalue references and move semantics of c++
Jul 28