www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Creating a reference counted type?

reply Gary Willoughby <dev nomad.so> writes:
I'm wondering if it's this easy to create a reference counted 
type:

struct Foo
{
	int _refCount = 1;

	this(...)
	{
		// allocate resources, etc.
	}

	this(this)
	{
		this._refCount++;
	}

	~this()
	{
		this._refCount--;

		if (this._refCount == 0)
		{
			// free resources.
		}
	}
}

Are there any other considerations I need to handle?

Another thing that is puzzling me is that when creating an 
instance of the above struct and passing as an argument to a 
function, the copy constructor is called and the reference count 
is incremented. This is expected. However, when the function 
returns, the destructor is called (on the copy) and the reference 
count lowered. How does the original instance know of the updated 
reference count from the copy?
Jun 12 2016
parent reply Gary Willoughby <dev nomad.so> writes:
On Sunday, 12 June 2016 at 14:29:19 UTC, Gary Willoughby wrote:
 Another thing that is puzzling me is that when creating an 
 instance of the above struct and passing as an argument to a 
 function, the copy constructor is called and the reference 
 count is incremented. This is expected. However, when the 
 function returns, the destructor is called (on the copy) and 
 the reference count lowered. How does the original instance 
 know of the updated reference count from the copy?
Actually, this doesn't puzzle me at all! I think I must be tired. Ignore this paragraph, it doesn't make sense. lol.
Jun 12 2016
parent reply ketmar <ketmar ketmar.no-ip.org> writes:
this won't work at all. let's insert `writeln("FREE!");` in dtor, 
and test it:

auto foo (Foo foo) {
   version(dump) writeln(foo._refCount);
   return foo;
}

void main () {
   auto f = foo(Foo());
   version(dump) writeln(f._refCount);
}

it prints "FREE" once, so it looks like the whole thing is 
working. but now let's try this with `-version=dump`:

1
FREE!
2

ahem... wut?! we have one copy of our struct freed half the way, 
and another copy has refcount of 2, so it won't be freed at all. 
it doesn't so innocent as it looks: we may try to use `f` in 
`main`... just to find out that resources was mysteriously freed, 
and refcount is total garbage.
Jun 12 2016
parent reply Gary Willoughby <dev nomad.so> writes:
On Sunday, 12 June 2016 at 14:45:12 UTC, ketmar wrote:
 ahem... wut?! we have one copy of our struct freed half the 
 way, and another copy has refcount of 2, so it won't be freed 
 at all. it doesn't so innocent as it looks: we may try to use 
 `f` in `main`... just to find out that resources was 
 mysteriously freed, and refcount is total garbage.
Hmmm. I thought it looked *too* simple. Have you any idea if there is a simple solution to this?
Jun 12 2016
next sibling parent reply ketmar <ketmar ketmar.no-ip.org> writes:
On Sunday, 12 June 2016 at 14:49:18 UTC, Gary Willoughby wrote:
 Hmmm. I thought it looked *too* simple. Have you any idea if 
 there is a simple solution to this?
yes. you have to turn your refcount to pointer. ;-) the cause of "misbehave" is the fact that there can exist several copies of the struct made in different program points, and they all have *independent* refcounter. but all those copies should have the *same* refcounter. the easiest way to solve this is to allocate refcounter separately, and only share *pointer* to it, like this: struct Foo { int* refcount; void allocResources () { import core.stdc.stdlib : malloc; assert(refcount is null); refcount = cast(int*)malloc(int.size); *refcount = 1; // init other resources } this (this) { if (refcount !is null) *refcount += 1; } // we need to do this in opAssign() and in dtor, hence the function private void decRef () { if (refcount !is null) { if ((*refcount -= 1) == 0) { import core.stdc.stdlib : free; free(refcount); // free resources } } } ~this (this) { decRef(); } // yes, we need this too void opAssign (Foo src) { *src.refcount += 1; // it is important to increase it first, in case `src` and `this` are sharing refcount decRef(); // release our resources *refcount = *src.refcount; // copy other handles and so on } } this is basically how refcounted structs are done. note that i just typed the code into reply box, so it may not compile or contain some small bugs, but i think you got the idea.
Jun 12 2016
parent Gary Willoughby <dev nomad.so> writes:
On Sunday, 12 June 2016 at 15:05:53 UTC, ketmar wrote:
 this is basically how refcounted structs are done. note that i 
 just typed the code into reply box, so it may not compile or 
 contain some small bugs, but i think you got the idea.
Thanks for the replies guys.
Jun 12 2016
prev sibling parent ZombineDev <petar.p.kirov gmail.com> writes:
On Sunday, 12 June 2016 at 14:49:18 UTC, Gary Willoughby wrote:
 On Sunday, 12 June 2016 at 14:45:12 UTC, ketmar wrote:
 ahem... wut?! we have one copy of our struct freed half the 
 way, and another copy has refcount of 2, so it won't be freed 
 at all. it doesn't so innocent as it looks: we may try to use 
 `f` in `main`... just to find out that resources was 
 mysteriously freed, and refcount is total garbage.
Hmmm. I thought it looked *too* simple. Have you any idea if there is a simple solution to this?
You need to allocate the ref count on the heap so it is actually shared between all copies of the struct. Also if you want to share the reference between multiple threads, you need to use core.atomic.atomicOp for incrementing and decrementing. See std.typecons.RefCounteed's implementation for reference https://github.com/dlang/phobos/blob/v2.071.0/std/typecons.d#L4712 You can use AffixAllocator http://dlang.org/phobos-prerelease/std_experimental_allocator_building_blocks_a fix_allocator.html, so that ref count is automatically placed before or after the actual objected that is being ref counted. Just note that AffixAllocator is safe to use only by a single thread.
Jun 12 2016