Re: [boost] [smart ptr] Any interest in copy-on-write pointer for C++11?

hi ralph! a heavy argument for a 'COW' pattern is made here: http://cppnow.org/session/value-semantics-and-concepts-based-polymorphism ( 'copy_on_write' class template included). maybe it could be interesting to compare your 'cow_ptr' to this 'copy_on_write'. but no doubt: the direct support of 'COW' in boost would fill e need thank you for your efforts! best regards andreas pfaffenbichler

On 2013-02-09 18:47:59 +0000, Andreas Pfaffenbichler said:
a heavy argument for a 'COW' pattern is made here: http://cppnow.org/session/value-semantics-and-concepts-based-polymorphism ( 'copy_on_write' class template included).
I was going to reply about the same, but here's the whole class with documentation: http://stlab.adobe.com/classadobe_1_1version__1_1_1copy__on__write.html
maybe it could be interesting to compare your 'cow_ptr' to this 'copy_on_write'.
The biggest difference and, in my opinion, the most important advantage in favour of adobe::copy_on_write is that the copying is explicit; given a copy_on_write<T> x; you can only use x->const_member(); // no refcount overhead even if x is non-const to access const members of T. Whenever you need to modify the instance, you'll need to explicitly tell copy_on_write about it by calling write(): T & r = x.write(); // write() performs the copy if needed r.mutating_member(); or, matching the use case of cow::cow_ptr<T>::apply: f(x.write()); This interface maps very nicely to dealing with value types: as long as you're treating everything as a value (i.e. read-only), there aren't any hidden costs of making a copy either. And as soon as you need to make changes to the data, the overhead of the internal copy will be visible in the code responsible of the mutations as well. I guess that's enough to clear most concerns about the thread-safety issues too. -- Pyry Jahkola pyry.jahkola@iki.fi https://twitter.com/pyrtsa

On Sun, Feb 10, 2013 at 2:27 AM, Pyry Jahkola <pyry.jahkola@iki.fi> wrote: [...]
On 2013-02-09 18:47:59 +0000, Andreas Pfaffenbichler said:
a heavy argument for a 'COW' pattern is made here:
http://cppnow.org/session/**value-semantics-and-concepts-** based-polymorphism<http://cppnow.org/session/value-semantics-and-concepts-based-polymorphism> ( 'copy_on_write' class template included).
I was going to reply about the same, but here's the whole class with documentation:
http://stlab.adobe.com/**classadobe_1_1version__1_1_** 1copy__on__write.html<http://stlab.adobe.com/classadobe_1_1version__1_1_1copy__on__write.html>
maybe it could be interesting to compare your 'cow_ptr' to this
'copy_on_write'.
The biggest difference and, in my opinion, the most important advantage in favour of adobe::copy_on_write is that the copying is explicit; given a
copy_on_write<T> x;
you can only use
x->const_member(); // no refcount overhead even if x is non-const
to access const members of T. Whenever you need to modify the instance, you'll need to explicitly tell copy_on_write about it by calling write():
T & r = x.write(); // write() performs the copy if needed r.mutating_member();
or, matching the use case of cow::cow_ptr<T>::apply:
f(x.write());
This interface maps very nicely to dealing with value types: as long as you're treating everything as a value (i.e. read-only), there aren't any hidden costs of making a copy either. And as soon as you need to make changes to the data, the overhead of the internal copy will be visible in the code responsible of the mutations as well. I guess that's enough to clear most concerns about the thread-safety issues too.
+1 for explicit copy-on-write. At that point, you're basically just dealing with something close to an augmented shared_ptr< T const >. - Jeff

Jeffrey Lee Hellrung, Jr. wrote:
+1 for explicit copy-on-write. At that point, you're basically just dealing with something close to an augmented shared_ptr< T const >.
With explicit write(), one could envisage a family of two pointers, one the equivalent of shared_ptr<T const>, the other, returned from write, an equivalent of unique_ptr<T> to the only mutable copy so far. Once you're done writing, you would move it into a read_ptr again. That interface might not prove very convenient in practice though. Although you could, I think, build apply/APPLY upon it. read_ptr<T> pr; if( write_ptr<T> pw = pr.write() ) { pw->mutate(); pr = std::move( pw ); } I don't, however, particularly like the semantics of the case in which pr is already unique. In principle it shouldn't matter because we're writing pr from this thread, so nobody ought to be reading it, but still, it doesn't feel right.

