View mode: basic / threaded / horizontal-split · Log in · Help
September 20, 2012
Stupid scope destruction question
Is there any guaranties that `ScopeTemp` will not be destroyed before 
`f` call because it isn't used?
---
struct ScopeTemp
{
    ...
    // allocates value
    this(...) { ... }

    @property void* value() { ... }

    // destroys value
    ~this() { ... }
}

void f(void* ptr) { ... }

void main()
{
    f(ScopeTemp(...).value);
}
---
According to http://dlang.org/struct.html#StructDestructor
"Destructors are called when an object goes out of scope."
So I understand it as "it will not be destroyed before scope exit even 
if not used". Is it correct?

So the question is if `ScopeTemp`'s scope is `main` scope, not some 
possibly generated "temp scope" (don't now what documentation statements 
prohibit compiler from doing so).


Working code:
---
import std.exception;
import std.c.stdlib;

struct ScopeTemp
{
    private int* p;
    // allocates value
    this(int i)
    in { assert(i); }
    body { *(p = cast(int*) enforce(malloc(int.sizeof))) = i; }

    @disable this(this);

    @property int* value() { return p; }

    // destroys value
    ~this() { *p = 0; p = null; /*free(p);*/ }
}

void f(int* ptr)
{
    assert(*ptr == 1);
}

void main()
{
    f(ScopeTemp(1).value);
}
---
-- 
Денис В. Шеломовский
Denis V. Shelomovskij
September 20, 2012
Re: Stupid scope destruction question
20.09.2012 13:27, Denis Shelomovskij пишет:
> Is there any guaranties that `ScopeTemp` will not be destroyed before
> `f` call because it isn't used?
> ---
> struct ScopeTemp
> {
>      ...
>      // allocates value
>      this(...) { ... }
>
>      @property void* value() { ... }
>
>      // destroys value
>      ~this() { ... }
> }
>
> void f(void* ptr) { ... }
>
> void main()
> {
>      f(ScopeTemp(...).value);
> }
> ---
> According to http://dlang.org/struct.html#StructDestructor
> "Destructors are called when an object goes out of scope."
> So I understand it as "it will not be destroyed before scope exit even
> if not used". Is it correct?
>
> So the question is if `ScopeTemp`'s scope is `main` scope, not some
> possibly generated "temp scope" (don't now what documentation statements
> prohibit compiler from doing so).
>
>
> Working code:
> ---
> import std.exception;
> import std.c.stdlib;
>
> struct ScopeTemp
> {
>      private int* p;
>      // allocates value
>      this(int i)
>      in { assert(i); }
>      body { *(p = cast(int*) enforce(malloc(int.sizeof))) = i; }
>
>      @disable this(this);
>
>      @property int* value() { return p; }
>
>      // destroys value
>      ~this() { *p = 0; p = null; /*free(p);*/ }
> }
>
> void f(int* ptr)
> {
>      assert(*ptr == 1);
> }
>
> void main()
> {
>      f(ScopeTemp(1).value);
> }
> ---

Wow, this `main` works fine too:
---
// same ScopeTemp definition as above

int* gptr;

void f(int* ptr)
{
    assert(*ptr == 1);
    gptr = ptr;
}

void g()
{
    assert(*gptr == 0);
}

void main()
{
    f(ScopeTemp(1).value);
    // Here `ScopeTemp` value is already destroyed
    g();
}
---

So `ScopeTemp`'s scope definitely isn't a `main` scope. So the question: 
what do we know about this "temp scope"?

-- 
Денис В. Шеломовский
Denis V. Shelomovskij
September 20, 2012
Re: Stupid scope destruction question
On Thursday, 20 September 2012 at 09:31:45 UTC, Denis 
Shelomovskij wrote:
> 20.09.2012 13:27, Denis Shelomovskij пишет:
>> Is there any guaranties that `ScopeTemp` will not be destroyed 
>> before
>> `f` call because it isn't used?
>> ---
>> struct ScopeTemp
>> {
>>     ...
>>     // allocates value
>>     this(...) { ... }
>>
>>     @property void* value() { ... }
>>
>>     // destroys value
>>     ~this() { ... }
>> }
>>
>> void f(void* ptr) { ... }
>>
>> void main()
>> {
>>     f(ScopeTemp(...).value);
>> }
>> ---
>> According to http://dlang.org/struct.html#StructDestructor
>> "Destructors are called when an object goes out of scope."
>> So I understand it as "it will not be destroyed before scope 
>> exit even
>> if not used". Is it correct?
>>
>> So the question is if `ScopeTemp`'s scope is `main` scope, not 
>> some
>> possibly generated "temp scope" (don't now what documentation 
>> statements
>> prohibit compiler from doing so).
>>
>>
>> Working code:
>> ---
>> import std.exception;
>> import std.c.stdlib;
>>
>> struct ScopeTemp
>> {
>>     private int* p;
>>     // allocates value
>>     this(int i)
>>     in { assert(i); }
>>     body { *(p = cast(int*) enforce(malloc(int.sizeof))) = i; }
>>
>>     @disable this(this);
>>
>>     @property int* value() { return p; }
>>
>>     // destroys value
>>     ~this() { *p = 0; p = null; /*free(p);*/ }
>> }
>>
>> void f(int* ptr)
>> {
>>     assert(*ptr == 1);
>> }
>>
>> void main()
>> {
>>     f(ScopeTemp(1).value);
>> }
>> ---
>
> Wow, this `main` works fine too:
> ---
> // same ScopeTemp definition as above
>
> int* gptr;
>
> void f(int* ptr)
> {
>     assert(*ptr == 1);
>     gptr = ptr;
> }
>
> void g()
> {
>     assert(*gptr == 0);
> }
>
> void main()
> {
>     f(ScopeTemp(1).value);
>     // Here `ScopeTemp` value is already destroyed
>     g();
> }
> ---
>
> So `ScopeTemp`'s scope definitely isn't a `main` scope. So the 
> question: what do we know about this "temp scope"?

