www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - returning struct, destructor

reply Eugene Wissner <belka caraus.de> writes:
Consider we have a function that returns a struct. So for example:

import std.stdio;

struct A {
     ~this() {
         writeln("Destruct");
     }
}

A myFunc() {
     auto a = A(), b = A();
     if (false) {
         return a;
     }
     return b;
}

void main() {
     myFunc();
}

This prints 3 times "Destruct" with dmd 0.072.1. If I remove the 
if block, it prints "Destruct" only 2 times - the behavior I'm 
expecting. Why?
Thx
Dec 21 2016
next sibling parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
On Wednesday, 21 December 2016 at 11:45:18 UTC, Eugene Wissner 
wrote:
 Consider we have a function that returns a struct. So for 
 example:

 import std.stdio;

 struct A {
     ~this() {
         writeln("Destruct");
     }
 }

 A myFunc() {
     auto a = A(), b = A();
     if (false) {
         return a;
     }
     return b;
 }

 void main() {
     myFunc();
 }

 This prints 3 times "Destruct" with dmd 0.072.1. If I remove 
 the if block, it prints "Destruct" only 2 times - the behavior 
 I'm expecting. Why?
 Thx
Structs are value types, so unless they you pass them by pointer/reference, they get copied. in myFunc it prints "Destruct" twice, one for a and once for b. In main it prints it one more for the (discarded) A returned from myfunc.
Dec 21 2016
parent Eugene Wissner <belka caraus.de> writes:
On Wednesday, 21 December 2016 at 12:32:51 UTC, Nicholas Wilson 
wrote:
 On Wednesday, 21 December 2016 at 11:45:18 UTC, Eugene Wissner 
 wrote:
 Consider we have a function that returns a struct. So for 
 example:

 import std.stdio;

 struct A {
     ~this() {
         writeln("Destruct");
     }
 }

 A myFunc() {
     auto a = A(), b = A();
     if (false) {
         return a;
     }
     return b;
 }

 void main() {
     myFunc();
 }

 This prints 3 times "Destruct" with dmd 0.072.1. If I remove 
 the if block, it prints "Destruct" only 2 times - the behavior 
 I'm expecting. Why?
 Thx
Structs are value types, so unless they you pass them by pointer/reference, they get copied. in myFunc it prints "Destruct" twice, one for a and once for b. In main it prints it one more for the (discarded) A returned from myfunc.
Why if the "if block" is removed, the code prints "Destruct" only two times. One because "a" goes out of scope and one in the main function. I don't understand, why "if (false) ..." changes the behavior
Dec 21 2016
prev sibling parent reply John C <johnch_atms hotmail.com> writes:
On Wednesday, 21 December 2016 at 11:45:18 UTC, Eugene Wissner 
wrote:
 This prints 3 times "Destruct" with dmd 0.072.1. If I remove 
 the if block, it prints "Destruct" only 2 times - the behavior 
 I'm expecting. Why?
Possibly to do with named return value optimisation.
Dec 21 2016
next sibling parent evilrat <evilrat666 gmail.com> writes:
On Wednesday, 21 December 2016 at 14:15:06 UTC, John C wrote:
 On Wednesday, 21 December 2016 at 11:45:18 UTC, Eugene Wissner 
 wrote:
 This prints 3 times "Destruct" with dmd 0.072.1. If I remove 
 the if block, it prints "Destruct" only 2 times - the behavior 
 I'm expecting. Why?