That interface might not prove very convenient in practice though. Although you could, I think, build apply/APPLY upon it.
read_ptr<T> pr;
if( write_ptr<T> pw = pr.write() ) { pw->mutate(); pr = std::move( pw ); }
I don't, however, particularly like the semantics of the case in which pr is already unique. In principle it shouldn't matter because we're writing pr from this thread, so nobody ought to be reading it, but still, it doesn't feel right.
Nice idea though. write_ptr<T> would behave like value_ptr<T> and read_ptr<T> would behave like shared_ptr<T const> with the additional ability to return write_ptrs and being constructible from an rref to write_ptr. The function apply can be implemented as a non-member function: template <typename F> void apply( read_ptr<T> & pr, F f ) { write_ptr<T> pw = pr.write(); assert( pw ); f( pw.get() ); pr = std::move( pw ); } The macro APPLY could be implemented similar to the way I implemented it for cow_ptr. Once you have apply() and/or APPLY the interface isn't so inconvenient anymore. write_ptr might as well just be a value_ptr. Possibly value_ptr<T> and read_ptr<T> need to be friends of each other. The question for this design is: Is it worth it? I guess I need to think about that a little more in order to come to a conclusion. Thanks for the idea. Ralph

On Mon, Feb 11, 2013 at 12:01 PM, Ralph Tandetzky < ralph.tandetzky@googlemail.com> wrote:
That interface might not prove very convenient in practice though.
Although you could, I think, build apply/APPLY upon it.
read_ptr<T> pr;
if( write_ptr<T> pw = pr.write() ) { pw->mutate(); pr = std::move( pw ); }
I don't, however, particularly like the semantics of the case in which pr is already unique. In principle it shouldn't matter because we're writing pr from this thread, so nobody ought to be reading it, but still, it doesn't feel right.
Nice idea though. write_ptr<T> would behave like value_ptr<T>
Not as I understand it; write_ptr would behave similar to unique_ptr, i.e., it would be move-only. and read_ptr<T> would behave like shared_ptr<T const> with the additional
ability to return write_ptrs and being constructible from an rref to write_ptr.
Right. The function apply can be implemented as a non-member function:
template <typename F> void apply( read_ptr<T> & pr, F f ) { write_ptr<T> pw = pr.write(); assert( pw ); f( pw.get() ); pr = std::move( pw ); } The macro APPLY could be implemented similar to the way I implemented it for cow_ptr. Once you have apply() and/or APPLY the interface isn't so inconvenient anymore. write_ptr might as well just be a value_ptr. Possibly value_ptr<T> and read_ptr<T> need to be friends of each other.
The question for this design is: Is it worth it? I guess I need to think about that a little more in order to come to a conclusion.
I think this is better (safer, clearer, more flexible) than packaging roughly equivalent behavior within a single cow_ptr class. IMHO of course. - Jeff

Jeffrey Lee Hellrung, Jr. wrote: On Mon, Feb 11, 2013 at 12:01 PM, Ralph Tandetzky < ralph.tandetzky@googlemail.com> wrote:
Nice idea though. write_ptr<T> would behave like value_ptr<T>
Not as I understand it; write_ptr would behave similar to unique_ptr, i.e., it would be move-only.
You could, in principle, make it do a deep copy, but the point of the exercise is to avoid inadvertent expensive copies, so a move-only pointer makes a bit more sense, I think. An explicit copy() could perform the deep copy, if needed. On the other hand, making it do a deep copy is slightly more flexible because you could then use it as a value_ptr if you don't care for the whole CoW business. I could go either way on that.

