Boost logo

Boost :

Subject: Re: [boost] [smart ptr] Any interest in copy-on-write pointerforC++11?
From: Jeffrey Lee Hellrung, Jr. (jeffrey.hellrung_at_[hidden])
Date: 2013-02-14 13:27:20


On Wed, Feb 13, 2013 at 4:10 AM, Ralph Tandetzky <
ralph.tandetzky_at_[hidden]> 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_at_[hidden]> 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


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk