digitalmars.D - Properties and Copy Constructors
- Chad J (113/113) Aug 06 2009 So I dug up Andrei's thread that mentioned something about properties
- Sergey Gromov (4/71) Aug 06 2009 Wouldn't it be better if BigInt implemented share-on-copy, copy-on-write
- Chad J (4/8) Aug 07 2009 Sure.
- Steven Schveighoffer (8/17) Aug 10 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?
Aug 06 2009
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
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
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