On Mon, Feb 11, 2013 at 1:52 PM, Peter Dimov <lists@pdimov.com> wrote:
Jeffrey Lee Hellrung, Jr. wrote: On Mon, Feb 11, 2013 at 12:01 PM, Ralph Tandetzky < ralph.tandetzky@googlemail.com**> wrote:
Nice idea though. write_ptr<T> would behave like value_ptr<T>
Not as I understand it; write_ptr would behave similar to unique_ptr, i.e., it would be move-only.
You could, in principle, make it do a deep copy, but the point of the exercise is to avoid inadvertent expensive copies, so a move-only pointer makes a bit more sense, I think.
Agreed. An explicit copy() could perform the deep copy, if needed. On the other
hand, making it do a deep copy is slightly more flexible because you could then use it as a value_ptr if you don't care for the whole CoW business. I could go either way on that.
I'm not sure what you mean by "not caring for the whole CoW business". If you mean you want shared mutable objects and don't care whether the mutations are shared, then why wouldn't you just use std::shared_ptr? If you actually want a copy, just make a copy or clone or whatever, seems orthogonal to sharing and mutating to me. Or maybe I'm misunderstanding you. - Jeff

on Mon Feb 11 2013, "Peter Dimov" <lists-AT-pdimov.com> wrote:
Jeffrey Lee Hellrung, Jr. wrote: On Mon, Feb 11, 2013 at 12:01 PM, Ralph Tandetzky < ralph.tandetzky@googlemail.com> wrote:
Nice idea though. write_ptr<T> would behave like value_ptr<T>
Not as I understand it; write_ptr would behave similar to unique_ptr, i.e., it would be move-only.
You could, in principle, make it do a deep copy, but the point of the exercise is to avoid inadvertent expensive copies, so a move-only pointer makes a bit more sense, I think. An explicit copy() could perform the deep copy, if needed.
Nit: there is no such thing as "deep copy." :-)
On the other hand, making it do a deep copy is slightly more flexible because you could then use it as a value_ptr if you don't care for the whole CoW business. I could go either way on that.
Random thoughts: I don't know where this discussion stands on the issue, but it just occurred to me that I wouldn't want copying a ptr to automatically do anything to the ptee (pointee). I could imagine something like shared_ptr taking an optional cloner (as well as a deleter) and having shared_ptr<T const> x(new T); shared_ptr<T> y = x.clone_ptee(); I like the way const qualification integrates with that. I wonder if everyone who wants to implement CoW also wants the cost of shared_xxx, though. -- Dave Abrahams

<offtopic> On 13-02-11 03:21 PM, Dave Abrahams wrote:
Nit: there is no such thing as "deep copy." :-)
Sure there is: http://www.boost.org/doc/libs/1_53_0/doc/html/boost/proto/deep_copy_idp88811... ;-) </offtopic>

on Mon Feb 11 2013, Eric Niebler <eric-AT-boostpro.com> wrote:
<offtopic>
On 13-02-11 03:21 PM, Dave Abrahams wrote:
Nit: there is no such thing as "deep copy." :-)
Sure there is:
http://www.boost.org/doc/libs/1_53_0/doc/html/boost/proto/deep_copy_idp88811...
Interesting, but that's not what most people mean when they say "deep copy." -- Dave Abrahams

On 02/12/2013 12:21 AM, Dave Abrahams wrote:
Random thoughts:
I don't know where this discussion stands on the issue, but it just occurred to me that I wouldn't want copying a ptr to automatically do anything to the ptee (pointee).
I could imagine something like shared_ptr taking an optional cloner (as well as a deleter) and having
shared_ptr<T const> x(new T); shared_ptr<T> y = x.clone_ptee(); I like the idea of making cow_ptr<T const> somewhat special. At least it should be convertible to cow_ptr<T>. I like the way const qualification integrates with that. I wonder if everyone who wants to implement CoW also wants the cost of shared_xxx, though. What costs do you mean? CoW is especially useful (in my opinion) to
Well, that's exacly what value_ptr does: Whenever the pointer is copied, its pointee is copied or cloned. This enables proper value semantics on polymorphic types. Copying a cow_ptr would only copy pointers and increment a reference count. Later the copy can be made, if writing occurs. prevent expensive copies in terms of time and memory (and maybe even other stuff). It should not be overused. Probably the ranking of pointer type usage for me would be 1. raw pointers (as functions arguments, etc., no memory management involved) 2. unique_ptr (when it's needed only in one place) 3. shared_ptr and weak_ptr (when you want to extend the lifetime of a pointed to object, or if there are several owners) 4. cow_ptr (to implement copy-on-write when copying is expensive and/or to give polymorphic types value semantics) Thanks for the feedback, Ralph

