Topic: Proposal: Improving TR1 Smart Pointers


Author: "vladimir kliatchko" <vladimir@kliatchko.com>
Date: Thu, 18 Aug 2005 17:44:35 CST
Raw View
Peter Dimov wrote:
> vladimir kliatchko wrote:
> > Peter Dimov wrote:
>
> > > I'd drop the is_allocator trait and leave only
> > >
> > > shared_ptr( T * p, D d, A a );
> > >
> > > Since replacing the allocator at construction time should be a
> > > relatively rare occurence, it's not that much work to provide an
> > > explicit default deleter.
> > >
> > > is_allocator should be proposed independently (if at all); if accepted,
> > > the deleter constructor can then be respecified to do the right thing.
> >
> > We definitely have no objection to considering is_allocator
> > independently. But please note that the is_allocator trait is also
> > important for the implementation of the create_obj method. Without the
> > trait we would either always require an allocator to be passed to
> > create_obj or we would have to split create_obj into
> > create_obj_with_allocator and create_obj_without_allocator.
>
> Separating the two is a better design as it supports constructors that
> take an allocator as a first argument.
>
> > > I'd reverse the arguments of the aliasing constructor:
> > >
> > > shared_ptr( shared_ptr<Y> const & py, T * pt );
> > >
> > > to avoid unintended clashes with the deleter constructor.
> >
> > We actually had a vacillated between the signature of the aliasing
> > constructor above and the one in the proposal. On one hand we did want,
> > as you pointed out, to avoid confusion with the deleter. On the other
> > hand we did want to keep various constructors of various smart pointers
> > consistent in that the pointer is the first parameter.
>
> This is a false consistency since the constructors taking a pointer
> assume ownership of the pointer. Constructors that take a shared_ptr as
> a first argument create a copy.

We expect that the gain obtained by avoiding the second allocation will
remain important even when "good" lock-free implementations become
common. While a lock-free implementation improves scalability under
high contention significantly, the overhead of a memory allocation
operation will still be quite noticeable. This is confirmed by the
single threaded case in our benchmarks. Alternatively you can just take
a look at the complexity of Maged Michael's lock-free allocator
algorithm (the best available as far as we know).

