Hi all.
I can't seem to get this to work properly. I'm trying to write a
copy-on-write proxy type. I'm using postblit and dtors along with the
usual ctors and opAssign to manage the refcount of the shared memory.
However, testing with dmd-2.026 (and a few previous versions a little
while ago), the attached program gives me the following output:
(NB: ">" means entering, "<" means leaving; these messages are printed
from in- and out-contracts. Printed values are the array being stored
and the ref count (or zero if the ref count pointer is null.))
> opAssign(T[]); COWArray(0x 0000[0..0]) @ 0
> ~this(); COWArray(0x 12FE88[0..4424652]) @ 1245120
< ~this(); COWArray(0x 12FE88[0..4424652]) @ 1245119
< opAssign(T[]); COWArray(0x AA2E40[0..4]) @ 1
> this(this); COWArray(0x AA2E40[0..4]) @ 1
< this(this); COWArray(0x AA2E40[0..4]) @ 2
a: [1,2,3,4]
> ~this(); COWArray(0x AA2E40[0..4]) @ 2
< ~this(); COWArray(0x AA2E40[0..4]) @ 1
For the following code:
void main()
{
COWArray!(int) a;
a = [1,2,3,4];
// This shouldn't affect ref counts
auto a_str = a.toString;
writefln("a: %s", a_str);
}
As you can see, it correctly enters opAssign and leaves with the correct
array reference and ref count. However, it also spuriously calls the
destructor with what appears to be a random pointer. It then calls the
postblit when it shouldn't be doing so, resulting in the refcount being
one too high.
I've attached the full source (compile with -debug); does anyone know
what I'm doing wrong, or whether this is a compiler bug?
-- Daniel
module cow_bug;
import std.string : format;
import std.stdio : writefln, writef;
struct COWArray(T)
{
private
{
T[] arr = null;
size_t* ctr = null;
}
/*
* Returns cowarray definition as a string for debugging
*/
debug private string stat()
{
return format("COWArray(0x%08x[0..%d]) @ %d",
arr.ptr, arr.length,
ctr ? *ctr : 0);
}
/*
* Post-blit function; increment reference count since we've just been
* copied.
*/
this(this)
in{debug writefln("> this(this); ",stat);}
out{debug writefln("< this(this); ",stat);}
body
{
++ *(this.ctr);
}
/*
* Dtor; decrement ref count, destroy memory if the counter is zero.
*/
~this()
in{debug writefln("> ~this(); ",stat);}
out{debug writefln("< ~this(); ",stat);}
body
{
// Accounts for empty cowarrays.
if( this.ctr )
{
-- *(this.ctr);
if( *(this.ctr) == 0 )
delete this.ctr;
}
}
const size_t length()
{
return arr.length;
}
COWArray opAssign(T[] arr)
in{debug writefln("> opAssign(T[]); ",stat);}
out{debug writefln("< opAssign(T[]); ",stat);}
body
{
// If we already have an array, dec ref, destroy if counter == 0.
if( this.ctr !is null )
{
-- *(this.ctr);
if( *(this.ctr) == 0 )
delete this.ctr;
}
// Attach to new array, assume no aliasing.
this.arr = arr;
this.ctr = new size_t;
*(this.ctr) = 1;
return this;
}
string toString()
{
return format("%s", this.arr);
}
}
void main()
{
// New array, no ctor, so it's not pointing at anything, and has zero
// length.
COWArray!(int) a;
// opAssign array; should have a single reference.
a = [1,2,3,4];
// This shouldn't affect ref counts
auto a_str = a.toString;
writefln("a: %s", a_str);
// Should have one reference, zero after the dtor, and the memory should
// be deleted.
}