On 02/11/2013 10:12 PM, Jeffrey Lee Hellrung, Jr. wrote:
On Mon, Feb 11, 2013 at 12:01 PM, Ralph Tandetzky < ralph.tandetzky@googlemail.com> wrote:
Nice idea though. write_ptr<T> would behave like value_ptr<T>
Not as I understand it; write_ptr would behave similar to unique_ptr, i.e., it would be move-only. Agreed. That makes more sense for client code.
and read_ptr<T> would behave like shared_ptr<T const> with the additional ability to return write_ptrs and being constructible from an rref to write_ptr. Right. Thanks, Ralph

read_ptr<T> pr;
if( write_ptr<T> pw = pr.write() ) { pw->mutate(); pr = std::move( pw ); }
I don't, however, particularly like the semantics of the case in which pr is already unique.
In fact, now that I think of it, .write should be a move, too. In this case, it's wouldn't be surprising to sometimes find pr holding a NULL after the move. write_ptr<T> pw = std::move( pr ); pw->mutate(); pr - std::move( pw ); The advantage here is that (1) in read_ptr<T> get_value(); write_ptr<T> p = get_value(); you don't need a write() call, and (2) this avoids the mistake of doing pr.write(); without storing the return value and as a result, losing *pr when it's unique. Just doing std::move( pr ); would do nothing, so it's safer.

On Mon, Feb 11, 2013 at 1:47 PM, Peter Dimov <lists@pdimov.com> wrote:
read_ptr<T> pr;
if( write_ptr<T> pw = pr.write() ) { pw->mutate(); pr = std::move( pw ); }
I don't, however, particularly like the semantics of the case in which pr is already unique.
In fact, now that I think of it, .write should be a move, too.
In the case that pr has a use_count of 1, definitely. I thought that's what you had been proposing all along :) Deep copy the pointed-to object only if the use_count >= 2. Whatever you get back, do your mutations, then pass it back to the read_ptr. In this case, it's wouldn't be surprising to sometimes find pr holding a
NULL after the move.
write_ptr<T> pw = std::move( pr ); pw->mutate(); pr - std::move( pw );
The advantage here is that (1) in
read_ptr<T> get_value(); write_ptr<T> p = get_value();
you don't need a write() call,
I would expect most of the time you're not going to be immediately modifying a read_ptr rvalue like above. The typical use case I'd expect is you have existing read_ptr lvalue which points to an object you want to mutate. So whether you write .write() or move(pr) is not a huge syntactic difference. and (2) this avoids the mistake of doing
pr.write();
without storing the return value and as a result, losing *pr when it's unique. Just doing
std::move( pr );
would do nothing, so it's safer.
That's a fair point, but I think this is really an abuse of rvalue reference syntax :) I guess one concern I have is if you're moving a smart ptr, I'd expect it to be as fast as a couple pointer assignments, but here it could incur a deep copy, and that behavior is entirely runtime-dependent. - Jeff

Jeffrey Lee Hellrung, Jr. wrote:
On Mon, Feb 11, 2013 at 1:47 PM, Peter Dimov <lists@pdimov.com> wrote:
read_ptr<T> pr;
if( write_ptr<T> pw = pr.write() ) { pw->mutate(); pr = std::move( pw ); }
... In fact, now that I think of it, .write should be a move, too.
In the case that pr has a use_count of 1, definitely. I thought that's what you had been proposing all along :)
Yes. I meant syntactically, as well, to match the semantics. ...
The advantage here is that (1) in
read_ptr<T> get_value(); write_ptr<T> p = get_value();
you don't need a write() call,
I would expect most of the time you're not going to be immediately modifying a read_ptr rvalue like above.
Well, you have to return _something_ from get_value. It's going to be either read_ptr or write_ptr. It can't be both. You don't know what the caller needs to do. So if you choose read_ptr for some reason, and the caller needs a write_ptr, all is well (and vice versa - they both move into each other).