AFAIK, if the rules are the same in C++ (which they probably 
are), then:
"Any object constructed during argument passing will remain valid 
for the duration of the call. It will go out of scope once the 
function has finished returning, and after the return value has 
itself gone out of scope and been destroyed."

The two catches are:
*"The implementation is allowed to elide the copy for pass by 
value, even if it has visible side effects, and construct 
directly into the function's stack."
*"If the function is able to use "(Named) Return Value 
Optimization" "(N)RVA" (invented by Walter himself), the returned 
value's destruction will not be observable (as it won't happen)."

Try running this:

C++
----
#include <iostream>

struct S
{
  int i;
  S(int i) : i(i) {::std::cout << "C:" << i << ::std::endl;}
  S(const S&){::std::cout << "CC:" << i << ::std::endl;}
  ~S(){::std::cout << "D~:" << i << ::std::endl;}
};

S foo(S)
{
  ::std::cout << "foo(S)" << ::std::endl;
  return S(2);
}
S foo(int)
{
  ::std::cout << "foo(int)" << ::std::endl;
  return S(6);
}

int main()
{
  foo(S(1));
  foo(S(5).i);
}
----
C:1
foo(S)
C:2
D~:2
D~:1
C:5
foo(int)
C:6
D~:6
D~:5
----

D:
----
import std.stdio;

struct S
{
  int i;
  this(int j){i = j;writeln("C",i);}
  this(this){writeln("PB",i);}
  ~this(){writeln("D~",i);}
};

S foo(S)
{
  writeln("foo(S)");
  return S(2);
}
S foo(int)
{
  writeln("foo(int)");
  return S(6);
}

void main()
{
  S a = foo(S(1));
  S a = foo(S(5).i);
}
----
C1
foo(S)
C2
D~1
D~2
C5
foo(int)
C6
D~6
D~5
----

I haven't seen any difference between C++ and D, including the 
argument pass by value elision, as well as (N)RVA
September 24, 2012
Re: Stupid scope destruction question
20.09.2012 15:35, monarch_dodra пишет:
> On Thursday, 20 September 2012 at 09:31:45 UTC, Denis Shelomovskij wrote:
>> 20.09.2012 13:27, Denis Shelomovskij пишет:
>>> Is there any guaranties that `ScopeTemp` will not be destroyed before
>>> `f` call because it isn't used?
>>> ---
>>> ...
>>>     f(ScopeTemp(...).value);
>>> }
>>> ---
>>> According to http://dlang.org/struct.html#StructDestructor
>>> "Destructors are called when an object goes out of scope."
>>> So I understand it as "it will not be destroyed before scope exit even
>>> if not used". Is it correct?
>>>
>>> So the question is if `ScopeTemp`'s scope is `main` scope, not some
>>> possibly generated "temp scope" (don't now what documentation statements
>>> prohibit compiler from doing so).
>>>
>>>
>
> AFAIK, if the rules are the same in C++ (which they probably are), then:
> "Any object constructed during argument passing will remain valid for
> the duration of the call. It will go out of scope once the function has
> finished returning, and after the return value has itself gone out of
> scope and been destroyed."
>

Thanks, looks like D does have C++ behaviour here. But your last 
statement about return value is incorrect. More than that function call 
doesn't change anything.

Correct answers are here:
* `12.2 Temporary objects [class.temporary]` section of C++ standard
* 
http://stackoverflow.com/questions/2506793/c-life-span-of-temporary-arguments
* 
http://stackoverflow.com/questions/5459759/full-expression-boundaries-and-lifetime-of-temporaries