>
> > The features that we proposed naturally create some new opportunities
> > for misuse but, in our opinion, a big reason for the success of C++ is
> > that it does not limit users in their ability to access advanced
> > features for the sake of protecting others from themselves. That does
> > not mean that we do not want to prevent accidental misuse by those who
> > do not intend to use these features, but the general usage of shared
> > pointer should not be negatively affected by the mere presence of these
> > features. On the other hand, not having these features, in our
> > experience, would often result in complicated and error-prone work
> > around code with inferior performance.
>
> True. The alternative is to provide safe methods that cover the common
> aliasing use cases - casts, pointer to member, pointer to container
> element. Not particularly appealing.
>
> > Thanks for your comments of the first two of the proposed features. We
> > are very much interested in your opinion of the other two though.
> > Please let us know what you think.
>
> It's too early for that. :-)
>
> create_obj is interesting, although I tend to prefer the free function
> formulation:
>
> template<class T, ...> shared_ptr<T> create_object( ... );
>
> Avoiding one of the malloc locks can be a big win; on the other hand,
> under a good lock-free malloc we won't gain much. It should also be
> noted that create_object will silently ignore a class-specific operator
> new.
>
> I assume that the deleter of such a shared_ptr instance invokes the
> destructor of T, but the storage is not deallocated until the last
> weak_ptr goes away, right?
>
> When an explicit allocator is specified, how do you obtain the storage?
> Rebind to pair< sp_control_block, aligned_storage_for<T> >? The array
> variant probably rebinds to char?
>
> ---
> [ comp.std.c++ is moderated.  To submit articles, try just posting with ]
> [ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
> [              --- Please see the FAQ before posting. ---               ]
> [ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]

I am going to update the proposal based on your comments. These are the
function names I plan to use, unless somebody can (please) suggest
better ones:

template<class T, ...>
shared_ptr<T> make_shared_object( ... );

template<class T, class A, ...>
shared_ptr<T> make_shared_object_with_alloc( ... );

template<class T>
shared_ptr<T> make_shared_array( ... );

template<class T, class A>
shared_ptr<T> make_shared_array_with_alloc( ... );

It also seems that I need to add conversions between manage_ptr and
weak_ptr.

Additionally, there are a couple of things in the proposed text that
were done consistently with the TR1 shared_ptr specification, but may
need to be amended:

1. shared_ptr's constructor takes managed_ptr by reference. This is
consistent with how shared_ptr's constructor takes auto_ptr. Shall we
change both constructors to take their respective parameters by value
(to allow rvalues)?

2. The behavior of managed_ptr when constructed with a 0 pointer value
is left unspecified. The same is true for the TR1 shared_ptr
specification. Do we want to define the behavior for both cases?

--vlad

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: jgottman@carolina.rr.com ("Joe Gottman")
Date: Fri, 19 Aug 2005 01:31:34 GMT
Raw View
"vladimir kliatchko" <vladimir@kliatchko.com> wrote in message
news:1124401334.940909.118900@o13g2000cwo.googlegroups.com...
>
> Additionally, there are a couple of things in the proposed text that
> were done consistently with the TR1 shared_ptr specification, but may
> need to be amended:
>
> 1. shared_ptr's constructor takes managed_ptr by reference. This is
> consistent with how shared_ptr's constructor takes auto_ptr. Shall we
> change both constructors to take their respective parameters by value
> (to allow rvalues)?
>

   If the rvalue reference proposal passes,  you should definitely use them.
This would allow binding to rvalues but not to lvalues, which is exactly
what you want.  If someone really wanted to pass an lvalue managed_ptr to
shared_ptr's constructor they could use move().

> 2. The behavior of managed_ptr when constructed with a 0 pointer value
> is left unspecified. The same is true for the TR1 shared_ptr
> specification. Do we want to define the behavior for both cases?

   I was under the impression that shared_ptr mandated that it create a
pointer that "owns" the 0 pointer if one was passed, but I definitely would
not mind if it were changed to create an empty shared_ptr instead.

Joe Gottman

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "vladimir kliatchko" <vladimir@kliatchko.com>
Date: 19 Aug 2005 05:00:25 GMT
Raw View
Howard Hinnant wrote:
> In article <1124055758.701281.62500@o13g2000cwo.googlegroups.com>,
>  "vladimir kliatchko" <vladimir@kliatchko.com> wrote:
>
> > > Another advantage is that the approach is extendable to any smart
> > > pointer that takes a custom deleter.  For example you could use this
> > > tool to make unique_ptr behave like the proposed managed_ptr in that it
> > > would share ownership with a shared_ptr:
> > >
> > > typedef unique_ptr<WorkItem, link_life_to> UPtr;
> > > processor.processWorkItem(Uptr(&*i, link_life_to(itemsPtr)));
> > >
> > > The resulting unique_ptr actually has a lower overhead compared to
> > > managed_ptr in my experiments (sizeof == 3 words instead of 4 words).
> >
> > The extra word used by managed_ptr buys us something that was a very
> > important design goal for us: the type of managed_ptr is not affected
> > by the type of the deleter. This makes managed_ptr an effective
> > interface type (see paper for more about this).
> >
> > Another minor point is that using the technique above with unique_ptr
> > seems to contradict the very name of the unique_ptr class (unless of
> > course I misunderstand what the name is supposed to imply).
>
> I think I was unclear with this part of my post.
>
> I assert that a zero-overhead unique ownership smart_ptr<T, D> is a
> valuable tool for building other types of smart pointers such as
> managed_ptr, especially if we get template aliases.
>
> For example:
>
> template <class T>
>   using managed_ptr = unique_ptr<T, link_life_to>;
>
> I.e. now managed_ptr<T> <i>is</i> your interface type.  Only you didn't
> have to reinvent all of the typical unique-ownership smart pointer
> drudgery.
>
> Here's a custom deleter (untested) which more closely mimics your
> current managed_ptr specification:
>
> template <class T> inline
> void reclaim_managed_object(T* t, std::default_delete<T>*)
>     {std::default_delete<T>()(t);}
>
> struct managed_ptr_deleter
> {
>     template <class T, class D>
>     static
>     void helper(void* t, void* d)
>     {
>         reclaim_managed_object((T*)t, (D*)d);
>     }
>
>     template <class Y>
>     managed_ptr_deleter(Y* p)
>         : orig_(p),
>           deleter_(0),
>           cleanup_(helper<Y, std::default_delete<Y> >)
>     {
>     }
>
>     template <class Y, class D>
>     managed_ptr_deleter(Y* p, D* d)
>         : orig_(p),
>           deleter_(d),
>           cleanup_(helper<Y, D>)
>     {
>     }
>
>     void operator()(void*) {cleanup_(orig_, deleter_);}
>
>     void* orig_;
>     void* deleter_;
>     void (*cleanup_)(void*, void*);
> };
>
> template <class T>
>   using managed_ptr = unique_ptr<T, managed_ptr_deleter>;
>
> --
>
> Although your use case doesn't call for it, I think a lot of use cases
> will call for vector<unique-ownership-smart-ptr<T> >.  And so I'm very
> motivated that this common use case be achievable with a zero-overhead
> smart pointer (sizeof == 1 word).
>
> In order to have custom deleter capabilities, and keep the overhead to
> zero, the deleter must be static (part of the type).  I.e. unique_ptr<T,
> D> (or by whatever name).  Given that, I'm now saying:  Let's see how
> much we can reuse this animal.  And it looks to me like quite a bit.
>
> -Howard
>
> ---
> [ comp.std.c++ is moderated.  To submit articles, try just posting with ]
> [ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
> [              --- Please see the FAQ before posting. ---               ]
> [ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]

Thanks for the clarification. It does look like unique_ptr could be a
very useful complement to the family of smart pointers. We could also
use unique_ptr to implement managed_ptr (when rvalue references become
available in the language), however, unique_ptr does not eliminate the
need for a separate managed_ptr class. The use cases for managed_ptr
are quite different from those for unique_ptr in that managed_ptr will
be more useful as an interface type while unique_ptr will more often
appear as only an "implementation detail".

--vlad

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hinnant@metrowerks.com (Howard Hinnant)
Date: Sat, 20 Aug 2005 05:21:24 GMT
Raw View
In article <xzaNe.431$es2.23372@twister.southeast.rr.com>,
 jgottman@carolina.rr.com ("Joe Gottman") wrote:

> > 2. The behavior of managed_ptr when constructed with a 0 pointer value
> > is left unspecified. The same is true for the TR1 shared_ptr
> > specification. Do we want to define the behavior for both cases?
>
>    I was under the impression that shared_ptr mandated that it create a
> pointer that "owns" the 0 pointer if one was passed, but I definitely would
> not mind if it were changed to create an empty shared_ptr instead.

What about this case?

shared_ptr<T> sp((T*)0, my_deleter(my_data));

An empty shared_ptr will throw away whatever state might be in
my_deleter.  That state might be important to program logic, even though
the associated pointer is null (e.g. ~my_deleter() may have an important
role).

-Howard

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Peter Dimov" <pdimov@gmail.com>
Date: Sat, 20 Aug 2005 00:20:52 CST
Raw View
Peter Dimov wrote:

> I'd reverse the arguments of the aliasing constructor:
>
> shared_ptr( shared_ptr<Y> const & py, T * pt );
>
> to avoid unintended clashes with the deleter constructor. The Effects
> clause should simply be:
>
> Constructs a shared_ptr object that shares ownership with py.
>
> The newly constructed shared_ptr doesn't own pt; it owns whatever py
> owns.
>
> The Postcondition should be get() == pt; the rest is redundant.

We also need to specify what happens when py is empty.

1. Undefined behavior (Requires: py is not empty)
2. Constructs an "ordinary" empty shared_ptr, get() == 0.
3. Constructs an empty shared_ptr, get() is unspecified.
4. Constructs an empty shared_ptr, get() == pt.

(2) seems best.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "vladimir kliatchko" <vladimir@kliatchko.com>
Date: Sat, 20 Aug 2005 09:20:33 CST
Raw View
Peter Dimov wrote:
> Peter Dimov wrote:
>
> > I'd reverse the arguments of the aliasing constructor:
> >
> > shared_ptr( shared_ptr<Y> const & py, T * pt );
> >
> > to avoid unintended clashes with the deleter constructor. The Effects
> > clause should simply be:
> >
> > Constructs a shared_ptr object that shares ownership with py.
> >
> > The newly constructed shared_ptr doesn't own pt; it owns whatever py
> > owns.
> >
> > The Postcondition should be get() == pt; the rest is redundant.
>
> We also need to specify what happens when py is empty.
>
> 1. Undefined behavior (Requires: py is not empty)
> 2. Constructs an "ordinary" empty shared_ptr, get() == 0.
> 3. Constructs an empty shared_ptr, get() is unspecified.
> 4. Constructs an empty shared_ptr, get() == pt.
>
> (2) seems best.
>

How about: "Constructs an empty shared_ptr if pt==0, and undefined
behavior otherwise"? It does not seem safe to discard pt pointer
quietly when pt is not 0. Leaving this case undefined will allow for an
assert.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: jgottman@carolina.rr.com ("Joe Gottman")
Date: Sat, 20 Aug 2005 16:24:32 GMT
Raw View
"Howard Hinnant" <hinnant@metrowerks.com> wrote in message
news:hinnant-554E30.12173219082005@syrcnyrdrs-02-ge0.nyroc.rr.com...
> In article <xzaNe.431$es2.23372@twister.southeast.rr.com>,
> jgottman@carolina.rr.com ("Joe Gottman") wrote:
>
>> > 2. The behavior of managed_ptr when constructed with a 0 pointer value
>> > is left unspecified. The same is true for the TR1 shared_ptr
>> > specification. Do we want to define the behavior for both cases?
>>
>>    I was under the impression that shared_ptr mandated that it create a
>> pointer that "owns" the 0 pointer if one was passed, but I definitely
>> would
>> not mind if it were changed to create an empty shared_ptr instead.
>
> What about this case?
>
> shared_ptr<T> sp((T*)0, my_deleter(my_data));
>
> An empty shared_ptr will throw away whatever state might be in
> my_deleter.  That state might be important to program logic, even though
> the associated pointer is null (e.g. ~my_deleter() may have an important
> role).
>

   I agree.  When I posted above I should have explicitly said I was talking
about the constructor that takes a single pointer (or auto_ptr) as a
parameter.

Joe Gottman

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "vladimir kliatchko" <vladimir@kliatchko.com>
Date: Sat, 20 Aug 2005 11:26:49 CST
Raw View
Howard Hinnant wrote:
> In article <xzaNe.431$es2.23372@twister.southeast.rr.com>,
>  jgottman@carolina.rr.com ("Joe Gottman") wrote:
>
> > > 2. The behavior of managed_ptr when constructed with a 0 pointer value
> > > is left unspecified. The same is true for the TR1 shared_ptr
> > > specification. Do we want to define the behavior for both cases?
> >
> >    I was under the impression that shared_ptr mandated that it create a
> > pointer that "owns" the 0 pointer if one was passed, but I definitely would
> > not mind if it were changed to create an empty shared_ptr instead.
>
> What about this case?
>
> shared_ptr<T> sp((T*)0, my_deleter(my_data));
>
> An empty shared_ptr will throw away whatever state might be in
> my_deleter.  That state might be important to program logic, even though
> the associated pointer is null (e.g. ~my_deleter() may have an important
> role).
>
> -Howard
>
> ---
> [ comp.std.c++ is moderated.  To submit articles, try just posting with ]
> [ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
> [              --- Please see the FAQ before posting. ---               ]
> [ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]

It seems that I should leave the proposed text for the case when 0
pointer is passed to the constructor as is. Although constructing
non-empty shared_ptr or managed_ptr with 0 pointer is a bit
counter-intuitive, the alternative (discarding the deleter) might be
more confusing yet.

Regarding passing auto_ptr and managed_ptr to shared_ptr's constructors
by value (vs reference):
Changing these constructors to take auto_ptr by value and, thus,
allowing both lvalue and rvalues to be passed in implicitly looks like
the best option. I'd rather not make shared_ptr and managed_ptr
specification dependent on any core languge changes (by using rvalue
reference). Also note that using rvalue reference would completely
reverse the TR1 specification. Since shared_ptr is specified to take
auto_ptr by reference it is only lvalues not rvalues that are allowed.
I wonder if this specification was intentional and why.

--vlad

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Peter Dimov" <pdimov@gmail.com>
Date: Sat, 20 Aug 2005 17:44:11 CST
Raw View
vladimir kliatchko wrote:

> Regarding passing auto_ptr and managed_ptr to shared_ptr's constructors
> by value (vs reference):
> Changing these constructors to take auto_ptr by value and, thus,
> allowing both lvalue and rvalues to be passed in implicitly looks like
> the best option. I'd rather not make shared_ptr and managed_ptr
> specification dependent on any core languge changes (by using rvalue
> reference). Also note that using rvalue reference would completely
> reverse the TR1 specification. Since shared_ptr is specified to take
> auto_ptr by reference it is only lvalues not rvalues that are allowed.
> I wonder if this specification was intentional and why.

See III.B.4 in

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1450.html

Pass by reference is used for the strong exception safety guarantee.
The intent is to switch to rvalue reference when/if there is one. In
the meantime, auto_ptr::release() can be used to construct a shared_ptr
from an auto_ptr rvalue.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "vladimir kliatchko" <vladimir@kliatchko.com>
Date: Thu, 25 Aug 2005 22:27:12 CST
Raw View
We would like to thank everyone for many insightful comments and to
announce that we have published a new version of the proposal, updated
based on your feedback:

http://home.earthlink.net/~phalpern/cpp/KliatchkoSharedPtr.pdf

Please take another look; any further comments are appreciated,
Sincerely,
Vladimir Kliatchko & Ilougino Rocha

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: nagle@animats.com (John Nagle)
Date: Thu, 11 Aug 2005 05:26:44 GMT
Raw View
vladimir kliatchko wrote:

> Abstract
>
> Over a period of two years we have accumulated significant experience
> in developing and then using a family of C++ components implementing
> smart pointers. While similar to the smart pointers described by TR1,
> our implementation has some important distinctions. Our experience
> shows that these distinctions often resulted in both improved usability
> and superior performance. In this paper we describe how our
> implementation differs from TR1 and propose a number of corresponding
> changes to the C++ standard.
>
> To see the entire proposal, follow this link:
> http://home.earthlink.net/~phalpern/cpp/KliatchkoSharedPtr.pdf

    It's hard to evaluate this.  There are no examples, no
discussion of safety, and no indications of programmer error
rates or MTBF.

    The notion of reference-counted pointers to portions of an
object is interesting, but seems overly complicated.  Scoped
pointers might be a more effective way of offering safe access to
objects internal to another object.

    John Nagle
    Animats

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: vladimir@kliatchko.com (vladimir kliatchko)
Date: Thu, 11 Aug 2005 16:00:11 GMT
Raw View
John Nagle wrote:
> vladimir kliatchko wrote:
>
>> Abstract
>>
>> Over a period of two years we have accumulated significant experience
>> in developing and then using a family of C++ components implementing
>> smart pointers. While similar to the smart pointers described by TR1,
>> our implementation has some important distinctions. Our experience
>> shows that these distinctions often resulted in both improved usability
>> and superior performance. In this paper we describe how our
>> implementation differs from TR1 and propose a number of corresponding
>> changes to the C++ standard.
>>
>> To see the entire proposal, follow this link:
>> http://home.earthlink.net/~phalpern/cpp/KliatchkoSharedPtr.pdf
>
>
>    It's hard to evaluate this.  There are no examples, no
> discussion of safety, and no indications of programmer error
> rates or MTBF.
>

Let me provide an example which combines all of the proposed new
features and, in our experience, is not unrealistic:

class LockFreePoolAllocator;
class WorkItem;

class AsynchProcessor {
     void processWorkItem(managed_ptr<WorkItem>& workItem);
   // note use of the managed_ptr in the interface:
   // AsynchProcessor requires ownership of the
                  // item but does not make any assumptions about
                         // sharing this ownership with the caller.
};

LockFreePoolAllocator pool;
AsynchProcessor       processor

// performance critical item processing loop
while (keepGoing)
{
     shared_ptr<std::vector<WorkItem> > itemsPtr;
     itemsPtr.create_obj(pool); // housing a vector in the shared_ptr
                                // while also using the pool to optimize
          // memory allocation performance
     workStream >> *itemsPtr;
     std::vector<WorkItem>::iterator i = itemsPtr->begin();
     while (i!=itemsPtr->end())
     {
 shared_ptr<WorkItem> item(&*i, itemsPtr); // aliasing
 processor.processWorkItem(item); // share to managed conversion
 ++i;
     }
}

// end-of-the-example

I hope this example helps with the evaluation.

As far as programmer errors are concerned:
Our experience using all of the proposed features (allocator support,
collation of shared objects with the shared pointers, aliasing,
managed_ptrs) seems to indicate that while compile-time detectable
errors  may be common for a programmer just introduced to these
features, harder to detect runtime errors are highly unlikely. Our
experience may not be very representative though.

>    The notion of reference-counted pointers to portions of an
> object is interesting, but seems overly complicated.  Scoped
> pointers might be a more effective way of offering safe access to
> objects internal to another object.

The implementation of aliasing is very straightforward, so is the use. I
guess the concept itself may be advanced; but, then, it has zero cost
when not used and very difficult to do without in cases illustrated by
the example above.

>
>                 John Nagle
>                 Animats
>
> ---
> [ comp.std.c++ is moderated.  To submit articles, try just posting with ]
> [ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
> [              --- Please see the FAQ before posting. ---               ]
> [ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]
>

--vlad

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Howard Hinnant <hinnant@metrowerks.com>
Date: Thu, 11 Aug 2005 13:49:58 CST
Raw View
In article <_3KKe.3550$Gx1.2720@fe11.lga>,
 vladimir@kliatchko.com (vladimir kliatchko) wrote:

> class LockFreePoolAllocator;
> class WorkItem;
>
> class AsynchProcessor {
>      void processWorkItem(managed_ptr<WorkItem>& workItem);
>    // note use of the managed_ptr in the interface:
>    // AsynchProcessor requires ownership of the
>                   // item but does not make any assumptions about
>                          // sharing this ownership with the caller.
> };
>
> LockFreePoolAllocator pool;
> AsynchProcessor       processor
>
> // performance critical item processing loop
> while (keepGoing)
> {
>      shared_ptr<std::vector<WorkItem> > itemsPtr;
>      itemsPtr.create_obj(pool); // housing a vector in the shared_ptr
>                                 // while also using the pool to optimize
>           // memory allocation performance
>      workStream >> *itemsPtr;
>      std::vector<WorkItem>::iterator i = itemsPtr->begin();
>      while (i!=itemsPtr->end())
>      {
>  shared_ptr<WorkItem> item(&*i, itemsPtr); // aliasing
>  processor.processWorkItem(item); // share to managed conversion
>  ++i;
>      }
> }

Thank you for the example use.  Could you compare/contrast it with the
following?

class LockFreePoolAllocator;
class WorkItem;

class AsynchProcessor {
     void processWorkItem(WorkItem& workItem);
};

LockFreePoolAllocator pool;
AsynchProcessor       processor

// performance critical item processing loop
while (keepGoing)
{
     std::vector<WorkItem, LockFreePoolAllocator> itemsPtr(pool);
     workStream >> itemsPtr;
     std::vector<WorkItem>::iterator i = itemsPtr.begin();
     while (i!=itemsPtr.end())
     {
        processor.processWorkItem(*i);
        ++i;
     }
}

Thanks,
Howard

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: vladimir@kliatchko.com (vladimir kliatchko)
Date: Thu, 11 Aug 2005 19:41:26 GMT
Raw View
Howard Hinnant wrote:
> In article <_3KKe.3550$Gx1.2720@fe11.lga>,
>  vladimir@kliatchko.com (vladimir kliatchko) wrote:
>
>
>>class LockFreePoolAllocator;
>>class WorkItem;
>>
>>class AsynchProcessor {
>>     void processWorkItem(managed_ptr<WorkItem>& workItem);
>>   // note use of the managed_ptr in the interface:
>>   // AsynchProcessor requires ownership of the
>>                  // item but does not make any assumptions about
>>                         // sharing this ownership with the caller.
>>};
>>
>>LockFreePoolAllocator pool;
>>AsynchProcessor       processor
>>
>>// performance critical item processing loop
>>while (keepGoing)
>>{
>>     shared_ptr<std::vector<WorkItem> > itemsPtr;
>>     itemsPtr.create_obj(pool); // housing a vector in the shared_ptr
>>                                // while also using the pool to optimize
>>          // memory allocation performance
>>     workStream >> *itemsPtr;
>>     std::vector<WorkItem>::iterator i = itemsPtr->begin();
>>     while (i!=itemsPtr->end())
>>     {
>> shared_ptr<WorkItem> item(&*i, itemsPtr); // aliasing
>> processor.processWorkItem(item); // share to managed conversion
>> ++i;
>>     }
>>}
>
>
> Thank you for the example use.  Could you compare/contrast it with the
> following?
>
> class LockFreePoolAllocator;
> class WorkItem;
>
> class AsynchProcessor {
>      void processWorkItem(WorkItem& workItem);
> };
>
> LockFreePoolAllocator pool;
> AsynchProcessor       processor
>
> // performance critical item processing loop
> while (keepGoing)
> {
>      std::vector<WorkItem, LockFreePoolAllocator> itemsPtr(pool);
>      workStream >> itemsPtr;
>      std::vector<WorkItem>::iterator i = itemsPtr.begin();
>      while (i!=itemsPtr.end())
>      {
>         processor.processWorkItem(*i);
>         ++i;
>      }
> }
>
> Thanks,
> Howard
>
> ---
> [ comp.std.c++ is moderated.  To submit articles, try just posting with ]
> [ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
> [              --- Please see the FAQ before posting. ---               ]
> [ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]
>

The modified version of the example above
AsynchProcessor::processWorkItem does not use any kind of smart pointer
and thus cannot acquire ownership of a work item. The original version
of the example used managed_ptr in the interface of AsyncProcessor to
demonstrate how ownership of an element of a vector can be passed
through an interface using aliasing and the conversion from shared_ptr
to managed_ptr. I used the name 'AsynchProcessor' to suggest
(unsuccessfully, sorry, I should have been more explicit) that the
processing of work items is done asynchronously and therefore may not
complete in time for the next iteration of the loop. The asynchronous
nature of AsynchProcessor::processWorkItem was supposed to make the
transfer of ownership essential.
I hope this explanation helps. Pls let me know if I am still not
illustrating the proposed features clearly.
--vlad

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hinnant@metrowerks.com (Howard Hinnant)
Date: Fri, 12 Aug 2005 01:18:21 GMT
Raw View
In article <HANKe.26697$sf6.247@fe08.lga>,
 vladimir@kliatchko.com (vladimir kliatchko) wrote:

> The modified version of the example above
> AsynchProcessor::processWorkItem does not use any kind of smart pointer
> and thus cannot acquire ownership of a work item. The original version
> of the example used managed_ptr in the interface of AsyncProcessor to
> demonstrate how ownership of an element of a vector can be passed
> through an interface using aliasing and the conversion from shared_ptr
> to managed_ptr. I used the name 'AsynchProcessor' to suggest
> (unsuccessfully, sorry, I should have been more explicit) that the
> processing of work items is done asynchronously and therefore may not
> complete in time for the next iteration of the loop. The asynchronous
> nature of AsynchProcessor::processWorkItem was supposed to make the
> transfer of ownership essential.
> I hope this explanation helps. Pls let me know if I am still not
> illustrating the proposed features clearly.

I did miss that point completely.  Thanks for the clarification.
Requoting your original code to continue disscussion:

class LockFreePoolAllocator;
class WorkItem;

class AsynchProcessor {
     void processWorkItem(managed_ptr<WorkItem>& workItem);
                        // note use of the managed_ptr in the interface:
                        // AsynchProcessor requires ownership of the
                         // item but does not make any assumptions about
                         // sharing this ownership with the caller.
};

LockFreePoolAllocator pool;
AsynchProcessor       processor

// performance critical item processing loop
while (keepGoing)
{
     shared_ptr<std::vector<WorkItem> > itemsPtr;
     itemsPtr.create_obj(pool); // housing a vector in the shared_ptr
                                // while also using the pool to optimize
                               // memory allocation performance
     workStream >> *itemsPtr;
     std::vector<WorkItem>::iterator i = itemsPtr->begin();
     while (i!=itemsPtr->end())
     {
        shared_ptr<WorkItem> item(&*i, itemsPtr); // aliasing
        processor.processWorkItem(item); // share to managed conversion
        ++i;
     }
}

Is it now true that LockFreePoolAllocator has to deal with simultaneous
deallocations from multiple threads?  That's not necessarily a bad
thing, just I didn't expect it because of the name.

Shouldn't processWorkItem take the managed_ptr by value?  Otherwise the
temp generated at the call site won't bind to the non-const ref.

What is the advantage of using managed_ptr over shared_ptr in the
processWorkItem interface?  In this context, IIUC, managed_ptr shares
ownership with the shared_ptr's in the keepGoing loop.  So it must take
into account that if it modifies *workItem, others may see the change.
Otoh, if processWorkItem is designed to uniquely own the workItem
(making changes to it without worrying what other's see), perhaps
transfer of ownership (instead of sharing ownership) to processWorkItem
might be preferable.

I've been working on something called unique_ptr that is like your
managed_ptr, except that it typically has the same overhead as auto_ptr
(for empty custom deleters).  And it won't share ownership with
shared_ptr (or anything else).  Its use in your example would transfer
instead of share ownership and might look something like:

class LockFreePoolAllocator;
class WorkItem;

typedef std::unique_ptr<WorkItem, LockFreePoolAllocator> Ptr;

class AsynchProcessor {
     void processWorkItem(Ptr workItem);
};

LockFreePoolAllocator pool;
AsynchProcessor       processor

// performance critical item processing loop
while (keepGoing)
{
     std::vector<Ptr, LockFreePoolAllocator> itemsPtr(pool);
     workStream >> itemsPtr;
     std::vector<WorkItem>::iterator i = itemsPtr.begin();
     while (i!=itemsPtr.end())
     {
        processor.processWorkItem(std::move(*i));
        ++i;
     }
}

Here the memory ownership is explicit transferred to processWorkItem
with the move() function.  Assuming the unique_ptr's are pointing into
pool, LockFreePoolAllocator still has to deal with simultaneous
deallocations though.  Or perhaps they could point into thread-specific
pools, and then truly get rid of the need for a thread-aware pool.

-Howard

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Howard Hinnant <hinnant@metrowerks.com>
Date: 12 Aug 2005 16:10:01 GMT
Raw View
In article <_3KKe.3550$Gx1.2720@fe11.lga>,
 vladimir@kliatchko.com (vladimir kliatchko) wrote:

>  shared_ptr<WorkItem> item(&*i, itemsPtr); // aliasing

I've been looking at other ways this type of aliasing can be achieved
with the existing tr1::shared_ptr spec.  Fwiw, here's one possibility:

class link_life_to
{
    typedef std::shared_ptr<void> D;
public:
    link_life_to(const D& d) : d_(d) {}

    template <class T> void operator()(T*) {}
private:
    D d_;
};

Now you can say:

shared_ptr<WorkItem> item(&*i, link_life_to(itemsPtr)); // aliasing

I believe this has the same effect as the proposed shared_ptr
constructor.  A disadvantage though is that it sets up a second
reference count instead of reusing the original reference count.  But
there are a couple of advantages.

One advantage is that it is easier to search for lifetime dependencies
between otherwise unrelated shared_ptr's (search for "link_life_to").

Another advantage is that the approach is extendable to any smart
pointer that takes a custom deleter.  For example you could use this
tool to make unique_ptr behave like the proposed managed_ptr in that it
would share ownership with a shared_ptr:

typedef unique_ptr<WorkItem, link_life_to> UPtr;
processor.processWorkItem(Uptr(&*i, link_life_to(itemsPtr)));

The resulting unique_ptr actually has a lower overhead compared to
managed_ptr in my experiments (sizeof == 3 words instead of 4 words).

If link_life_to is templated:

template <class D = std::shared_ptr<void> >
class link_life_to
{
public:
    link_life_to(const D& d) : d_(d) {}

    template <class T> void operator()(T*) {}
private:
    D d_;
};

then it can used to link the lifetimes of other types of smart pointers
(as long as they accept custom deleters).  In order to use a unique_ptr
as the lifetime to link with, link_life_to needs to be extended to
handle move-only types:

template <class D = std::shared_ptr<void> >
class link_life_to
{
public:
    link_life_to(const D& d) : d_(d) {}
    link_life_to(D&& d) : d_(std::move(d)) {}
    link_life_to(link_life_to&& x) : d_(std::move(x.d_)) {}
    link_life_to& operator=(link_life_to&& x)
        {d_ = std::move(x.d_); return *this;}

    template <class T> void operator()(T*) {}
private:
    D d_;
};

Unless of course move members are auto-generated as Mirek Fidler
proposes, in which case you don't need the explicit move constructor or
move assignment.

Or perhaps with N1717 (extended):

template <class D = std::shared_ptr<void> >
class link_life_to
{
public:
    link_life_to(const D& d) : d_(d) {}
    link_life_to(D&& d) : d_(std::move(d)) {}
    link_life_to(link_life_to&&)            {default}
    link_life_to& operator=(link_life_to&&) {default}

    template <class T> void operator()(T*) {}
private:
    D d_;
};

-Howard

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "vladimir kliatchko" <vladimir@kliatchko.com>
Date: Fri, 12 Aug 2005 11:40:37 CST
Raw View
Howard Hinnant wrote:
> In article <HANKe.26697$sf6.247@fe08.lga>,
>  vladimir@kliatchko.com (vladimir kliatchko) wrote:
>
> > The modified version of the example above
> > AsynchProcessor::processWorkItem does not use any kind of smart pointer
> > and thus cannot acquire ownership of a work item. The original version
> > of the example used managed_ptr in the interface of AsyncProcessor to
> > demonstrate how ownership of an element of a vector can be passed
> > through an interface using aliasing and the conversion from shared_ptr
> > to managed_ptr. I used the name 'AsynchProcessor' to suggest
> > (unsuccessfully, sorry, I should have been more explicit) that the
> > processing of work items is done asynchronously and therefore may not
> > complete in time for the next iteration of the loop. The asynchronous
> > nature of AsynchProcessor::processWorkItem was supposed to make the
> > transfer of ownership essential.
> > I hope this explanation helps. Pls let me know if I am still not
> > illustrating the proposed features clearly.
>
> I did miss that point completely.  Thanks for the clarification.
> Requoting your original code to continue disscussion:
>
> class LockFreePoolAllocator;
> class WorkItem;
>
> class AsynchProcessor {
>      void processWorkItem(managed_ptr<WorkItem>& workItem);
>                         // note use of the managed_ptr in the interface:
>                         // AsynchProcessor requires ownership of the
>                          // item but does not make any assumptions about
>                          // sharing this ownership with the caller.
> };
>
> LockFreePoolAllocator pool;
> AsynchProcessor       processor
>
> // performance critical item processing loop
> while (keepGoing)
> {
>      shared_ptr<std::vector<WorkItem> > itemsPtr;
>      itemsPtr.create_obj(pool); // housing a vector in the shared_ptr
>                                 // while also using the pool to optimize
>                                // memory allocation performance
>      workStream >> *itemsPtr;
>      std::vector<WorkItem>::iterator i = itemsPtr->begin();
>      while (i!=itemsPtr->end())
>      {
>         shared_ptr<WorkItem> item(&*i, itemsPtr); // aliasing
>         processor.processWorkItem(item); // share to managed conversion
>         ++i;
>      }
> }
>

> Is it now true that LockFreePoolAllocator has to deal with simultaneous
> deallocations from multiple threads?  That's not necessarily a bad
> thing, just I didn't expect it because of the name.

I used the name 'LockFreePoolAllocator' to suggest a high-performance
('lock-free' part was supposed to carry this suggestion) thread-safe
allocator to illustrate how we use allocators with smart pointer (see
the paper for more info).

>
> Shouldn't processWorkItem take the managed_ptr by value?  Otherwise the
> temp generated at the call site won't bind to the non-const ref.

Yes, it should. My bug.

>
> What is the advantage of using managed_ptr over shared_ptr in the
> processWorkItem interface?  In this context, IIUC, managed_ptr shares
> ownership with the shared_ptr's in the keepGoing loop.  So it must take
> into account that if it modifies *workItem, others may see the change.
> Otoh, if processWorkItem is designed to uniquely own the workItem
> (making changes to it without worrying what other's see), perhaps
> transfer of ownership (instead of sharing ownership) to processWorkItem
> might be preferable.
>

We generally prefer managed_ptr in interfaces like processWorkItem
because while such interfaces allow callers to pass in shared_ptrs, the
callers who do not need to retain ownership are allowed to pass
managed_ptrs directly. Passing managed_ptrs is significantly more
efficient then passing shared_ptrs.
Also, using managed_ptr in the interface makes a statement regarding
who controls the lifetime of the object but not regarding who is
allowed to modify it. This can be separately expressed by using (or not
using) the const qualifier (e.g. managed_ptr<const WorkItem>). Note
that using share_ptr in an interface conveys no more information on who
is allowed to modify the shared object. (For more on advantages of
using managed_ptr as an interface type see the paper)

> I've been working on something called unique_ptr that is like your
> managed_ptr, except that it typically has the same overhead as auto_ptr
> (for empty custom deleters).  And it won't share ownership with
> shared_ptr (or anything else).  Its use in your example would transfer
> instead of share ownership and might look something like:
>
> class LockFreePoolAllocator;
> class WorkItem;
>
> typedef std::unique_ptr<WorkItem, LockFreePoolAllocator> Ptr;
>
> class AsynchProcessor {
>      void processWorkItem(Ptr workItem);
> };
>
> LockFreePoolAllocator pool;
> AsynchProcessor       processor
>
> // performance critical item processing loop
> while (keepGoing)
> {
>      std::vector<Ptr, LockFreePoolAllocator> itemsPtr(pool);
>      workStream >> itemsPtr;
>      std::vector<WorkItem>::iterator i = itemsPtr.begin();
>      while (i!=itemsPtr.end())
>      {
>         processor.processWorkItem(std::move(*i));
>         ++i;
>      }
> }
>
> Here the memory ownership is explicit transferred to processWorkItem
> with the move() function.  Assuming the unique_ptr's are pointing into
> pool, LockFreePoolAllocator still has to deal with simultaneous
> deallocations though.  Or perhaps they could point into thread-specific
> pools, and then truly get rid of the need for a thread-aware pool.

Requirement to use std::vector<WorkItem> rather then a vector of smart
pointers was important in my example to illustrate the use of aliasing.
This requirement may be motivated by performance considirations, by the
need for compatibility with a particular wire format or a legacy
interface, or by that fact that std::vector<WorkItem> is eaier to
marshall accross process boundries.

It is also important for our implementation of the support for
allocators that neither managed_ptr class nor shared_ptr class are
templatized on the type of the allocator. It seems that the unique_ptr
is.

Also, I am not familiar with the move function. Where can I find some
info on it?

>
> -Howard
>
> ---
> [ comp.std.c++ is moderated.  To submit articles, try just posting with ]
> [ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
> [              --- Please see the FAQ before posting. ---               ]
> [ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]

Updated example:

class HighPerformanceThreadSafeAllocator;
class WorkItem;

class AsynchProcessor {
    void processWorkItem(managed_ptr<const WorkItem> workItem);
                         // Process work item in a separate thread.
                         // Note use of the managed_ptr in the
                         // interface: AsynchProcessor requires
                         // ownership of the item but does not make
                         // any assumptions about sharing this
                         // ownership with the caller.
};

HighPerformanceThreadSafeAllocator alloc;
AsynchProcessor                    processor

// performance critical item processing loop
while (keepGoing)
{
     shared_ptr<std::vector<WorkItem> > itemsPtr;
                                 // must be std::vector<WorkItem>
                                 // for performance and
                                 // "wire format compatibility"

     itemsPtr.create_obj(alloc); // housing a vector in the shared_ptr
                                 // while also using the pool to
optimize
                                 // memory allocation performance

     int numWorkItems;
     workStream >> numWorkItems;
     itemsPtr->reserve(numWorkItems);
     ReadWorkItems(workStream, *itemsPtr, numWorkItems);

     std::vector<WorkItem>::iterator i = itemsPtr->begin();
     while (i!=itemsPtr->end())
     {
        shared_ptr<WorkItem> item(&*i, itemsPtr); // aliasing
        processor.processWorkItem(item); // share to managed conversion
        ++i;
     }
}

// end-of-the-example

--vlad

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hinnant@metrowerks.com (Howard Hinnant)
Date: Fri, 12 Aug 2005 18:17:17 GMT
Raw View
In article <1123862967.412092.115540@g47g2000cwa.googlegroups.com>,
 "vladimir kliatchko" <vladimir@kliatchko.com> wrote:

Thanks for the answers and updated example.

> Also, I am not familiar with the move function. Where can I find some
> info on it?

Oh, sorry.  I've been working a proposal for the past 3-4 years and
sometimes I get so deep into it I start assuming knowledge of it in my
discussions. ;-)

In reverse chronological order:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1771.html
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1770.html
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1690.html
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1385.htm
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm

In a nutshell:  These papers introduce a new type of reference called an
rvalue-reference.  The rvalue-reference is very similar to our current
reference (now named lvalue-reference) except that it will bind to
rvalues even when not const-qualifed.  Overload resolution rules have
been carefully crafted between the lvalue and rvalue references so as to
enable both "move semantics" and "perfect forwarding".

The proposed std::move does nothing but cast an lvalue to an rvalue.
This triggers "move" over "copy" in classes which have a move
constructor and/or move assignment operator.  Think of "move" as
auto_ptr copy, or destructive copy.

The earlier papers talk about something called move_ptr.  This is the
same thing as unique_ptr.  I just changed names midstream.  I've posted
a reference implementation here:

http://home.twcny.rr.com/hinnant/cpp_extensions/unique_ptr.html

I am currently working on proposed wording for the library half of the
proposal - that which is proposed in N1771.  This will include proposed
wording for unique_ptr.  There are a few minor changes between the
unique_ptr as presented in N1771 and what I will propose in the Fall
mailing (mainly due to some excellent suggestions by Jonathan Turkanis).

-Howard

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Peter Dimov" <pdimov@gmail.com>
Date: 13 Aug 2005 23:20:05 GMT
Raw View
vladimir kliatchko wrote:
> Abstract
>
> Over a period of two years we have accumulated significant experience
> in developing and then using a family of C++ components implementing
> smart pointers. While similar to the smart pointers described by TR1,
> our implementation has some important distinctions. Our experience
> shows that these distinctions often resulted in both improved usability
> and superior performance. In this paper we describe how our
> implementation differs from TR1 and propose a number of corresponding
> changes to the C++ standard.
>
> To see the entire proposal, follow this link:
> http://home.earthlink.net/~phalpern/cpp/KliatchkoSharedPtr.pdf

The first two parts of your proposal - allocator support and aliasing -
are useful and straightforward extensions that I've considered several
times. I'm still not sure whether the std::allocator interface is the
right way to go, but it seems good enough.

I'd drop the is_allocator trait and leave only

shared_ptr( T * p, D d, A a );

Since replacing the allocator at construction time should be a
relatively rare occurence, it's not that much work to provide an
explicit default deleter.

is_allocator should be proposed independently (if at all); if accepted,
the deleter constructor can then be respecified to do the right thing.

tr1::bind did contain the Throws clause that you mention in its
original incarnation... we'll see what we can do to bring it back; I'm
pretty sure that nobody intended to remove it.

I'd reverse the arguments of the aliasing constructor:

shared_ptr( shared_ptr<Y> const & py, T * pt );

to avoid unintended clashes with the deleter constructor. The Effects
clause should simply be:

Constructs a shared_ptr object that shares ownership with py.

The newly constructed shared_ptr doesn't own pt; it owns whatever py
owns.

The Postcondition should be get() == pt; the rest is redundant.

This aliasing constructor does indeed increase the expressive power of
shared_ptr substantially (you can now implement the casts in user code,
for example). I was - and still am - a bit scared of the opportunities
for misuse, though.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: nagle@animats.com (John Nagle)
Date: Sun, 14 Aug 2005 12:41:09 GMT
Raw View
Peter Dimov wrote:

> vladimir kliatchko wrote:
> This aliasing constructor does indeed increase the expressive power of
> shared_ptr substantially (you can now implement the casts in user code,
> for example). I was - and still am - a bit scared of the opportunities
> for misuse, though.

     Yes.

     The whole point of automatic memory management is to make
it automatic and safe.  Creating a semiautomatic memory
management system with complex, obscure semantics is
likely to result in complex, obscure bugs.

     Microsoft's "Managed C++" system took that route, and
it wasn't pretty.

    John Nagle

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "vladimir kliatchko" <vladimir@kliatchko.com>
Date: Sun, 14 Aug 2005 17:28:53 CST
Raw View
Howard Hinnant wrote:
> In article <_3KKe.3550$Gx1.2720@fe11.lga>,
>  vladimir@kliatchko.com (vladimir kliatchko) wrote:
>
> >  shared_ptr<WorkItem> item(&*i, itemsPtr); // aliasing
>
> I've been looking at other ways this type of aliasing can be achieved
> with the existing tr1::shared_ptr spec.  Fwiw, here's one possibility:
>
> class link_life_to
> {
>     typedef std::shared_ptr<void> D;
> public:
>     link_life_to(const D& d) : d_(d) {}
>
>     template <class T> void operator()(T*) {}
> private:
>     D d_;
> };
>
> Now you can say:
>
> shared_ptr<WorkItem> item(&*i, link_life_to(itemsPtr)); // aliasing
>
> I believe this has the same effect as the proposed shared_ptr
> constructor.  A disadvantage though is that it sets up a second
> reference count instead of reusing the original reference count.  But
> there are a couple of advantages.

The extra memory allocation is a really significant disadvantage in our
experience. This extra allocation degrades performance and makes it
impossible to provide a no-failure guarantee. This disadvantage was (to
a large degree) what motivated us to incorporate aliasing into the
shared pointer directly. Also, we considered aliasing done by shared
pointer directly to be much more elegant solution and one that is
consistent with how casts are implemented.

>
> One advantage is that it is easier to search for lifetime dependencies
> between otherwise unrelated shared_ptr's (search for "link_life_to").

If this is important, though I am not sure that it is, we could provide
an explicit easily searchable "alias" method.

>
> Another advantage is that the approach is extendable to any smart
> pointer that takes a custom deleter.  For example you could use this
> tool to make unique_ptr behave like the proposed managed_ptr in that it
> would share ownership with a shared_ptr:
>
> typedef unique_ptr<WorkItem, link_life_to> UPtr;
> processor.processWorkItem(Uptr(&*i, link_life_to(itemsPtr)));
>
> The resulting unique_ptr actually has a lower overhead compared to
> managed_ptr in my experiments (sizeof == 3 words instead of 4 words).

The extra word used by managed_ptr buys us something that was a very
important design goal for us: the type of managed_ptr is not affected
by the type of the deleter. This makes managed_ptr an effective
interface type (see paper for more about this).

Another minor point is that using the technique above with unique_ptr
seems to contradict the very name of the unique_ptr class (unless of
course I misunderstand what the name is supposed to imply).

>
> If link_life_to is templated:
>
> template <class D = std::shared_ptr<void> >
> class link_life_to
> {
> public:
>     link_life_to(const D& d) : d_(d) {}
>
>     template <class T> void operator()(T*) {}
> private:
>     D d_;
> };
>
> then it can used to link the lifetimes of other types of smart pointers
> (as long as they accept custom deleters).  In order to use a unique_ptr
> as the lifetime to link with, link_life_to needs to be extended to
> handle move-only types:
>
> template <class D = std::shared_ptr<void> >
> class link_life_to
> {
> public:
>     link_life_to(const D& d) : d_(d) {}
>     link_life_to(D&& d) : d_(std::move(d)) {}
>     link_life_to(link_life_to&& x) : d_(std::move(x.d_)) {}
>     link_life_to& operator=(link_life_to&& x)
>         {d_ = std::move(x.d_); return *this;}
>
>     template <class T> void operator()(T*) {}
> private:
>     D d_;
> };
>
> Unless of course move members are auto-generated as Mirek Fidler
> proposes, in which case you don't need the explicit move constructor or
> move assignment.
>
> Or perhaps with N1717 (extended):
>
> template <class D = std::shared_ptr<void> >
> class link_life_to
> {
> public:
>     link_life_to(const D& d) : d_(d) {}
>     link_life_to(D&& d) : d_(std::move(d)) {}
>     link_life_to(link_life_to&&)            {default}
>     link_life_to& operator=(link_life_to&&) {default}
>
>     template <class T> void operator()(T*) {}
> private:
>     D d_;
> };
>
> -Howard
>
> ---
> [ comp.std.c++ is moderated.  To submit articles, try just posting with ]
> [ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
> [              --- Please see the FAQ before posting. ---               ]
> [ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]

Thanks for your comments and, also, thanks the links to the unique_ptr
documents. I am still looking through your proposals.

--vlad

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "vladimir kliatchko" <vladimir@kliatchko.com>
Date: Sun, 14 Aug 2005 18:48:57 CST
Raw View
John Nagle wrote:
> Peter Dimov wrote:
>
> > vladimir kliatchko wrote:
> > This aliasing constructor does indeed increase the expressive power of
> > shared_ptr substantially (you can now implement the casts in user code,
> > for example). I was - and still am - a bit scared of the opportunities
> > for misuse, though.
>
>      Yes.
>
>      The whole point of automatic memory management is to make
> it automatic and safe.  Creating a semiautomatic memory
> management system with complex, obscure semantics is
> likely to result in complex, obscure bugs.

Can you pls provide some examples of what kind of misuse you are
concerned with? Whenever a shared_ptr is constructed it is always up to
the user to supply a correct object pointer and an appropriate deleter.
The aliasing constructor follows the same pattern with another smart
pointer playing the role of a deleter. As long as we make aliasing
explicit and avoid accidental misuse (i.e., when the user did not
intend to use aliasing to begin with) I see no reason to not make this
feature available.

>
>      Microsoft's "Managed C++" system took that route, and
> it wasn't pretty.
>
>     John Nagle
>
> ---
> [ comp.std.c++ is moderated.  To submit articles, try just posting with ]
> [ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
> [              --- Please see the FAQ before posting. ---               ]
> [ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "vladimir kliatchko" <vladimir@kliatchko.com>
Date: Sun, 14 Aug 2005 18:46:39 CST
Raw View
Peter Dimov wrote:
> vladimir kliatchko wrote:
> > Abstract
> >
> > Over a period of two years we have accumulated significant experience
> > in developing and then using a family of C++ components implementing
> > smart pointers. While similar to the smart pointers described by TR1,
> > our implementation has some important distinctions. Our experience
> > shows that these distinctions often resulted in both improved usability
> > and superior performance. In this paper we describe how our
> > implementation differs from TR1 and propose a number of corresponding
> > changes to the C++ standard.
> >
> > To see the entire proposal, follow this link:
> > http://home.earthlink.net/~phalpern/cpp/KliatchkoSharedPtr.pdf
>
> The first two parts of your proposal - allocator support and aliasing -
> are useful and straightforward extensions that I've considered several
> times. I'm still not sure whether the std::allocator interface is the
> right way to go, but it seems good enough.
>
> I'd drop the is_allocator trait and leave only
>
> shared_ptr( T * p, D d, A a );
>
> Since replacing the allocator at construction time should be a
> relatively rare occurence, it's not that much work to provide an
> explicit default deleter.
>
> is_allocator should be proposed independently (if at all); if accepted,
> the deleter constructor can then be respecified to do the right thing.

We definitely have no objection to considering is_allocator
independently. But please note that the is_allocator trait is also
important for the implementation of the create_obj method. Without the
trait we would either always require an allocator to be passed to
create_obj or we would have to split create_obj into
create_obj_with_allocator and create_obj_without_allocator. In fact we
have contemplated not using the trait and doing exactly what you
suggested with the constructor.  We decided that using the traits was a
better approach but we definitely do not feel strongly about it.

>
> tr1::bind did contain the Throws clause that you mention in its
> original incarnation... we'll see what we can do to bring it back; I'm
> pretty sure that nobody intended to remove it.
>
> I'd reverse the arguments of the aliasing constructor:
>
> shared_ptr( shared_ptr<Y> const & py, T * pt );
>
> to avoid unintended clashes with the deleter constructor.

We actually had a vacillated between the signature of the aliasing
constructor above and the one in the proposal. On one hand we did want,
as you pointed out, to avoid confusion with the deleter. On the other
hand we did want to keep various constructors of various smart pointers
consistent in that the pointer is the first parameter. It seems that
you just tipped the balance in the direction of the first
consideration.

> The Effects clause should simply be:
>
> Constructs a shared_ptr object that shares ownership with py.
>
> The newly constructed shared_ptr doesn't own pt; it owns whatever py
> owns.
>
> The Postcondition should be get() == pt; the rest is redundant.
>
> This aliasing constructor does indeed increase the expressive power of
> shared_ptr substantially (you can now implement the casts in user code,
> for example). I was - and still am - a bit scared of the opportunities
> for misuse, though.

The features that we proposed naturally create some new opportunities
for misuse but, in our opinion, a big reason for the success of C++ is
that it does not limit users in their ability to access advanced
features for the sake of protecting others from themselves. That does
not mean that we do not want to prevent accidental misuse by those who
do not intend to use these features, but the general usage of shared
pointer should not be negatively affected by the mere presence of these
features. On the other hand, not having these features, in our
experience, would often result in complicated and error-prone work
around code with inferior performance.

>
> ---
> [ comp.std.c++ is moderated.  To submit articles, try just posting with ]
> [ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
> [              --- Please see the FAQ before posting. ---               ]
> [ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]

Thanks for your comments of the first two of the proposed features. We
are very much interested in your opinion of the other two though.
Please let us know what you think.

--vlad

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Peter Dimov" <pdimov@gmail.com>
Date: Mon, 15 Aug 2005 09:49:19 CST
Raw View
vladimir kliatchko wrote:
> Peter Dimov wrote:

> > I'd drop the is_allocator trait and leave only
> >
> > shared_ptr( T * p, D d, A a );
> >
> > Since replacing the allocator at construction time should be a
> > relatively rare occurence, it's not that much work to provide an
> > explicit default deleter.
> >
> > is_allocator should be proposed independently (if at all); if accepted,
> > the deleter constructor can then be respecified to do the right thing.
>
> We definitely have no objection to considering is_allocator
> independently. But please note that the is_allocator trait is also
> important for the implementation of the create_obj method. Without the
> trait we would either always require an allocator to be passed to
> create_obj or we would have to split create_obj into
> create_obj_with_allocator and create_obj_without_allocator.

Separating the two is a better design as it supports constructors that
take an allocator as a first argument.

> > I'd reverse the arguments of the aliasing constructor:
> >
> > shared_ptr( shared_ptr<Y> const & py, T * pt );
> >
> > to avoid unintended clashes with the deleter constructor.
>
> We actually had a vacillated between the signature of the aliasing
> constructor above and the one in the proposal. On one hand we did want,
> as you pointed out, to avoid confusion with the deleter. On the other
> hand we did want to keep various constructors of various smart pointers
> consistent in that the pointer is the first parameter.

This is a false consistency since the constructors taking a pointer
assume ownership of the pointer. Constructors that take a shared_ptr as
a first argument create a copy.

> The features that we proposed naturally create some new opportunities
> for misuse but, in our opinion, a big reason for the success of C++ is
> that it does not limit users in their ability to access advanced
> features for the sake of protecting others from themselves. That does
> not mean that we do not want to prevent accidental misuse by those who
> do not intend to use these features, but the general usage of shared
> pointer should not be negatively affected by the mere presence of these
> features. On the other hand, not having these features, in our
> experience, would often result in complicated and error-prone work
> around code with inferior performance.

True. The alternative is to provide safe methods that cover the common
aliasing use cases - casts, pointer to member, pointer to container
element. Not particularly appealing.

> Thanks for your comments of the first two of the proposed features. We
> are very much interested in your opinion of the other two though.
> Please let us know what you think.

It's too early for that. :-)

create_obj is interesting, although I tend to prefer the free function
formulation:

template<class T, ...> shared_ptr<T> create_object( ... );

Avoiding one of the malloc locks can be a big win; on the other hand,
under a good lock-free malloc we won't gain much. It should also be
noted that create_object will silently ignore a class-specific operator
new.

I assume that the deleter of such a shared_ptr instance invokes the
destructor of T, but the storage is not deallocated until the last
weak_ptr goes away, right?

When an explicit allocator is specified, how do you obtain the storage?
Rebind to pair< sp_control_block, aligned_storage_for<T> >? The array
variant probably rebinds to char?

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Howard Hinnant <hinnant@metrowerks.com>
Date: Mon, 15 Aug 2005 16:17:33 CST
Raw View
In article <1124055758.701281.62500@o13g2000cwo.googlegroups.com>,
 "vladimir kliatchko" <vladimir@kliatchko.com> wrote:

> > Another advantage is that the approach is extendable to any smart
> > pointer that takes a custom deleter.  For example you could use this
> > tool to make unique_ptr behave like the proposed managed_ptr in that it
> > would share ownership with a shared_ptr:
> >
> > typedef unique_ptr<WorkItem, link_life_to> UPtr;
> > processor.processWorkItem(Uptr(&*i, link_life_to(itemsPtr)));
> >
> > The resulting unique_ptr actually has a lower overhead compared to
> > managed_ptr in my experiments (sizeof == 3 words instead of 4 words).
>
> The extra word used by managed_ptr buys us something that was a very
> important design goal for us: the type of managed_ptr is not affected
> by the type of the deleter. This makes managed_ptr an effective
> interface type (see paper for more about this).
>
> Another minor point is that using the technique above with unique_ptr
> seems to contradict the very name of the unique_ptr class (unless of
> course I misunderstand what the name is supposed to imply).

I think I was unclear with this part of my post.

I assert that a zero-overhead unique ownership smart_ptr<T, D> is a
valuable tool for building other types of smart pointers such as
managed_ptr, especially if we get template aliases.

For example:

template <class T>
  using managed_ptr = unique_ptr<T, link_life_to>;

I.e. now managed_ptr<T> <i>is</i> your interface type.  Only you didn't
have to reinvent all of the typical unique-ownership smart pointer
drudgery.

Here's a custom deleter (untested) which more closely mimics your
current managed_ptr specification:

template <class T> inline
void reclaim_managed_object(T* t, std::default_delete<T>*)
    {std::default_delete<T>()(t);}

struct managed_ptr_deleter
{
    template <class T, class D>
    static
    void helper(void* t, void* d)
    {
        reclaim_managed_object((T*)t, (D*)d);
    }

    template <class Y>
    managed_ptr_deleter(Y* p)
        : orig_(p),
          deleter_(0),
          cleanup_(helper<Y, std::default_delete<Y> >)
    {
    }

    template <class Y, class D>
    managed_ptr_deleter(Y* p, D* d)
        : orig_(p),
          deleter_(d),
          cleanup_(helper<Y, D>)
    {
    }

    void operator()(void*) {cleanup_(orig_, deleter_);}

    void* orig_;
    void* deleter_;
    void (*cleanup_)(void*, void*);
};

template <class T>
  using managed_ptr = unique_ptr<T, managed_ptr_deleter>;

--

Although your use case doesn't call for it, I think a lot of use cases
will call for vector<unique-ownership-smart-ptr<T> >.  And so I'm very
motivated that this common use case be achievable with a zero-overhead
smart pointer (sizeof == 1 word).

In order to have custom deleter capabilities, and keep the overhead to
zero, the deleter must be static (part of the type).  I.e. unique_ptr<T,
D> (or by whatever name).  Given that, I'm now saying:  Let's see how
much we can reuse this animal.  And it looks to me like quite a bit.

-Howard

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "vladimir kliatchko" <vladimir@kliatchko.com>
Date: Wed, 10 Aug 2005 00:44:35 CST
Raw View
Abstract

Over a period of two years we have accumulated significant experience
in developing and then using a family of C++ components implementing
smart pointers. While similar to the smart pointers described by TR1,
our implementation has some important distinctions. Our experience
shows that these distinctions often resulted in both improved usability
and superior performance. In this paper we describe how our
implementation differs from TR1 and propose a number of corresponding
changes to the C++ standard.

To see the entire proposal, follow this link:
http://home.earthlink.net/~phalpern/cpp/KliatchkoSharedPtr.pdf

Sincerely,
--vlad

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]