On Mon, Feb 11, 2013 at 1:47 PM, Peter Dimov <lists@pdimov.com> wrote:
read_ptr<T> pr; if( write_ptr<T> pw = pr.write() ) { pw->mutate(); pr = std::move( pw ); }
I don't, however, particularly like the semantics of the case in which pr is already unique. In fact, now that I think of it, .write should be a move, too.
In the case that pr has a use_count of 1, definitely. I thought that's what you had been proposing all along :) Deep copy the pointed-to object only if the use_count >= 2. Whatever you get back, do your mutations, then pass it back to the read_ptr. If you have two pointers, then it should be move semantics, I agree. However, the above code is not strongly exception-safe. You would have to write read_ptr<T> pr; if ( write_ptr<T> pw = pr.write() ) { try { pr->mutate(); // might throw } catch (...) { pr = std::move( pw ); // no-throw throw; } pr = std::move( pw ); // no-throw } In order to fix it. (Or some scope_guard stuff, if you like.) The other
On 02/11/2013 11:34 PM, Jeffrey Lee Hellrung, Jr. wrote: thing: It it not very clear at which point the cloning happens, if it is necessary. There are 2 possibilities: 1. pr.write() does it. Or, if you want make it std::move(pr). 2. pw.operator->() does it. In case 1 using std::move() would be bad, because moving should normally be cheap and preferably not throw. Therefore I would prefer to use pr.write(). In case 2 the pointer types probably have to be friends and they are more tightly coupled. That isn't necessarily bad, because they are shipped together. I would prefer the variant 2. The two-pointer-solution makes it harder to write exception-safe code. Therefore I would prefer a single pointer type solution.
In this case, it's wouldn't be surprising to sometimes find pr holding a NULL after the move.
write_ptr<T> pw = std::move( pr ); pw->mutate(); pr - std::move( pw );
The advantage here is that (1) in
read_ptr<T> get_value(); write_ptr<T> p = get_value();
you don't need a write() call, I would expect most of the time you're not going to be immediately modifying a read_ptr rvalue like above. The typical use case I'd expect is you have existing read_ptr lvalue which points to an object you want to mutate. So whether you write .write() or move(pr) is not a huge syntactic difference. and (2) this avoids the mistake of doing
pr.write();
without storing the return value and as a result, losing *pr when it's unique. Just doing
std::move( pr );
would do nothing, so it's safer.
That's a fair point, but I think this is really an abuse of rvalue reference syntax :) I guess one concern I have is if you're moving a smart ptr, I'd expect it to be as fast as a couple pointer assignments, but here it could incur a deep copy, and that behavior is entirely runtime-dependent.
- Jeff Not if you do the work in pw.operator->() as pointed out above.
Thank you for your ideas, Ralph

