www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Struct copy and destruction

reply Morlan <home valentimex.com> writes:
The following code:

//***************************************************
import std.conv, std.stdio;

struct Slice {
	int[] buff;
	this(size_t len) {
		buff = new int[len];
	}
	this(this) {
		buff = buff.dup;
		writeln("postblit");
	}
}

struct S {
	string name;
	Slice slc;
	this(string name) {
		writeln(name, " constructor");
		this.name = name;
	}
	~this() {
		writeln(name, " destructor");
	}
}

void main() {
	auto s1 = S("s1");
	auto s2 = S("s2");
	s1 = s2;
	writeln("after assignment");
}
//***********************************************

produces the following output:

s1 constructor
s2 constructor
postblit
s1 destructor
after assignment
s2 destructor
s2 destructor

Note the "s1 destructor" line after "postblit". I cannot find any justification
for the destructor
call in this place either in the TDPL book or in the Language Reference. Is
this behaviour to be
expected? If so I would be grateful for the proper reference. Otherwise is it a
bug?
Apr 09 2011
next sibling parent reply spir <denis.spir gmail.com> writes:
On 04/09/2011 10:00 AM, Morlan wrote:
 The following code:

 //***************************************************
 import std.conv, std.stdio;

 struct Slice {
 	int[] buff;
 	this(size_t len) {
 		buff = new int[len];
 	}
 	this(this) {
 		buff = buff.dup;
 		writeln("postblit");
 	}
 }

 struct S {
 	string name;
 	Slice slc;
 	this(string name) {
 		writeln(name, " constructor");
 		this.name = name;
 	}
 	~this() {
 		writeln(name, " destructor");
 	}
 }

 void main() {
 	auto s1 = S("s1");
 	auto s2 = S("s2");
 	s1 = s2;
 	writeln("after assignment");
 }
 //***********************************************

 produces the following output:

 s1 constructor
 s2 constructor
 postblit
 s1 destructor
 after assignment
 s2 destructor
 s2 destructor

 Note the "s1 destructor" line after "postblit". I cannot find any
justification for the destructor
 call in this place either in the TDPL book or in the Language Reference. Is
this behaviour to be
 expected? If so I would be grateful for the proper reference. Otherwise is it
a bug?

This may not reflect actual processing sequence; instead be caused by output, precisely the order in which strings are written on the terminal, due to queuing. I often have such messy outputs for compile / build error logging, for instance, like: error 1 error 2 compilation failed // should be last error 3 There is certainly a way to flush output streams (stdout) in D (someone knows?). Then, you can actually check. Denis -- _________________ vita es estrany spir.wikidot.com
Apr 09 2011
parent reply Daniel Gibson <metalcaedes gmail.com> writes:
Am 09.04.2011 11:47, schrieb spir:
 On 04/09/2011 10:00 AM, Morlan wrote:
 The following code:

 //***************************************************
 import std.conv, std.stdio;

 struct Slice {
 int[] buff;
 this(size_t len) {
 buff = new int[len];
 }
 this(this) {
 buff = buff.dup;
 writeln("postblit");
 }
 }

 struct S {
 string name;
 Slice slc;
 this(string name) {
 writeln(name, " constructor");
 this.name = name;
 }
 ~this() {
 writeln(name, " destructor");
 }
 }

 void main() {
 auto s1 = S("s1");
 auto s2 = S("s2");
 s1 = s2;
 writeln("after assignment");
 }
 //***********************************************

 produces the following output:

 s1 constructor
 s2 constructor
 postblit
 s1 destructor
 after assignment
 s2 destructor
 s2 destructor

 Note the "s1 destructor" line after "postblit". I cannot find any
 justification for the destructor
 call in this place either in the TDPL book or in the Language
 Reference. Is this behaviour to be
 expected? If so I would be grateful for the proper reference.
 Otherwise is it a bug?

This may not reflect actual processing sequence; instead be caused by output, precisely the order in which strings are written on the terminal, due to queuing. I often have such messy outputs for compile / build error logging, for instance, like: error 1 error 2 compilation failed // should be last error 3 There is certainly a way to flush output streams (stdout) in D (someone knows?). Then, you can actually check. Denis

Or insert sleep()s
Apr 09 2011
parent reply Morlan <home valentimex.com> writes:
The essence of my problem is why is the destructor called as a
result of the assignment in the first place? There is no
information about this behaviour in the language reference. Can
anyone explain this?
Apr 09 2011
parent reply Mike Wey <mike-wey example.com> writes:
On 04/09/2011 12:42 PM, Morlan wrote:
 The essence of my problem is why is the destructor called as a
 result of the assignment in the first place? There is no
 information about this behaviour in the language reference. Can
 anyone explain this?

Because you assign a copy of s2 to s1 the struct that was originally in s1 gets has it's destructor called, sine it ceases to exist. -- Mike Wey
Apr 09 2011
parent reply Morlan <home valentimex.com> writes:
It sounds reasonable. But I cannot find information about this behaviour in the
Language Reference or TDPL book. Can you point to a relevant source?
Apr 09 2011
next sibling parent reply Dan Olson <zans.is.for.cans yahoo.com> writes:
Morlan <home valentimex.com> writes:

 It sounds reasonable. But I cannot find information about this
 behaviour in the Language Reference or TDPL book. Can you point to a
 relevant source?

