www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Properties and Copy Constructors

reply Chad J <chadjoan __spam.is.bad__gmail.com> writes:
So I dug up Andrei's thread that mentioned something about properties
that hasn't been discussed as of recent: expensive copy semantics.

The thread is here:
http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=82621

The jist is that you have some value type like a Matrix or BigInt that
might actually have memory allocated to it.  It is a value type though,
so whenever it is copied, any memory allocated to it needs to also be
copied and new memory allocated for the copy.

Consider there are also algorithms that don't have to actually copy
these things, but do need to move them around.  Let's take the canonical
example: a sorting algorithm.  It compares and it swaps.  No need for
assignment and copying; not really.  It should be possible to conserve
memory usage for a sort over BigInts.

This interferes poorly with properties because properties have a getter
and a setter: any use of said property will always imply a copy one way
or another.  The copies can be bad not only due to efficiency, but
because a sorting algorithm that was previously nothrow now has to
possibly throw an OutOfMemory exception because one of the allocating
copies might do that.

Notably, sometimes the use of getters and setters is inevitable, such as
when using calculated values like the distances along lines.

Now for the recap:

Andrei suggested properties could have 3 functions instead of 2:

property foo
{
	T get() {...}
	acquire(ref val) {...}
	T release() {...}
}

(I'm being lazy about type signatures here because it doesn't matter.)

* get() is the typical getter.
* acquire(ref val) moves the contents of val into the property while
removing them from val.  This is a destructive operation on val.
* release() Frees any resources the property owns and returns them.

Then operations like set(prop) move(prop1,prop2) and swap(prop1,prop2)
can be optimized by writing them like so:

void move(ref prop1, ref prop2)
{
	prop1.release(); // May not be needed if acquire implies release
	prop1.acquire(prop2);
}

void swap(ref prop1, ref prop2)
{
	tmp = prop1.release();
	prop1.acquire(prop2.release());
	prop2.acquire(tmp);
}

property foo
{
	// ...
	void set(ref val)
	{
		release();
		tmp = val.get(); // Make a copy.
		acquire(tmp);
	}
}

Given that no one cares to write these odd property methods for the
99.9% of cases where they don't matter, this strategy gained little favor.

Towards the end of the discussion Steven Schveighoffer mentioned that
this need to acquire and release is dictated by the type being used in
the property, not by the property itself.  Thus it is the type's
responsibility to make decisions about how it should be
moved/swapped/acquired/released.

Alright, that's all I'm going to bother recapping.  Sorry if I
misrepresented anyone or messed up.

Onwards!:

I really like Steven's suggestion, though it seems difficult to actually
/do/.  Thus I feel this deserves to be broken down a bit more so that we
can have our cake and eat it too.

I see two ways out of this dilemma:
1.  The property proxies these memory-conserving transactions.
2.  The getter doesn't /necessarily/ return a copy.  The setter also
doesn't /necessarily/ create it's own copy.

Going down road (1) is tricky.  If we don't make this supremely easy to
automate, then this will become a tedious C++-ism where any property
ever that accesses a Matrix will have to implement the same biolerplate
acquire/release over and over and over.  Yuck.  Even if the common cases
are allowed to fall back to get/set, this is still yuck.  Still, there
may be some way to automate this.  I'm just not clear what it is.

Option (2) is what I'm currently biased towards.  It also comes with
caveats though:  if you choose to return a reference to the matrix, then
without any guarantee that your reference is singular you will smack
right into the aliasing problem.  Nonetheless, my cursory look at
ref-returns seems to suggest they have this property of being singular
in most cases.  Then we can have something like the following:

class Blah
{
	property foo
	{
		ref BigInt get() { ... }
		ref BigInt set(ref BigInt m) { ... }
	}

	// etc
}

struct BigInt
{
	void swap( ref BigInt other )
	{
		// BigInt defines it's own swap internally.
	}

	// etc
}

void main()
{
	auto b1 = new Blah();
	auto b2 = new Blah();
	// Do things with b1 and b2.

	// Now we want to swap big integers for whatever reason.
	b1.foo.swap(b2.foo);
}