On Wed, Feb 13, 2013 at 4:10 AM, Ralph Tandetzky < ralph.tandetzky@googlemail.com> wrote:
On 02/11/2013 11:34 PM, Jeffrey Lee Hellrung, Jr. wrote:
On Mon, Feb 11, 2013 at 1:47 PM, Peter Dimov <lists@pdimov.com> wrote:
read_ptr<T> pr;
if( write_ptr<T> pw = pr.write() ) { pw->mutate(); pr = std::move( pw ); }
I don't, however, particularly like the semantics of the case in which pr is already unique.
In fact, now that I think of it, .write should be a move, too.
In the case that pr has a use_count of 1, definitely. I thought that's what you had been proposing all along :) Deep copy the pointed-to object only if the use_count >= 2. Whatever you get back, do your mutations, then pass it back to the read_ptr.
If you have two pointers, then it should be move semantics, I agree. However, the above code is not strongly exception-safe. You would have to write read_ptr<T> pr; if ( write_ptr<T> pw = pr.write() ) { try { pr->mutate(); // might throw } catch (...) { pr = std::move( pw ); // no-throw throw; } pr = std::move( pw ); // no-throw } In order to fix it. (Or some scope_guard stuff, if you like.)
Sure; probably sufficient to package this in a mutate(read_ptr<T>, F) free function if you want to reduce that boilerplate. Or, instead of using a write_ptr, use a very similar object, say, mutator, which, upon destruction, automatically moves itself back to the read_ptr: if (mutator<T> m = pr.write()) { m->mutate(); } // mutator<T>::~mutator() effects pr = std::move(m) or something similar The other thing: It it not very clear at which point the cloning happens,
if it is necessary.
I think calling it unclear is stretching things, but okay.
There are 2 possibilities: 1. pr.write() does it. Or, if you want make it std::move(pr).
I prefer .write() (or whatever you want to call it).
2. pw.operator->() does it.
Hmmm...I'm going to need more details on how this works and what the expected usage pattern is. It seems there are some non-trivial design considerations (more nontrivial than case 1) if you try to go this path. If you end up cloning on write_ptr::operator->(), does anything happen to the corresponding read_ptr? What happens if you get multiple write_ptr's from the same read_ptr? Are there any thread safety issues? In case 1 using std::move() would be bad, because moving should normally be
cheap and preferably not throw. Therefore I would prefer to use pr.write().
Sounds good.
In case 2 the pointer types probably have to be friends and they are more tightly coupled. That isn't necessarily bad, because they are shipped together. I would prefer the variant 2. The two-pointer-solution makes it harder to write exception-safe code. Therefore I would prefer a single pointer type solution.
Okay, (1) cloning on operator->() smells a lot like implicit copy-on-write, the avoidance of which was the entire reason for introducing a second smart pointer. And (2) I'm confused, because it looks like you want write_ptr::operator->() to do the cloning, but then seem to imply this is a "single pointer type solution"...? Does that mean there's no more read_ptr? I think explicit copy-on-write is better because read-only and write access to the target object are clearly syntactically distinguished. No need to keep checking the use_count within operator->() and make defensive copies. That may be what you're proposing but I'm not sure. - Jeff

On 02/11/2013 06:22 PM, Jeffrey Lee Hellrung, Jr. wrote:
On Sun, Feb 10, 2013 at 2:27 AM, Pyry Jahkola<pyry.jahkola@iki.fi> wrote: [...]
The biggest difference and, in my opinion, the most important advantage in favour of adobe::copy_on_write is that the copying is explicit; given a
copy_on_write<T> x;
you can only use
x->const_member(); // no refcount overhead even if x is non-const
to access const members of T. Whenever you need to modify the instance, you'll need to explicitly tell copy_on_write about it by calling write():
T & r = x.write(); // write() performs the copy if needed r.mutating_member();
or, matching the use case of cow::cow_ptr<T>::apply:
f(x.write());
This interface maps very nicely to dealing with value types: as long as you're treating everything as a value (i.e. read-only), there aren't any hidden costs of making a copy either. And as soon as you need to make changes to the data, the overhead of the internal copy will be visible in the code responsible of the mutations as well. I guess that's enough to clear most concerns about the thread-safety issues too.
It would probably make sense to rename apply and apply_const to modify and read. The same with the macro. The get() member function is fine in my opinion, but what about operator->()? You can accidently trigger a copy with the non-const version. Should I take the non-const version out?
+1 for explicit copy-on-write. At that point, you're basically just dealing with something close to an augmented shared_ptr< T const >.
- Jeff
Not quiet, since the guts of shared_ptr<T const> can be changed by someone else without you noticing it. That's the thing about shared_ptr. It's really "shared". And const doesn't really mean const, but read-only. I've notices that cow_ptr actually does three things: 1. It implements copy-on-write, similar to copy_on_write. 2. It adds value semantics to polymorphic copyable/clonable types like value_ptr. This way these polymorphic objects can be put into standard containers and get all these nice features value types have. 3. It enables one to add cloning non-intrusively to a class hierarchy that doesn't support cloning. 4. It can be used as a pimpl pointer for value classes. copy_on_write doesn't do 2, 3 and 4. value_ptr doesn't do 1. Hence cow_ptr combines the positive sides of both worlds in an efficient manner. You could combine the template classes copy_on_write<T> and value_ptr<T> to do the same thing (copy_on_write<value_ptr<T>>), but it would be more verbose and not as efficient as cow_ptr<T>. (More indirection, extra allocations, more verbose client code, etc.) It would provide a better separation of concerns though. Possibly a bool template argument could change the behaviour between the current implementation of cow_ptr and value_ptr. What do you think of that?

On Mon, Feb 11, 2013 at 11:00 AM, Ralph Tandetzky < ralph.tandetzky@googlemail.com> wrote:
On 02/11/2013 06:22 PM, Jeffrey Lee Hellrung, Jr. wrote:
On Sun, Feb 10, 2013 at 2:27 AM, Pyry Jahkola<pyry.jahkola@iki.fi> wrote: [...]
The biggest difference and, in my opinion, the most important advantage in favour of adobe::copy_on_write is that the copying is explicit; given a
copy_on_write<T> x;
you can only use
x->const_member(); // no refcount overhead even if x is non-const
to access const members of T. Whenever you need to modify the instance, you'll need to explicitly tell copy_on_write about it by calling write():
T & r = x.write(); // write() performs the copy if needed r.mutating_member();
or, matching the use case of cow::cow_ptr<T>::apply:
f(x.write());
This interface maps very nicely to dealing with value types: as long as you're treating everything as a value (i.e. read-only), there aren't any hidden costs of making a copy either. And as soon as you need to make changes to the data, the overhead of the internal copy will be visible in the code responsible of the mutations as well. I guess that's enough to clear most concerns about the thread-safety issues too.
It would probably make sense to rename apply and apply_const to modify and read. The same with the macro. The get() member function is fine in my opinion, but what about operator->()? You can accidently trigger a copy with the non-const version. Should I take the non-const version out?
+1 for explicit copy-on-write. At that point, you're basically just
dealing with something close to an augmented shared_ptr< T const >.
- Jeff
Not quiet, since the guts of shared_ptr<T const> can be changed by someone else without you noticing it. That's the thing about shared_ptr. It's really "shared". And const doesn't really mean const, but read-only.
I'm not sure what you mean, but I didn't literally mean a shared_ptr< T const >; just some smart pointer to shared objects where the only way to mutate said objects through the smart pointer is via explicit copy-on-write. I've notices that cow_ptr actually does three things:
1. It implements copy-on-write, similar to copy_on_write. 2. It adds value semantics to polymorphic copyable/clonable types like value_ptr. This way these polymorphic objects can be put into standard containers and get all these nice features value types have. 3. It enables one to add cloning non-intrusively to a class hierarchy that doesn't support cloning. 4. It can be used as a pimpl pointer for value classes.
copy_on_write doesn't do 2, 3 and 4. value_ptr doesn't do 1. Hence cow_ptr combines the positive sides of both worlds in an efficient manner. You could combine the template classes copy_on_write<T> and value_ptr<T> to do the same thing (copy_on_write<value_ptr<T>>), but it would be more verbose and not as efficient as cow_ptr<T>. (More indirection, extra allocations, more verbose client code, etc.) It would provide a better separation of concerns though.
Possibly a bool template argument could change the behaviour between the current implementation of cow_ptr and value_ptr. What do you think of that?
I haven't taken the time to look at value_ptr or copy_on_write, but I think the ideal situation is a pair of smart pointers, one like I just described above (smart pointer to shared object), and one like (or precisely) unique_ptr. This makes the deep copying and mutation explicit and safe. But I should note I haven't looked in detail at any of the smart pointers discussed in this thread yet :/ - Jeff

On 02/11/2013 11:45 PM, Jeffrey Lee Hellrung, Jr. wrote: > On Mon, Feb 11, 2013 at 11:00 AM, Ralph Tandetzky < > ralph.tandetzky@googlemail.com> wrote: > >> On 02/11/2013 06:22 PM, Jeffrey Lee Hellrung, Jr. wrote: >> >>> On Sun, Feb 10, 2013 at 2:27 AM, Pyry Jahkola<pyry.jahkola@iki.fi> >>> wrote: >>> [...] >>> >>> >>>> The biggest difference and, in my opinion, the most important advantage >>>> in >>>> favour of adobe::copy_on_write is that the copying is explicit; given a >>>> >>>> copy_on_write<T> x; >>>> >>>> you can only use >>>> >>>> x->const_member(); // no refcount overhead even if x is non-const >>>> >>>> to access const members of T. Whenever you need to modify the instance, >>>> you'll need to explicitly tell copy_on_write about it by calling write(): >>>> >>>> T & r = x.write(); // write() performs the copy if needed >>>> r.mutating_member(); >>>> >>>> or, matching the use case of cow::cow_ptr<T>::apply: >>>> >>>> f(x.write()); >>>> >>>> This interface maps very nicely to dealing with value types: as long as >>>> you're treating everything as a value (i.e. read-only), there aren't any >>>> hidden costs of making a copy either. And as soon as you need to make >>>> changes to the data, the overhead of the internal copy will be visible in >>>> the code responsible of the mutations as well. I guess that's enough to >>>> clear most concerns about the thread-safety issues too. >>>> >> It would probably make sense to rename apply and apply_const to modify and >> read. The same with the macro. The get() member function is fine in my >> opinion, but what about operator->()? You can accidently trigger a copy >> with the non-const version. Should I take the non-const version out? >> >> +1 for explicit copy-on-write. At that point, you're basically just >>> dealing >>> with something close to an augmented shared_ptr< T const >. >>> >>> - Jeff >> Not quiet, since the guts of shared_ptr<T const> can be changed by someone >> else without you noticing it. That's the thing about shared_ptr. It's >> really "shared". And const doesn't really mean const, but read-only. > I'm not sure what you mean, but I didn't literally mean a shared_ptr< T > const >; just some smart pointer to shared objects where the only way to > mutate said objects through the smart pointer is via explicit copy-on-write. Well, then we are on the same page. :) > I've notices that cow_ptr actually does three things: >> 1. It implements copy-on-write, similar to copy_on_write. >> 2. It adds value semantics to polymorphic copyable/clonable types like >> value_ptr. This way these polymorphic objects can be put into standard >> containers and get all these nice features value types have. >> 3. It enables one to add cloning non-intrusively to a class hierarchy that >> doesn't support cloning. >> 4. It can be used as a pimpl pointer for value classes. >> >> copy_on_write doesn't do 2, 3 and 4. value_ptr doesn't do 1. Hence cow_ptr >> combines the positive sides of both worlds in an efficient manner. You >> could combine the template classes copy_on_write<T> and value_ptr<T> to do >> the same thing (copy_on_write<value_ptr<T>>), but it would be more verbose >> and not as efficient as cow_ptr<T>. (More indirection, extra allocations, >> more verbose client code, etc.) It would provide a better separation of >> concerns though. >> >> Possibly a bool template argument could change the behaviour between the >> current implementation of cow_ptr and value_ptr. What do you think of that? > I haven't taken the time to look at value_ptr or copy_on_write, but I think > the ideal situation is a pair of smart pointers, one like I just described > above (smart pointer to shared object), and one like (or precisely) > unique_ptr. This makes the deep copying and mutation explicit and safe. But > I should note I haven't looked in detail at any of the smart pointers > discussed in this thread yet :/ unique_ptr wouldn't work, because it does not provide cloning and a dynamic deleter, which must be transported back to the read_ptr<T> at some point. But an appropriate non-copyable but movable pointer type makes sense. > - Jeff
participants (7)
-
Andreas Pfaffenbichler
-
Dave Abrahams
-
Eric Niebler
-
Jeffrey Lee Hellrung, Jr.
-
Peter Dimov
-
Pyry Jahkola
-
Ralph Tandetzky