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. }