Now I'm assuming the ref return and ref passing of these BigInts doesn't
invoke their copy-constructors.  Is there any reason this can't work?
Aug 06 2009
next sibling parent reply Sergey Gromov <snake.scaly gmail.com> writes:
Thu, 06 Aug 2009 18:08:00 -0400, Chad J wrote:

 [snip]
 
 The jist is that you have some value type like a Matrix or BigInt that
 might actually have memory allocated to it.  It is a value type though,
 so whenever it is copied, any memory allocated to it needs to also be
 copied and new memory allocated for the copy.
 
 [snip]
 
 Onwards!:
 
 I really like Steven's suggestion, though it seems difficult to actually
 /do/.  Thus I feel this deserves to be broken down a bit more so that we
 can have our cake and eat it too.
 
 I see two ways out of this dilemma:
 1.  The property proxies these memory-conserving transactions.
 2.  The getter doesn't /necessarily/ return a copy.  The setter also
 doesn't /necessarily/ create it's own copy.
 
 Going down road (1) is tricky.  If we don't make this supremely easy to
 automate, then this will become a tedious C++-ism where any property
 ever that accesses a Matrix will have to implement the same biolerplate
 acquire/release over and over and over.  Yuck.  Even if the common cases
 are allowed to fall back to get/set, this is still yuck.  Still, there
 may be some way to automate this.  I'm just not clear what it is.
 
 Option (2) is what I'm currently biased towards.  It also comes with
 caveats though:  if you choose to return a reference to the matrix, then
 without any guarantee that your reference is singular you will smack
 right into the aliasing problem.  Nonetheless, my cursory look at
 ref-returns seems to suggest they have this property of being singular
 in most cases.  Then we can have something like the following:
 
 class Blah
 {
 	property foo
 	{
 		ref BigInt get() { ... }
 		ref BigInt set(ref BigInt m) { ... }
 	}
 
 	// etc
 }
 
 struct BigInt
 {
 	void swap( ref BigInt other )
 	{
 		// BigInt defines it's own swap internally.
 	}
 
 	// etc
 }
 
 void main()
 {
 	auto b1 = new Blah();
 	auto b2 = new Blah();
 	// Do things with b1 and b2.
 
 	// Now we want to swap big integers for whatever reason.
 	b1.foo.swap(b2.foo);
 }
 
 Now I'm assuming the ref return and ref passing of these BigInts doesn't
 invoke their copy-constructors.  Is there any reason this can't work?

Wouldn't it be better if BigInt implemented share-on-copy, copy-on-write semantics? This really boils down to a single reference counter within BigInt's allocated data and working struct constructors/destructors.
Aug 06 2009
parent Chad J <chadjoan __spam.is.bad__gmail.com> writes:
Sergey Gromov wrote:
 
 Wouldn't it be better if BigInt implemented share-on-copy, copy-on-write
 semantics?  This really boils down to a single reference counter within
 BigInt's allocated data and working struct constructors/destructors.

Sure. (Given the lack of activity on this thread, I'm suspecting we just solved the problem and everything is OK.)
Aug 07 2009
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 06 Aug 2009 18:08:00 -0400, Chad J  
<chadjoan __spam.is.bad__gmail.com> wrote:

 So I dug up Andrei's thread that mentioned something about properties
 that hasn't been discussed as of recent: expensive copy semantics.

 Towards the end of the discussion Steven Schveighoffer mentioned that
 this need to acquire and release is dictated by the type being used in
 the property, not by the property itself.  Thus it is the type's
 responsibility to make decisions about how it should be
 moved/swapped/acquired/released.

 Now I'm assuming the ref return and ref passing of these BigInts doesn't
 invoke their copy-constructors.  Is there any reason this can't work?

Yeah, I think that's exactly what I was thinking. If a struct controls its copy semantics, you can avoid aliasing problems, and use reference properties for doing swap-like operations. -Steve
Aug 10 2009