Possibly to do with named return value optimisation.
looks like it is really optimized away. an "improved" test case, even with "if" block removed or changed to true now it always the same number of operations ========================================== import std.stdio; import std.conv : to; struct A { int num; this(int n) { num = n; } this(const ref A other) { num = -1; // special case writeln("copy of " ~ to!string(other.num)); } ~this() { writeln("Destruct" ~ to!string(num)); } } A myFunc() { auto a = A(1), b = A(2); if (false) { return A(a); } return A(b); } void main() { myFunc(); }
Dec 21 2016
prev sibling parent reply Eugene Wissner <belka caraus.de> writes:
On Wednesday, 21 December 2016 at 14:15:06 UTC, John C wrote:
 On Wednesday, 21 December 2016 at 11:45:18 UTC, Eugene Wissner 
 wrote:
 This prints 3 times "Destruct" with dmd 0.072.1. If I remove 
 the if block, it prints "Destruct" only 2 times - the behavior 
 I'm expecting. Why?
Possibly to do with named return value optimisation.
Isn't an optimization that changes the behavior bad? I had a crash in the code where the destructor did something meaningfull, freed the memory (the same pointer) twice.
Dec 21 2016
next sibling parent reply kinke <noone nowhere.com> writes:
On Wednesday, 21 December 2016 at 15:01:20 UTC, Eugene Wissner 
wrote:
 On Wednesday, 21 December 2016 at 14:15:06 UTC, John C wrote:
 On Wednesday, 21 December 2016 at 11:45:18 UTC, Eugene Wissner 
 wrote:
 This prints 3 times "Destruct" with dmd 0.072.1. If I remove 
 the if block, it prints "Destruct" only 2 times - the 
 behavior I'm expecting. Why?
Possibly to do with named return value optimisation.
Isn't an optimization that changes the behavior bad? I had a crash in the code where the destructor did something meaningfull, freed the memory (the same pointer) twice.
Basic stuff such as this is appropriately tested. The named return value optimization is enforced by D (incl. unoptimized builds), so behavior doesn't change by this optimization. It's you who changed the behavior by removing the if. Due to the `if`, the compiler doesn't know whether it should construct `a` or `b` directly into the memory (sret pointee) provided by the caller. When omitting the `if`, it's clear that `b` is returned in all cases, so the compiles constructs `a` on the local stack (and destructs it before exiting the function), but emplaces `b` into `*sret` (i.e., the caller's stack) and can thus elide 1x postblit + 1x dtor.
Dec 21 2016
parent Eugene Wissner <belka caraus.de> writes:
On Wednesday, 21 December 2016 at 17:49:22 UTC, kinke wrote:
 Basic stuff such as this is appropriately tested. The named 
 return value optimization is enforced by D (incl. unoptimized 
 builds), so behavior doesn't change by this optimization. It's 
 you who changed the behavior by removing the if. Due to the 
 `if`, the compiler doesn't know whether it should construct `a` 
 or `b` directly into the memory (sret pointee) provided by the 
 caller. When omitting the `if`, it's clear that `b` is returned 
 in all cases, so the compiles constructs `a` on the local stack 
 (and destructs it before exiting the function), but emplaces 
 `b` into `*sret` (i.e., the caller's stack) and can thus elide 
 1x postblit + 1x dtor.
Thanks a lot. It makes sense. It seemed just weired that a conditional return value causes such a change. But I begin to understand the background.
Dec 21 2016
prev sibling next sibling parent reply bauss <jj_1337 live.dk> writes:
On Wednesday, 21 December 2016 at 15:01:20 UTC, Eugene Wissner 
wrote:
 On Wednesday, 21 December 2016 at 14:15:06 UTC, John C wrote:
 On Wednesday, 21 December 2016 at 11:45:18 UTC, Eugene Wissner 
 wrote:
 This prints 3 times "Destruct" with dmd 0.072.1. If I remove 
 the if block, it prints "Destruct" only 2 times - the 
 behavior I'm expecting. Why?
Possibly to do with named return value optimisation.
Isn't an optimization that changes the behavior bad? I had a crash in the code where the destructor did something meaningfull, freed the memory (the same pointer) twice.
It isn't an optimization that changes behavior. It removes an unnecessary allocation for the returning copy of the struct, as the return value is never used. Hence why it's pointless that it would be compiled anyway.
Dec 21 2016
parent kinke <noone nowhere.com> writes:
On Wednesday, 21 December 2016 at 18:02:54 UTC, bauss wrote:
 It removes an unnecessary allocation for the returning copy of 
 the struct, as the return value is never used. Hence why it's 
 pointless that it would be compiled anyway.
That's incorrect, it doesn't have anything to do with the return value being used or not.
Dec 21 2016
prev sibling parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 12/21/2016 07:01 AM, Eugene Wissner wrote:

 Isn't an optimization that changes the behavior bad? I had a crash in
 the code where the destructor did something meaningfull, freed the
 memory (the same pointer) twice.
Sounds like you ended up with two objects that owned the same resource. You can either ' disable this(this)' so that copies of this type are not allowed or you can handle the resource in a different way: Make a copy of the resource in this(this) so the two copies have their own separate resources, leave the resource to the GC, use reference counting, etc. Ali
Dec 21 2016