Why is dtor called for lazy parameter?
Simen Kjærås
simen.kjaras at gmail.com
Fri Sep 18 13:28:39 UTC 2020
On Friday, 18 September 2020 at 12:32:49 UTC, Andrey Zherikov
wrote:
> ======
> The output is:
> ======
> -> void test.main()
> -> test.do_lazy(lazy S s)
> -> test.create()
> 1 S test.S.this(int n)
> <- test.create()
> 1 void test.S.~this()
> ===-1 (2)
> <- test.do_lazy(lazy S s)
> -> 1703096 test.do_something(S s)
> <- 1703096 test.do_something(S s)
> <- void test.main()
> ======
>
> As you can see, dtor is called before writeln on (1) and s1.i
> is -1 (2)
Indeed. As we can see from the output, first do_lazy() is called
from test.main, then create() is called (this happens inside
do_lazy, as s is lazy). When create() returns, the scoped!S you
created goes out of scope and is destroyed. scoped's destructor
overwrites the memory with S.init, which is why s.i is -1 at that
point. Then the memory is overwritten by subsequent function
calls, as that stack space is now considered vacant. That's why
the output changes from -1 to 1703096.
A bit interesting is the fact that <- test.create() is printed
before ~this(). I expected the order to be opposite, but there
may be sensible reasons why it's not.
scoped!S is only valid inside the scope its variable exists in,
and when that scope is exited, it refers to random stack data.
It's a lot like this code:
int* fun() {
int a;
int* p = &a;
return p;
}
Note that return &a; does not compile, with a warning about
escaping a reference to a local variable. That's exactly the same
thing that's happening with scoped!S, but since scoped is more
complex, the compiler has a hard time keeping track of things,
and code that in a perfect world would not compile, does. It may
be that D's scope tracking functionality has become good enough
to catch this error now, if the functions are properly marked.
Even if this is the case, then they are obviously not properly
marked. :p
Now, this begets the question: *when* should I use scoped!T?
Short answer: Basically never.
Longer answer:
1) When the lifetime of one object needs to be a strict subset of
another. That is, the class instance you created only exists as
long as the function create() is on the stack. When scoped!T is a
member of another class or struct, it continues to live as long
as that object exists. In most cases, you don't mind that it
stays around for longer, and can let the GC handle the cleanup.
If you really do care, you can use scoped!T, or explicitly
destroy the object when you're done with it.
2) scoped!T may be used as a performance hack, letting you avoid
the GC. If you have instrumented your code and found that this is
the culprit, scoped!T might help. Even if GC is the problem,
you'll still need #1 to be true.
There may be other cases, but I believe those are the main two
reasons to use scoped!T.
--
Simen
More information about the Digitalmars-d-learn
mailing list