-- 
Денис В. Шеломовский
Denis V. Shelomovskij
September 24, 2012
Re: Stupid scope destruction question
On Monday, 24 September 2012 at 07:25:28 UTC, Denis Shelomovskij 
wrote:
> 20.09.2012 15:35, monarch_dodra пишет:
>>
>> AFAIK, if the rules are the same in C++ (which they probably 
>> are), then:
>> "Any object constructed during argument passing will remain 
>> valid for
>> the duration of the call. It will go out of scope once the 
>> function has
>> finished returning, and after the return value has itself gone 
>> out of
>> scope and been destroyed."
>>
>
> Thanks, looks like D does have C++ behaviour here. But your 
> last statement about return value is incorrect. More than that 
> function call doesn't change anything.
>
> Correct answers are here:
> * `12.2 Temporary objects [class.temporary]` section of C++ 
> standard
> * 
> http://stackoverflow.com/questions/2506793/c-life-span-of-temporary-arguments
> * 
> http://stackoverflow.com/questions/5459759/full-expression-boundaries-and-lifetime-of-temporaries

How is my statement incorrect? The "function call" itself doesn't 
change anything sure, since it is more generally a "full 
expression": The return value itself is created *during* that 
full expression, but after the creation of the arguments. Last 
in, first out, it is destroyed before the passed in arguments.

Unless you were playing on the words "gone out of scope" (which 
is indeed not the 100% correct), I don't see how my statement is 
incorrect.
September 24, 2012
Re: Stupid scope destruction question
On 09/24/2012 12:49 AM, monarch_dodra wrote:
> On Monday, 24 September 2012 at 07:25:28 UTC, Denis Shelomovskij wrote:
>> 20.09.2012 15:35, monarch_dodra пишет:
>>>
>>> AFAIK, if the rules are the same in C++ (which they probably are), 
then:
>>> "Any object constructed during argument passing will remain valid for
>>> the duration of the call. It will go out of scope once the function has
>>> finished returning, and after the return value has itself gone out of
>>> scope and been destroyed."

That sounds like you are saying that the temporary S() is destroyed 
after 'r' below:

    // C++ code
    R r = foo(S());

> How is my statement incorrect? The "function call" itself doesn't change
> anything sure, since it is more generally a "full expression": The
> return value itself is created *during* that full expression, but after
> the creation of the arguments. Last in, first out, it is destroyed
> before the passed in arguments.

That is not correct. The temporary S() is destroyed at the end of the 
assignment expression (at the semicolon), while 'r' will live until the 
end of the current scope.

Ali
September 24, 2012
Re: Stupid scope destruction question
On Monday, 24 September 2012 at 17:49:22 UTC, Ali Çehreli wrote:
> On 09/24/2012 12:49 AM, monarch_dodra wrote:
> > On Monday, 24 September 2012 at 07:25:28 UTC, Denis
> Shelomovskij wrote:
> >> 20.09.2012 15:35, monarch_dodra пишет:
> >>>
> >>> AFAIK, if the rules are the same in C++ (which they
> probably are), then:
> >>> "Any object constructed during argument passing will remain
> valid for
> >>> the duration of the call. It will go out of scope once the
> function has
> >>> finished returning, and after the return value has itself
> gone out of
> >>> scope and been destroyed."
>
> That sounds like you are saying that the temporary S() is 
> destroyed after 'r' below:
>
>     // C++ code
>     R r = foo(S());
>
> > How is my statement incorrect? The "function call" itself
> doesn't change
> > anything sure, since it is more generally a "full
> expression": The
> > return value itself is created *during* that full expression,
> but after
> > the creation of the arguments. Last in, first out, it is
> destroyed
> > before the passed in arguments.
>
> That is not correct. The temporary S() is destroyed at the end 
> of the assignment expression (at the semicolon), while 'r' will 
> live until the end of the current scope.
>
> Ali

Ah... but see, "s" is *not* the temporary returned value.

It is a normal stack variable to which the the return value is 
copied. Also, as I said, in this situation, the compiler will 
elide the return completely by constructing the returned value 
directly into s, so you won't see the destroyer.

Try this though:
--------
void main()
{
  S s;
  foo(S(1));
}
--------
D:        C++
----      ----
C0        C:0
C1        C:1
foo(S)    foo(S)
C2        C:2
D~1       D~:2
D~2       D~:1
D~0       D~:0
----      ----


As you can see, three destructors. It would appear that D, 
contrary to C++, actually destroys the return value before the 
passed in arguments. Interesting. A valid behavior like any other 
I guess, but doesn't change much. I'll admit I was wrong 
regarding that (regarding D), but it stays right for C++ :p

Not your example you ask? HERE is the *money* example:
--------
void main()
{
  S s;
  s = foo(S(1));
}
--------
D:        C++
----      ----
C0        C:0
C1        C:1
foo(S)    foo(S)
C2        C:2
D~1       D~:2
D~0       D~:1
D~2       D~:2
----      ----

Again 3 destroyers. Again, C++, again works as I said.

Regarding D, WTF is D~0 ? That is D's "return value move" into 
action! First time I observe it myself! Exciting!

* First, the passed in argument is destroyed: D~1.
* Then it gets interesting:
* D first calls a destroyer on s (but doesn't actually remove it 
from the stack): D~0.
* Then D does a memcpy from the returned value, onto s (the move).
* Finally, the actual return value is then removed from the 
stack, but no destructor ever called on it.
* Finally, at the end of the program, s is destroyed (D~2).

Neat-o!
Top | Discussion index | About this forum | D home