I was curious too, so found in Section 7.1.5.1 the description of opAssign using a swap. That explains it I think.
Apr 09 2011
parent reply Morlan <home valentimex.com> writes:
 I was curious too, so found in Section 7.1.5.1 the description of
 opAssign using a swap.  That explains it I think.


Section 7.1.5.1 does not apply because it concerns the case where you define your own overload of the assignment operator. In my example the default assignment is used. The description of the default assignment is somewhat vague and does not ever mention the fact that the destructor of the target is called as its byproduct. In fact, if you add opAssign to S you will see that the destructor is not called any more. It is probably assumed that if you define your own assignment its up to you to take care of such details. This whole subject needs some reworking in the documentation.
Apr 09 2011
parent Dan Olson <zans.is.for.cans yahoo.com> writes:
Morlan <home valentimex.com> writes:

 I was curious too, so found in Section 7.1.5.1 the description of
 opAssign using a swap.  That explains it I think.


Section 7.1.5.1 does not apply because it concerns the case where you define your own overload of the assignment operator. In my example the default assignment is used. The description of the default assignment is somewhat vague and does not ever mention the fact that the destructor of the target is called as its byproduct. In fact, if you add opAssign to S you will see that the destructor is not called any more. It is probably assumed that if you define your own assignment its up to you to take care of such details. This whole subject needs some reworking in the documentation.

But if you define the more general opAssign() that takes a value for the rhs instead of a ref, then the dtor is called (second example on page 257 with swap). Let me explain my thinking, maybe I was inferring too much. I noticed that the example of opAssign with the swap takes a rhs by value, so it will be a copy with its own lifecycle and must have the dtor called. The compiler could generate opAssign taking rhs by value. I think if the address of struct S to the ctor and dtor is printed (e.g.) writeln(&this, ": ", name, " destructor"); it will show that the dtor is question is called on copy. I don't think this is a bug, just compiler implementation of assignment so it can take non lvalues. -- Dan
Apr 10 2011
prev sibling parent Mike Wey <mike-wey example.com> writes:
On 04/09/2011 04:34 PM, Morlan wrote:
 It sounds reasonable. But I cannot find information about this behaviour in the
 Language Reference or TDPL book. Can you point to a relevant source?

Section 7.1.3.6 of TPLD talks about struct destructors, but like the online documentation it only talks about calling the destructors when they go out of scope. But what whould you think it should do when there is no longer any reference to one of the structs? -- Mike Wey
Apr 10 2011
prev sibling parent reply Jason House <jason.james.house gmail.com> writes:
I agree that the output ordering does not make sense. Try altering your example
slightly so the program will segfault or do some other nonsensical thing if
that is truly the order of operations. Once you have that, it'd make a great
bugzilla entry! It definitely looks like a bug to me.

Morlan Wrote:

 The following code:
 
 //***************************************************
 import std.conv, std.stdio;
 
 struct Slice {
 	int[] buff;
 	this(size_t len) {
 		buff = new int[len];
 	}
 	this(this) {
 		buff = buff.dup;
 		writeln("postblit");
 	}
 }
 
 struct S {
 	string name;
 	Slice slc;
 	this(string name) {
 		writeln(name, " constructor");
 		this.name = name;
 	}
 	~this() {
 		writeln(name, " destructor");
 	}
 }
 
 void main() {
 	auto s1 = S("s1");
 	auto s2 = S("s2");
 	s1 = s2;
 	writeln("after assignment");
 }
 //***********************************************
 
 produces the following output:
 
 s1 constructor
 s2 constructor
 postblit
 s1 destructor
 after assignment
 s2 destructor
 s2 destructor
 
 Note the "s1 destructor" line after "postblit". I cannot find any
justification for the destructor
 call in this place either in the TDPL book or in the Language Reference. Is
this behaviour to be
 expected? If so I would be grateful for the proper reference. Otherwise is it
a bug?

Apr 09 2011
parent Brad Roberts <braddr puremagic.com> writes:
The next version of dmd will contain a number of bug fixes for struct ctor/dtor
and lifetime management issues.  Unless
you're testing with the most current dmd code in git, I'd hold off.

On 4/9/2011 8:01 AM, Jason House wrote:
 I agree that the output ordering does not make sense. Try altering your
example slightly so the program will segfault or do some other nonsensical
thing if that is truly the order of operations. Once you have that, it'd make a
great bugzilla entry! It definitely looks like a bug to me.
 
 Morlan Wrote:
 
 The following code:

 //***************************************************
 import std.conv, std.stdio;

 struct Slice {
 	int[] buff;
 	this(size_t len) {
 		buff = new int[len];
 	}
 	this(this) {
 		buff = buff.dup;
 		writeln("postblit");
 	}
 }

 struct S {
 	string name;
 	Slice slc;
 	this(string name) {
 		writeln(name, " constructor");
 		this.name = name;
 	}
 	~this() {
 		writeln(name, " destructor");
 	}
 }

 void main() {
 	auto s1 = S("s1");
 	auto s2 = S("s2");
 	s1 = s2;
 	writeln("after assignment");
 }
 //***********************************************

 produces the following output:

 s1 constructor
 s2 constructor
 postblit
 s1 destructor
 after assignment
 s2 destructor
 s2 destructor

 Note the "s1 destructor" line after "postblit". I cannot find any
justification for the destructor
 call in this place either in the TDPL book or in the Language Reference. Is
this behaviour to be
 expected? If so I would be grateful for the proper reference. Otherwise is it
a bug?


Apr 09 2011