Pitfalls of delegates inside ranges
Artur Skawina
art.08.09 at gmail.com
Mon Sep 2 06:39:39 PDT 2013
On 09/02/13 00:27, Joseph Rushton Wakeling wrote:
> Hello all,
>
> I came across an interesting little pitfall of delegates recently which I thought I'd share.
>
> TL;DR: having reference types as struct members can be dangerous. ;-)
>
> Suppose we have a fairly simple range which is supposed to count from 0 to some maximum. The trick is that the count is implemented by a function that's accessed by a delegate.
>
> struct Foo
> {
> private size_t _count = 0, _max;
> private void delegate() jump = null;
>
> @disable this();
>
> this(size_t max)
> {
> _max = max;
> jump = &jump10;
> }
>
> bool empty() @property @safe pure const nothrow
> {
> return (_count > _max);
> }
>
> size_t front()
> {
> return _count;
> }
>
> void popFront()
> {
> if (empty)
> {
> return;
> }
>
> writeln("At start of popFront(), count = ", _count);
> writeln("Jumping ...");
> jump();
> writeln("At end of popFront(), count = ", _count);
> }
>
> void jump10()
> {
> _count += 10;
> writeln("At end of jump, count = ", _count);
> }
> }
>
> Now let's put this struct to use:
>
> auto foo = Foo(50);
>
> foreach (f; foo)
> {
> writeln(f);
> }
>
> What we expect is that the program will print out 0, 10, 20, 30, 40, 50 (on new lines) and exit.
>
> In fact, the foreach () loop never exits and the logging writeln's we put in tell us a strange story:
> ... and so on. It seems like the value of _count is never being incremented. The logging function inside jump10 keeps telling us: "At end of jump, count = 70" (and 80, and 90, and ... 30470 ... and ...), but front() and popFront() keep telling us the count is zero.
>
> Of course, it's because of the delegate. The foreach () loop has taken a (value-type) copy of the Foo struct, but the delegate jump() is a reference -- which is pointing to the function jump10 in the _original_ Foo, not the copy that foreach () is operating on.
>
> Hence, it's the original's _count that's being incremented, and not that in foreach () loop's copy -- and so the foreach loop never exits.
this(this) { jump.ptr = &this; }
While this may not seem ideal, sometimes you do *not* want to do it
(when you need to keep the original context), so there are no obvious
alternatives that would handle all cases automagically.
artur
More information about the Digitalmars-d-learn
mailing list