Properties and Copy Constructors

Chad J chadjoan at __spam.is.bad__gmail.com
Thu Aug 6 15:08:00 PDT 2009


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?



More information about the Digitalmars-d mailing list