Defect Report: Unintended consequences of N2351

37 views
Skip to first unread message

Joe Gottman

unread,
Aug 13, 2007, 10:03:02 AM8/13/07
to
N2351
(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2351.htm),
which approved at the latest meeting, has some unintended consequences
for other parts of the shared_ptr specification. The new aliasing
constructor for shared_ptr,
template<class Y> shared_ptr( shared_ptr<Y> const & r, T * p )

includes the following note:
"This constructor allows creation of an empty shared_ptr instance with
a non-NULL stored pointer."

However, there are several other functions that return a shared_ptr
that is specified to be empty, and they previously were able to assume
that the stored pointer will be null:

a) dynamic_pointer_cast returns an empty shared_ptr when the
internal dynamic_cast fails.
b) weak_ptr::lock() returns an empty shared_ptr when the associated
shared_ptr has expired.
c) shared_ptr's new move constructor (also added as part of N2351)
specifies that the shared_ptr passed into the constructor will be empty
after the constructor finishes.

In all these cases, we now need to state that the shared_ptr's in
question not only are empty but also return 0 from get().

Joe Gottman

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

Greg Herlihy

unread,
Aug 13, 2007, 11:43:29 PM8/13/07
to
On Aug 13, 7:03 am, jgott...@carolina.rr.com (Joe Gottman) wrote:
> N2351
> (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2351.htm),
> which approved at the latest meeting, has some unintended consequences
> for other parts of the shared_ptr specification. The new aliasing
> constructor for shared_ptr,
> template<class Y> shared_ptr( shared_ptr<Y> const & r, T * p )
>
> includes the following note:
> "This constructor allows creation of an empty shared_ptr instance >
> with a non-NULL stored pointer."

For example:

shared_ptr<int> p1( new int );
shared_ptr<int> p2( p1, NULL );

The shared pointer p2 is empty, nonetheless p2 "stores" (in a virtual
sense) p1's int pointer.

> However, there are several other functions that return a shared_ptr
> that is specified to be empty, and they previously were able to assume
> that the stored pointer will be null:

The "stored" pointer cannot be retrieved from the empty shared_ptr (p2
in the above example) because calling get() on an empty shared_ptr
always returns NULL (otherwise the shared_ptr would not be empty).
Now, it appears that a client could detect whether an empty shared_ptr
is an alias for a non-NULL shared pointer value by testing whether the
empty shared_ptr's use_count() method returns a positive number
(although I don't see how that information could be useful).

> a) dynamic_pointer_cast returns an empty shared_ptr when the
> internal dynamic_cast fails.
> b) weak_ptr::lock() returns an empty shared_ptr when the associated
> shared_ptr has expired.
> c) shared_ptr's new move constructor (also added as part of N2351)
> specifies that the shared_ptr passed into the constructor will be empty
> after the constructor finishes.
>
> In all these cases, we now need to state that the shared_ptr's in
> question not only are empty but also return 0 from get().

No new wording is needed here, because a shared_ptr cannot be "empty"
unless its get() method returns a NULL pointer. The addition of a
shared_ptr constructor to support pointer aliasing does not change the
meaning of "empty". In fact, the proposed rewording would be at best
redundant - and at worst, confusing - because the Standard would be
suggesting that an "empty" shared_ptr does not in fact have to be
empty.

Greg

Joe Gottman

unread,
Aug 13, 2007, 11:51:20 PM8/13/07
to
Greg Herlihy wrote:
> On Aug 13, 7:03 am, jgott...@carolina.rr.com (Joe Gottman) wrote:
>> N2351
>> (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2351.htm),
>> which approved at the latest meeting, has some unintended consequences
>> for other parts of the shared_ptr specification. The new aliasing
>> constructor for shared_ptr,
>> template<class Y> shared_ptr( shared_ptr<Y> const & r, T * p )
>>
>> includes the following note:
>> "This constructor allows creation of an empty shared_ptr instance >
>> with a non-NULL stored pointer."
>
> For example:
>
> shared_ptr<int> p1( new int );
> shared_ptr<int> p2( p1, NULL );
>
> The shared pointer p2 is empty, nonetheless p2 "stores" (in a virtual
> sense) p1's int pointer.
>

I thought that an empty shared_ptr was one with a use_count() of 0,
and that the stored pointer is the one obtained from get(). Could
someone please clarify this for us? The situation I was worried about
was the following:

shared_ptr<int> p1; //Empty shared_ptr.
static int x = 7;
shared_ptr p2(p1, &x); //"Shares ownership" with p1, which is empty.

While this code is guaranteed not to seg-fault and might be a useful way
to create a shared_ptr to a global or static object, p2 now has the
unusual property that p2.use_count() == 0 but p2.get() != 0. I
definitely would not want to get a shared_ptr that looks like this from
a failed dynamic_pointer_cast or weak_ptr::lock(), because both of these
are often used inside if statements.

Joe Gottman

Roman.Pe...@gmail.com

unread,
Aug 14, 2007, 9:09:48 AM8/14/07
to
> ...

I just checked the document and noticed incorrect signature
of allocate_shared function template.

template<class T, class... Args>
shared_ptr<T> allocate_shared( A const & a, Args && ... args );

'A' is undefined in this scope. Should be

template<class T, class A, class... Args>
shared_ptr<T> allocate_shared( A const & a, Args && ... args );

Roman Perepelitsa.

Peter Dimov

unread,
Aug 14, 2007, 5:44:49 PM8/14/07
to
On Aug 14, 4:09 pm, "Roman.Perepeli...@gmail.com"
<Roman.Perepeli...@gmail.com> wrote:

> I just checked the document and noticed incorrect signature
> of allocate_shared function template.
>
> template<class T, class... Args>
> shared_ptr<T> allocate_shared( A const & a, Args && ... args );
>
> 'A' is undefined in this scope. Should be
>
> template<class T, class A, class... Args>
> shared_ptr<T> allocate_shared( A const & a, Args && ... args );

You are right. This appears to have already been fixed in the Working
Paper (N2369).

Peter Dimov

unread,
Aug 14, 2007, 6:12:10 PM8/14/07
to
On Aug 14, 6:51 am, jgott...@carolina.rr.com (Joe Gottman) wrote:

> I thought that an empty shared_ptr was one with a use_count() of 0,
> and that the stored pointer is the one obtained from get(). Could
> someone please clarify this for us? The situation I was worried about
> was the following:
>
> shared_ptr<int> p1; //Empty shared_ptr.
> static int x = 7;
> shared_ptr p2(p1, &x); //"Shares ownership" with p1, which is empty.

You are reading the note right. I agree that it wouldn't hurt to
clarify that get() == 0 wherever else an empty pointer is said to be
returned or created, although I wouldn't expect this to be a problem
in practice.

Alberto Ganesh Barbati

unread,
Aug 17, 2007, 8:18:21 PM8/17/07
to
Greg Herlihy ha scritto:

> On Aug 13, 7:03 am, jgott...@carolina.rr.com (Joe Gottman) wrote:
>> N2351
>> (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2351.htm),
>> which approved at the latest meeting, has some unintended consequences
>> for other parts of the shared_ptr specification. The new aliasing
>> constructor for shared_ptr,
>> template<class Y> shared_ptr( shared_ptr<Y> const & r, T * p )
>>
>> includes the following note:
>> "This constructor allows creation of an empty shared_ptr instance >
>> with a non-NULL stored pointer."
>
> For example:
>
> shared_ptr<int> p1( new int );
> shared_ptr<int> p2( p1, NULL );
>
> The shared pointer p2 is empty, nonetheless p2 "stores" (in a virtual
> sense) p1's int pointer.
>

The ctor use to construct p2 is defined as (emphasis mine) "Constructs a
shared_ptr instance that *stores p* and shares ownership with r." If I
understand this sentence correctly:

1) the "stored pointer" of p2 is NULL, in particular, it has nothing to
do with p1.

2) p2 isn't empty at all, because it shares ownership with a non-empty
pointer, yet get() returns NULL because that's what the "stored pointer" is.

>
> The "stored" pointer cannot be retrieved from the empty shared_ptr (p2
> in the above example) because calling get() on an empty shared_ptr
> always returns NULL (otherwise the shared_ptr would not be empty).

You completely lost me here. It seems that you are using the expression
"stored pointer" to refer to something related with p1, which can't be
according to my interpretation above. Could you please elaborate?

Now, let's consider this case (as reported in the OP's second post):

shared_ptr<int> p1; //Empty shared_ptr.
static int x = 7;
shared_ptr p2(p1, &x); //"Shares ownership" with p1, which is empty.

is p2 empty? The current wording doesn't say so explicitly, so we shall
assume that it is *not* empty! The current wording never defines what
"empty" means, it merely states which constructors makes empty pointers
and under which conditions and this "new" constructor is not listed as
such. However, I guess the intent was to make p2 empty, so I suggest
replacing the sentence:

"Effects: Constructs a shared_ptr instance that stores p and shares
ownership with r."

with a more explicit:

"Effects: If r is empty, constructs an empty shared_ptr object;
otherwise, constructs a shared_ptr object that stores p and
shares ownership with r."

(compare with the definition of the copy constructor.)

With this wording we lose the possibility to have an empty pointer with
a non-NULL stored pointer. But is it really necessary? I mean, you can't
get the value of the stored pointer anyway, because get() always returns
NULL for empty pointers, so what's the point?

Just my opinion,

Ganesh

Joe Gottman

unread,
Aug 18, 2007, 1:18:38 AM8/18/07
to
Peter Dimov wrote:
> On Aug 14, 6:51 am, jgott...@carolina.rr.com (Joe Gottman) wrote:
>
>> I thought that an empty shared_ptr was one with a use_count() of 0,
>> and that the stored pointer is the one obtained from get(). Could
>> someone please clarify this for us? The situation I was worried about
>> was the following:
>>
>> shared_ptr<int> p1; //Empty shared_ptr.
>> static int x = 7;
>> shared_ptr p2(p1, &x); //"Shares ownership" with p1, which is empty.
>
> You are reading the note right. I agree that it wouldn't hurt to
> clarify that get() == 0 wherever else an empty pointer is said to be
> returned or created, although I wouldn't expect this to be a problem
> in practice.
>

I've been thinking about this case a bit, and I think that there's
more that needs to be done besides just adding a few notes. As far as I
can tell, there are 3 differences in observable behavior between the
shared_ptr p2 above and an ordinary shared_ptr created with a non-null
pointer:

1) p2.use_count() returns 0
2) Under the equivalence relation defined by operator<, p2 compares
equivalent to a default-constructed shared_ptr. This could be rather
surprising if a user tried to store p2 in a set. It would be even more
surprising if a user created two shared_ptr's to two different global or
static objects in this way and tried to store both of them in the same set.
3) If the user created a weak_ptr<int> wp from p2 and then called
wp.lock() he would not get p2 back. He would get a default-constructed
shared_ptr, despite the fact that p2.get() is still a valid pointer.

Difference 1) isn't a problem. The major use-case of use_count() is to
find cyclic dependencies, and there won't be any in this case.
Differences 2) and 3), on the other hand, are less obvious and could
easily end up producing hard-to-find bugs.

Joe Gottman

Greg Herlihy

unread,
Aug 18, 2007, 10:46:52 AM8/18/07
to
On 8/17/07 5:18 PM, in article BDpxi.91954$U01.7...@twister1.libero.it,

"Alberto Ganesh Barbati" <Alberto...@libero.it> wrote:

> Greg Herlihy ha scritto:
>> On Aug 13, 7:03 am, jgott...@carolina.rr.com (Joe Gottman) wrote:
>>>
>>> The new aliasing constructor for shared_ptr,
>>> template<class Y> shared_ptr( shared_ptr<Y> const & r, T * p )
>>>
>>> includes the following note:
>>> "This constructor allows creation of an empty shared_ptr instance >
>>> with a non-NULL stored pointer."
>>
>> For example:
>>
>> shared_ptr<int> p1( new int );
>> shared_ptr<int> p2( p1, NULL );
>>
>> The shared pointer p2 is empty, nonetheless p2 "stores" (in a virtual
>> sense) p1's int pointer.
>>
>
> The ctor use to construct p2 is defined as (emphasis mine) "Constructs a
> shared_ptr instance that *stores p* and shares ownership with r." If I
> understand this sentence correctly:
>
> 1) the "stored pointer" of p2 is NULL, in particular, it has nothing to
> do with p1.

Is the antecedent of "...with a non-NULL stored pointer" (in the Note), the
"shared_ptr" or its "creation"? In other words, did the program use a
non-null pointer stored in p1 in the creation of p2, or did the program
create an empty p2 that stores a non-NULL pointer?

Grammatically, the latter interpretation is the more probable, but
logically, it is also impossible. A empty shared_ptr cannot store a non-NULL
pointer and still fulfill the requirements for shared_ptr::get()'s return
value:

T* get() const;

Returns: the stored pointer. Returns a null pointer if *this is empty.

So passing a non-NULL pointer to initialize p2 would mean that p2 is not
empty (because calling p2.get() would not return NULL). So, with the
assumption that this Note is meant to convey some meaningful piece of
information to its reader, the only alternative interpretation with a chance
of making sense - is the one that I presented.



> 2) p2 isn't empty at all, because it shares ownership with a non-empty
> pointer, yet get() returns NULL because that's what the "stored pointer" is.

Even if p2 is not empty, then it may as well be - at least as far as the C++
program is concerned. I don't see how distinguishing between a shared_ptr
that stores a NULL pointer from a shared_ptr that is certifiably empty,
would be at all a useful distinction to make. Whether get() returns a
"stored" NULL pointer or an "empty" NULL pointer, the program either way
does not end up with a pointer that points to anything.



>> The "stored" pointer cannot be retrieved from the empty shared_ptr (p2
>> in the above example) because calling get() on an empty shared_ptr
>> always returns NULL (otherwise the shared_ptr would not be empty).
>

> Now, let's consider this case (as reported in the OP's second post):
>
> shared_ptr<int> p1; //Empty shared_ptr.
> static int x = 7;
> shared_ptr p2(p1, &x); //"Shares ownership" with p1, which is empty.
>
> is p2 empty? The current wording doesn't say so explicitly, so we shall
> assume that it is *not* empty! The current wording never defines what
> "empty" means, it merely states which constructors makes empty pointers
> and under which conditions and this "new" constructor is not listed as
> such. However, I guess the intent was to make p2 empty, so I suggest
> replacing the sentence:

A program cannot store and retrieve a pointer value from a shared_ptr and
still call the shared_ptr "empty". According to the current draft, a
shared_ptr may be empty only if its usage_count() returns 0 and its get()
method returns NULL. (Note that even if a shared_ptr meets both conditions
it may not be technically "empty", because "empty" is never defined
anywhere. Nonetheless, because such a pointer would be indistinguishable
from an empty shared_ptr - for all practical purposes the shared_ptr is
empty).



> "Effects: Constructs a shared_ptr instance that stores p and shares
> ownership with r."
>
> with a more explicit:
>
> "Effects: If r is empty, constructs an empty shared_ptr object;
> otherwise, constructs a shared_ptr object that stores p and
> shares ownership with r."
>

> With this wording we lose the possibility to have an empty pointer with
> a non-NULL stored pointer. But is it really necessary? I mean, you can't
> get the value of the stored pointer anyway, because get() always returns
> NULL for empty pointers, so what's the point?

The problem is the meaning of "empty" in the Note is at variance with the
its meaning in the draft text. (The Note considers any shared_ptr with
usage_count() that returns zero to be "empty", regardless of what get()
returns). Note that this discrepancy is technically not a defect in the
draft - because the Note is not normative and therefore doesn't have to make
sense.

Greg

Greg Herlihy

unread,
Aug 18, 2007, 10:47:50 AM8/18/07
to


On 8/13/07 8:51 PM, in article 46c121f2$0$30614$4c36...@roadrunner.com,


"Joe Gottman" <jgot...@carolina.rr.com> wrote:
>
> I thought that an empty shared_ptr was one with a use_count() of 0,
> and that the stored pointer is the one obtained from get(). Could
> someone please clarify this for us? The situation I was worried about
> was the following:
>
> shared_ptr<int> p1; //Empty shared_ptr.
> static int x = 7;
> shared_ptr p2(p1, &x); //"Shares ownership" with p1, which is empty.
>
> While this code is guaranteed not to seg-fault and might be a useful way
> to create a shared_ptr to a global or static object, p2 now has the
> unusual property that p2.use_count() == 0 but p2.get() != 0.

I hope that there is a more compelling example of this constructor's
usefulness - than simply to create a shared_ptr instance that does not
delete its stored pointer. After all, if the goal is to provide a way to
store a pointer to a static or a local object in a shared_ptr safely (say,
for the purpose of passing the pointer as a shared_ptr parameter in a
function call), then declaring a "null_deleter" functor and a corresponding
deleter object would seem to make a lot more sense. For example:

struct null_deleter
{
template <class T>
void operator()(T*) const {}
};

const null_deleter dont_delete = {};

Now the example above could be written like this:

static int x = 7;

shared_ptr<int> p2( &x, dont_delete );

Not only is this revised program one line shorter (and one object lighter)
than the original - its purpose is much more clear. N2351 calls the use of
the shared_ptr alias constructor, "advanced" - which I think is something of
an understatement. A C++ programmer would practically have had to have
implemented shared_ptr themselves - before they could accurately predict how
the p2 shared_ptr in the first program actually behaves - whereas
understanding the p2 declaration in the revised program, requires no such
expertise.

Greg

Alberto Ganesh Barbati

unread,
Aug 18, 2007, 3:16:35 PM8/18/07
to
Greg Herlihy ha scritto:
>
> <snip>

You didn't seem to be getting my point, anyway it's no use replying
word-by-word to what you wrote. Let's stick to the facts, it may be that
we are actually agreeing on something.

>
> The problem is the meaning of "empty" in the Note is at variance with the
> its meaning in the draft text. (The Note considers any shared_ptr with
> usage_count() that returns zero to be "empty", regardless of what get()
> returns).

The standard does *not* say that a shared_ptr for which use_count() == 0
is empty! The condition use_count() == 0 is necessary for empty-ness but
not sufficient. Whether that is the intent or not, I don't know, but
logically speaking that is a fact. (The same argument applies to get()
== NULL).

Yet, "empty" is *very clearly* defined by the standard. An empty
shared_ptr is any shared_ptr instance obtained in either one of these ways:

1) by construction with the default constructor (notice that
"shared_ptr<int> p(0);" does *not* make p empty)

2) by copying an empty shared_ptr

3) by applying the move constructor (the source is made empty as a
result of the move)

4) by calling reset() (in fact this fact follows from 1) because reset()
is defined in terms of the swap trick)

5) as a result of a static_pointer_cast or const_pointer_cast when the
argument is an empty shared_ptr

6) as a result of a dynamic_pointer_cast when dynamic_cast<>(r.get())
returns a null pointer

This list is (currently) exhaustive. The standard does not describe any
other way to produce an empty shared pointer.

Given that, the text in the note is clearly contradictory because it
says: "This constructor allows creation of an empty shared_ptr instance
with [blah blah]" yet that constructor is not normatively able to
produce empty shared_ptrs *at all*!

I believe that the "Effects" clause should be amended to explicitly (and
normatively!) allow the creation of empty shared_ptrs. I already gave
one possible proposal for such amendment, which, incidentally, rules out
the note completely. In fact it would de facto require that empty
shared_ptrs do not actually have any "stored pointer" to speak about.
I'm curious to hear comments about this approach.

> returns). Note that this discrepancy is technically not a defect in the
> draft - because the Note is not normative and therefore doesn't have to make
> sense.
>

Well... Would you really like to have a standard filled with
non-normative notes that don't make any sense? I know that's not a
formal "defect" but it should be fixed, if possible, shouldn't it?

Just my opinion,

Ganesh

Alberto Ganesh Barbati

unread,
Aug 18, 2007, 8:04:01 PM8/18/07
to
Greg Herlihy ha scritto:

> On 8/13/07 8:51 PM, in article 46c121f2$0$30614$4c36...@roadrunner.com,
> "Joe Gottman" <jgot...@carolina.rr.com> wrote:
>>
>> shared_ptr<int> p1; //Empty shared_ptr.
>> static int x = 7;
>> shared_ptr p2(p1, &x); //"Shares ownership" with p1, which is empty.
>>
>> While this code is guaranteed not to seg-fault and might be a useful way
>> to create a shared_ptr to a global or static object, p2 now has the
>> unusual property that p2.use_count() == 0 but p2.get() != 0.
>
> I hope that there is a more compelling example of this constructor's
> usefulness - than simply to create a shared_ptr instance that does not
> delete its stored pointer.

Of course that's not the motivating example of this feature! At least
two more interesting examples come to my mind. The first one is the
following:

class Base1 { /* ... */ };
class Base2 { /* ... */ };
class Derived : public Base1, public Base2 { /* ... */ };

void foo(shared_ptr<Base2> p);

void bar(shared_ptr<Derived> p)
{
assert(p.get());
foo(shared_ptr<Base2>(p, static_cast<Base2*>(p.get()));
// same as:
// foo(static_pointer_cast<Base2>(p));
}

The use of multiple inheritance is only to show that the "stored
pointers" can actually have completely different values.

Another case that is similar but cannot be achieved through
*_pointer_cast occurs quite often in COM (delegation to sub-object):

class AbstractInterface { /* ... */ };

class ConcreteInterface : public AbstractInterface { /* ... */ };

struct ObjectClass {
ConcreteInterface m_iface;
/* ... */
};

void foo(shared_ptr<AbstractInterface> p);

void bar(shared_ptr<ObjectClass> p)
{
assert(p.get());
foo(shared_ptr<AbstractInterface>(p, &(p->m_iface)));
}

These two example alone are sufficient, IMHO, to motivate the "new"
constructor. However, I still can't find an example that can motivate
having an empty shared_ptr with a non-null stored pointer...

Ganesh

Peter Dimov

unread,
Aug 19, 2007, 10:21:17 AM8/19/07
to
On Aug 18, 8:18 am, Joe Gottman <jgott...@carolina.rr.com> wrote:

> I've been thinking about this case a bit, and I think that there's
> more that needs to be done besides just adding a few notes. As far as I
> can tell, there are 3 differences in observable behavior between the
> shared_ptr p2 above and an ordinary shared_ptr created with a non-null
> pointer:
>
> 1) p2.use_count() returns 0
> 2) Under the equivalence relation defined by operator<, p2 compares
> equivalent to a default-constructed shared_ptr. This could be rather
> surprising if a user tried to store p2 in a set. It would be even more
> surprising if a user created two shared_ptr's to two different global or
> static objects in this way and tried to store both of them in the same set.
> 3) If the user created a weak_ptr<int> wp from p2 and then called
> wp.lock() he would not get p2 back. He would get a default-constructed
> shared_ptr, despite the fact that p2.get() is still a valid pointer.

These are all properties of empty pointers; nothing new here.
Regarding (2), pointers to global/static objects already don't play
well with sets today unless one takes care to also define a
corresponding global/static shared_ptr master.

Peter Dimov

unread,
Aug 19, 2007, 12:39:58 PM8/19/07
to
On Aug 19, 3:04 am, AlbertoBarb...@libero.it (Alberto Ganesh Barbati)
wrote:

> These two example alone are sufficient, IMHO, to motivate the "new"
> constructor. However, I still can't find an example that can motivate
> having an empty shared_ptr with a non-null stored pointer...

An empty shared_ptr is almost as efficient as a raw pointer; it
doesn't allocate on construction, and it doesn't maintain a reference
count on copy/destroy. This is what makes it useful in certain
scenarios and circles. :-)

The aliasing constructor is already sharp enough to cut through limbs,
so its uses should be screened carefully and there isn't much safety
to be gained by specifically testing for this situation in the
constructor and doing something to prevent it.

Alberto Ganesh Barbati

unread,
Aug 20, 2007, 1:53:55 AM8/20/07
to
Peter Dimov ha scritto:

> On Aug 19, 3:04 am, AlbertoBarb...@libero.it (Alberto Ganesh Barbati)
> wrote:
>
>> These two example alone are sufficient, IMHO, to motivate the "new"
>> constructor. However, I still can't find an example that can motivate
>> having an empty shared_ptr with a non-null stored pointer...
>
> An empty shared_ptr is almost as efficient as a raw pointer; it
> doesn't allocate on construction, and it doesn't maintain a reference
> count on copy/destroy. This is what makes it useful in certain
> scenarios and circles. :-)
>

Speaking of efficiency, allowing empty shared_ptr with a non-NULL
"stored pointer" is going to make the implementation less efficient. For
example, the current boost implementation of get() is the following:

T * get() const // never throws
{
return px;
}

(px is the member holding the "stored pointer")

Clearly get() is as efficient as it could be! That's because the
implementation currently can assume that the shared_ptr is empty iff px
== NULL. However, if the assumption is no longer valid then get() will
need an additional check in order to comply to the requirement that it
must return NULL for an empty shared_ptr even if px != NULL.

The same overhead is due for the other observers that are defined in
terms of get().

So in order to allow an obscure corner case that practically no people
can exploit (because you can't extract the "stored pointer" of an empty
shared_ptr in any way), everyone will be paying a small but
non-negligible cost.

Yet it seems that you are defending the availability of the "corner
case". What am I missing?

Ganesh

Peter Dimov

unread,
Aug 20, 2007, 11:18:25 AM8/20/07
to
On Aug 20, 8:53 am, AlbertoBarb...@libero.it (Alberto Ganesh Barbati)
wrote:

> Clearly get() is as efficient as it could be! That's because the


> implementation currently can assume that the shared_ptr is empty iff px
> == NULL. However, if the assumption is no longer valid then get() will
> need an additional check in order to comply to the requirement that it
> must return NULL for an empty shared_ptr even if px != NULL.

There is no such assumption, and no such requirement. get() will stay
as is. What would the point of the non-NULL px be if you can't get()
it?

As an example of the reverse case, consider:

shared_ptr<void> px( (void*)0, f );

Here px is NULL, but the pointer isn't empty.

Alberto Ganesh Barbati

unread,
Aug 20, 2007, 5:46:22 PM8/20/07
to
Peter Dimov ha scritto:

> On Aug 20, 8:53 am, AlbertoBarb...@libero.it (Alberto Ganesh Barbati)
> wrote:
>
>> Clearly get() is as efficient as it could be! That's because the
>> implementation currently can assume that the shared_ptr is empty iff px
>> == NULL. However, if the assumption is no longer valid then get() will
>> need an additional check in order to comply to the requirement that it
>> must return NULL for an empty shared_ptr even if px != NULL.
>
> There is no such assumption, and no such requirement. get() will stay
> as is.

I apologize, I made a mistake. In fact this implementation of get() is
making a different assumption, namely that if the shared_ptr is empty
then px == NULL, so that returning px always result in the correct
behaviour whether the shared_ptr is empty or not. This kind of
"optimization" could not be exploited if the new wording is accepted as is.

> as is. What would the point of the non-NULL px be if you can't get()
> it?

Exactly, there's no point at all. That's what I am trying to say! This
case is both useless and detrimental. It should be explicitly
disallowed, IMHO, and I proposed wording for that in a previous post of
mine. For sake of completeness, I repeat it here:

Replace the paragraph:

"Effects: Constructs a shared_ptr instance that stores p and shares
ownership with r."

with:

"Effects: If r is empty, constructs an empty shared_ptr object;
otherwise, constructs a shared_ptr object that stores p and
shares ownership with r."

and remove entirely the note:

"This constructor allows creation of an empty shared_ptr instance with a
non-NULL stored pointer."

I still haven't received any feedback, neither positive nor negative,
about this proposal...

>
> As an example of the reverse case, consider:
>
> shared_ptr<void> px( (void*)0, f );
>
> Here px is NULL, but the pointer isn't empty.
>

So what? This case makes perfect sense to me and I find it good that
it's allowed. Are you arguing that this case should be forbidden as
well? Or are you suggesting that this apparent symmetry could be enough
to justify both cases? I hope the answer is no to both questions...

Ganesh

Daniel Krügler

unread,
Aug 20, 2007, 10:40:59 PM8/20/07
to
On 20 Aug., 23:46, AlbertoBarb...@libero.it (Alberto Ganesh Barbati)
wrote:

> I still haven't received any feedback, neither positive nor negative,
> about this proposal...

This discussion has left me in a totally confused state (maybe
"empty"?).

Alberto has done the work to enumerate the cases where the standard
can create empty shared pointers, Joe Gottman seems to interpret that
the current wording can be read as: "A shared_ptr instance is *empty*,
if, and only if, use_count() == 0." Specifically, this definition says
*nothing*
concerning the return value of get() (Please correct me, if I
misunderstand him).

Would it be too difficult, to clearly *define*, what we mean with
"empty"
in this situation? Personally, I would prefer an unambigious sentence
like above compared to an enumeration of creation szenarios...

Given the new constructor

template<class Y> shared_ptr(shared_ptr<Y> const& r, T *p);

(which I characterize as really useful, btw), we have IMO some
form of mismatch between what the specification of what get()
says compared to the possible out-come of this c'tor. If I correctly
understand the consensus (if their is one), then we can indeed
prepare shared_ptr instances which are empty and which return
get() != 0, but get() says it will return 0 if empty. IMO, this is a
contradiction. Either we should say that empty means that we
have use_count() == 0 && get() == 0 or that we should change
the specification of get() related to the empty state. Since the
c'tor

template<class Y> explicit shared_ptr(Y* p);

(and it's variants with deleter and/or allocator) already allows
creation of shared pointer with get() == 0 && use_count() != 0
there seems not to be any advantage to demand that "empty"
is related to the usecount in some form. This again seems to
imply that the return clause specification of get() should be
adapted to get rid of the contradiction.

Or have I misunderstand the whole point?

Greetings from Bremen,

Daniel

Peter Dimov

unread,
Aug 22, 2007, 2:50:34 AM8/22/07
to
On Aug 21, 5:40 am, Daniel Krügler <daniel.krueg...@googlemail.com>
wrote:

> ... but get() says it will return 0 if empty.

Ah. I get it now. The get() wording somehow acquired "Returns a null
pointer if *this is empty." somewhere between N1450/N1596 and N1836.
I'm relying on my memory about what I wrote originally in N1450 and as
a result appear to not understand seemingly simple points. Sorry. :-)

> IMO, this is a contradiction.

Yes, I agree.

Bo Persson

unread,
Aug 22, 2007, 12:58:31 PM8/22/07
to
Peter Dimov wrote:
:: On Aug 21, 5:40 am, Daniel Krügler <daniel.krueg...@googlemail.com>

:: wrote:
::
::: ... but get() says it will return 0 if empty.
::
:: Ah. I get it now. The get() wording somehow acquired "Returns a
:: null pointer if *this is empty." somewhere between N1450/N1596 and
:: N1836.
:: I'm relying on my memory about what I wrote originally in N1450
:: and as
:: a result appear to not understand seemingly simple points. Sorry.
:: :-)

Already in N1596 shared_ptr gets a default constructor, which seems to
be the definiton of empty (as the wrod is in italics).

As this constructor has the post condition "use_count() == 0 && get()
== 0", we must assume that an empty shared_ptr returns a null pointer.


You could perhaps remove the italics from "empty", and say that a
default constructed empty pointer has get()==0, while other empty
pointers might not. (Not that I propose this).

Bo Persson


::
::: IMO, this is a contradiction.

Daniel Krügler

unread,
Aug 22, 2007, 2:53:01 PM8/22/07
to
On Aug 22, 8:50 am, Peter Dimov <pdi...@gmail.com> wrote:
> On Aug 21, 5:40 am, Daniel Krügler <daniel.krueg...@googlemail.com>
> > ... but get() says it will return 0 if empty.
>
> Ah. I get it now. The get() wording somehow acquired "Returns a null
> pointer if *this is empty." somewhere between N1450/N1596 and N1836.
> I'm relying on my memory about what I wrote originally in N1450 and as
> a result appear to not understand seemingly simple points. Sorry. :-)
>
> > IMO, this is a contradiction.
>
> Yes, I agree.

Finally the world is a sphere again! I had the impression that there
must have been a misunderstanding between Alberto and you,
because according to my understanding Alberto wanted to point
out exactly the same issue.

I would like to repeat (because you haven't commented my
corresponding request) that I see strong need for a clear definition
of "empty" of a shared pointer, because the word "empty" is usually
associated with "contains nothing". But now we can have the
situation where an "empty" shared pointer can have a stored pointer
that is different from NULL and the "natural" meaning of "empty" is
somewhat misleading. IMO, a proper attribute for use_count() == 0
is "not owning". Any better words?

Greetings from Bremen,

Daniel

David O

unread,
Aug 22, 2007, 4:42:45 PM8/22/07
to
On Aug 22, 1:50 am, Peter Dimov <pdi...@gmail.com> wrote:
> On Aug 21, 5:40 am, Daniel Krügler <daniel.krueg...@googlemail.com>
> wrote:
>
> > ... but get() says it will return 0 if empty.
>
> Ah. I get it now. The get() wording somehow acquired "Returns a null
> pointer if *this is empty." somewhere between N1450/N1596 and N1836.
> I'm relying on my memory about what I wrote originally in N1450 and as
> a result appear to not understand seemingly simple points. Sorry. :-)
>
> > IMO, this is a contradiction.
>
> Yes, I agree.

It would also contradict TR.1, in particular the constructor:

template<class Y> explicit shared_ptr(Y* p);

Requires: p shall be convertible to T*. Y shall be a complete type.
The expression delete p shall be well-formed, shall have well defined
behavior, and shall not throw exceptions.
Effects: Constructs a shared_ptr object that owns the pointer p.
Postconditions: use_count() == 1 && get() == p.

The requirements allow for p==0, giving a "shared ownership of
nothing", but *not* an empty pointer, which is indirectly defined as
the use_count() being 0:

long use_count() const;
Returns: the number of shared_ptr objects, *this included, that
share ownership with *this, or 0 when *this is empty.

On the other hand, the specification of get() saying that an empty
shared_ptr returns a null pointer is not a problem: all empty
shared_ptrs store a null pointer value, but a null get() value does
not imply the shared_ptr is empty. To the implementor, this means that
the default constructor and reset() must (act as if they) nullify the
pointer value, but otherwise take no special action for null pointer
values.

Generally, users do not check the use_count():

{Note: use_count() is not necessarily efficient. Use only for
debugging and testing purposes, not for production code. -end note]

and instead use null-comparisons or, equivalently, operator
unspecified-bool-type(), which work as expected in "normal" usage,
i.e. except where explicitly constructing a shared_ptr from a null
value. Unless this is done deliberately for some sort of resource
management, the only situation that seems likely is:

shared_ptr< T > p( condition ? new X : 0 );
...
if( p )
{
p->do_something();
}

Even in this situation, the code behaves as expected; it may have
slightly degraded performance, because it generates a use-count object
for the null pointer value.

Instead of the note in the aliasing constructor, which implies it
behaves differently to the other constructors, it would be preferable
to note the behavior more generally.

Best Regards,

David O.

Alberto Ganesh Barbati

unread,
Aug 25, 2007, 12:42:56 PM8/25/07
to
Daniel Krügler ha scritto:

>
> I would like to repeat (because you haven't commented my
> corresponding request) that I see strong need for a clear definition
> of "empty" of a shared pointer, because the word "empty" is usually
> associated with "contains nothing". But now we can have the
> situation where an "empty" shared pointer can have a stored pointer
> that is different from NULL and the "natural" meaning of "empty" is
> somewhat misleading. IMO, a proper attribute for use_count() == 0
> is "not owning". Any better words?
>

What about banning the word "empty" entirely? IMHO it is a source of
great confusion and certainly won't help a newbie understand such a
useful new component as shared_ptr. I propose we replace the word
"empty" with "with no ownership" as opposed to "non-empty" which instead
is "with ownership". A lot of things immediately becomes clearer:

1) ownership just means someone eventually has to call the deleter, you
don't need to define "empty" anymore

2) a non-NULL empty shared_ptr is almost an oxymoron, but it's not hard
at all to imagine a non-NULL shared_ptr with no ownership... it's just
the same as a regular pointer

I am willing to write down a formal proposal, if there's any interest.

Just my 2 eurocent,

Ganesh

Joe Gottman

unread,
Aug 26, 2007, 11:11:03 AM8/26/07
to
Alberto Ganesh Barbati wrote:
> Daniel Krügler ha scritto:
>> I would like to repeat (because you haven't commented my
>> corresponding request) that I see strong need for a clear definition
>> of "empty" of a shared pointer, because the word "empty" is usually
>> associated with "contains nothing". But now we can have the
>> situation where an "empty" shared pointer can have a stored pointer
>> that is different from NULL and the "natural" meaning of "empty" is
>> somewhat misleading. IMO, a proper attribute for use_count() == 0
>> is "not owning". Any better words?
>>
>
> What about banning the word "empty" entirely? IMHO it is a source of
> great confusion and certainly won't help a newbie understand such a
> useful new component as shared_ptr. I propose we replace the word
> "empty" with "with no ownership" as opposed to "non-empty" which instead
> is "with ownership". A lot of things immediately becomes clearer:
>
> 1) ownership just means someone eventually has to call the deleter, you
> don't need to define "empty" anymore
>
> 2) a non-NULL empty shared_ptr is almost an oxymoron, but it's not hard
> at all to imagine a non-NULL shared_ptr with no ownership... it's just
> the same as a regular pointer
>
> I am willing to write down a formal proposal, if there's any interest.

Actually, I think we would be better off using the terms "empty" and
"with no ownership" to mean two different things. As you suggested,
"with no ownership" should mean that use_count() == 0. We can then
define "empty" to mean use_count() == 0 and get() == 0, so that empty
implies without ownership but not vice-versa. Then we can still
specify that certain functions return empty shared_ptr's and mean what
we want to mean, and we can also explain how to use the new aliasing
constructor to create a shared_ptr that has no ownership but is not empty.

Joe Gottman

Daniel Krügler

unread,
Aug 26, 2007, 4:59:59 PM8/26/07
to
On 25 Aug., 18:42, AlbertoBarb...@libero.it (Alberto Ganesh Barbati)
wrote:

> What about banning the word "empty" entirely? IMHO it is a source of
> great confusion and certainly won't help a newbie understand such a
> useful new component as shared_ptr. I propose we replace the word
> "empty" with "with no ownership" as opposed to "non-empty" which instead
> is "with ownership". A lot of things immediately becomes clearer:
>
> 1) ownership just means someone eventually has to call the deleter, you
> don't need to define "empty" anymore
>
> 2) a non-NULL empty shared_ptr is almost an oxymoron, but it's not hard
> at all to imagine a non-NULL shared_ptr with no ownership... it's just
> the same as a regular pointer

<hefty nodding> I absolutely support your proposal, as it is in line
with
my own thoughts. Interestingly neither unique_ptr nor auto_ptr use
the term "empty" and the fact that weak_ptr simply equals "empty"
with "use_count() == 0" emphasizes the problem. One might consider
to reuse the function name "weak_ptr::expired()" here, but this term
characterizes more the position of weak_ptr in this affair than the
more neutral term "ownership" as you and I have both proposed.
What about an observer bool shared_ptr::owns() const?

Greetings,

Daniel

Alberto Ganesh Barbati

unread,
Aug 26, 2007, 4:00:12 PM8/26/07
to
Alberto Ganesh Barbati ha scritto:

>
> I am willing to write down a formal proposal, if there's any interest.
>

I couldn't resist. I wrote down the proposal. You can download it from
<http://barbati.net/c++/shared_ptr.pdf>. The paper is mainly proposing a
change in the terminology, but also fixes the definition of get() and
clarifies the value of the stored pointer in a few places (those pointed
out by Joe Gottman in the OP, for example).

Any feedback is appreciated. If there is any quantity of consensus about
the proposal, I would kindly ask Peter Dimov and Beman Dawes to consider
incorporating this proposal into a further revision of N2351, rather
than formally submit a completely new paper.

Alberto Ganesh Barbati

unread,
Aug 26, 2007, 5:34:46 PM8/26/07
to
Joe Gottman ha scritto:

> Actually, I think we would be better off using the terms "empty" and
> "with no ownership" to mean two different things. As you suggested,
> "with no ownership" should mean that use_count() == 0. We can then
> define "empty" to mean use_count() == 0 and get() == 0, so that empty
> implies without ownership but not vice-versa. Then we can still
> specify that certain functions return empty shared_ptr's and mean what
> we want to mean, and we can also explain how to use the new aliasing
> constructor to create a shared_ptr that has no ownership but is not empty.
>

I don't think there's much value in having both concepts. In fact, after
re-reading the current draft, I realized that when it speaks of an empty
shared_ptr, there's always an obvious choice of the value of get(). In
the proposal I prepared (see my other post), whenever such obvious value
is 0 I replaced "empty shared_ptr" with "shared_ptr<T>()"; in all other
cases, I specified that the pointer has no ownership and directly or
indirectly specified the value of get(). It's more simple and effective,
IMHO, and does not introduce an additional term.

In fact, in order to be more concise, I used in a few places the
expression "p has the same ownership as r" with the meaning "if r has
ownership, p shares ownership with r, otherwise p has no ownership"
(same as the equivalence induced by operator<). For example, the
definition of the copy constructor:

"If r is empty, constructs an empty shared_ptr object; otherwise,

constructs a shared_ptr object that shares ownership with r."

becomes:

"Constructs a shared_ptr object that has the same ownership as r and
stores a copy of the pointer stored in r."

Note how the value of the stored pointer is always specified, as it
should (and wasn't).

Ganesh

Daniel Krügler

unread,
Aug 26, 2007, 6:35:09 PM8/26/07
to
On 26 Aug., 22:00, AlbertoBarb...@libero.it (Alberto Ganesh Barbati)
wrote:

> Alberto Ganesh Barbati ha scritto:
> I couldn't resist. I wrote down the proposal. You can download it from
> <http://barbati.net/c++/shared_ptr.pdf>. The paper is mainly proposing a
> change in the terminology, but also fixes the definition of get() and
> clarifies the value of the stored pointer in a few places (those pointed
> out by Joe Gottman in the OP, for example).
>
> Any feedback is appreciated.

Nice stuff, Alberto!
Here my personal 2 euro cents after a first reading:

1) Minor: Actually I prefer the shorter formula

"use_count() == r.use_count()"

compared to your proposed one

"*this and r share ownership or both have no ownership"

because it's simple, short and right (I hope).

2) Major: I propose changes of your proposed changes of
[util.smartptr.shared.cast]/2 and [util.smartptr.shared.cast]/10,
which are nearer to the original intend (which makes a sharing
of NULL pointers unneccessary):

I propose for [util.smartptr.shared.cast]/2:

Returns:
- If r.get() is a non-null pointer, shared_ptr<T>(r,
static_cast<T*>(r.get()));
- Otherwise, shared_ptr<T>().

and for [util.smartptr.shared.cast]/10:

- If r.get() is a non-null pointer, shared_ptr<T>(r,
const_cast<T*>(r.get()));
- Otherwise, shared_ptr<T>().

This is also more symmetric to the dynamic_cast case.

3) I strongly support your proposed has_ownership() function!

Greetings from Bremen,

Daniel

Alberto Ganesh Barbati

unread,
Aug 26, 2007, 10:05:17 PM8/26/07
to
Daniel Krügler ha scritto:

> On 26 Aug., 22:00, AlbertoBarb...@libero.it (Alberto Ganesh Barbati)
> wrote:
>> Alberto Ganesh Barbati ha scritto:
>> I couldn't resist. I wrote down the proposal. You can download it from
>> <http://barbati.net/c++/shared_ptr.pdf>. The paper is mainly proposing a
>> change in the terminology, but also fixes the definition of get() and
>> clarifies the value of the stored pointer in a few places (those pointed
>> out by Joe Gottman in the OP, for example).
>>
>> Any feedback is appreciated.
>
> Nice stuff, Alberto!

Thanks!

> Here my personal 2 euro cents after a first reading:
>
> 1) Minor: Actually I prefer the shorter formula
>
> "use_count() == r.use_count()"
>
> compared to your proposed one
>
> "*this and r share ownership or both have no ownership"
>
> because it's simple, short and right (I hope).

I'm not adamant about that. I proposed that change mainly for two reasons:

1) it was an attempt to clarify the deliberately vague expression "has
the same ownership" I put in the "effects" clause

2) use_count() == r.use_count() doesn't actually says anything about the
ownership of *this and r

I agree that neither reasons are very strong and both could be dropped
in case we found a better expression for "has the same ownership" or we
add an explicit definition of that.

> 2) Major: I propose changes of your proposed changes of
> [util.smartptr.shared.cast]/2 and [util.smartptr.shared.cast]/10,
> which are nearer to the original intend (which makes a sharing
> of NULL pointers unneccessary):
>
> I propose for [util.smartptr.shared.cast]/2:
>
> Returns:
> - If r.get() is a non-null pointer, shared_ptr<T>(r,
> static_cast<T*>(r.get()));
> - Otherwise, shared_ptr<T>().
>
> and for [util.smartptr.shared.cast]/10:
>
> - If r.get() is a non-null pointer, shared_ptr<T>(r,
> const_cast<T*>(r.get()));
> - Otherwise, shared_ptr<T>().
>
> This is also more symmetric to the dynamic_cast case.

Your proposal makes a lot of sense, really. Frankly, I am torn between
the two alternatives and unable to choose (maybe because it's too late
at night... ;) Let's here some more feedback about this point.

> 3) I strongly support your proposed has_ownership() function!

:-)

Ganesh

Peter Dimov

unread,
Aug 27, 2007, 1:18:13 PM8/27/07
to
On Aug 26, 11:00 pm, AlbertoBarb...@libero.it (Alberto Ganesh Barbati)
wrote:

> I couldn't resist. I wrote down the proposal. You can download it from


> <http://barbati.net/c++/shared_ptr.pdf>. The paper is mainly proposing a
> change in the terminology, but also fixes the definition of get() and
> clarifies the value of the stored pointer in a few places (those pointed
> out by Joe Gottman in the OP, for example).

I like your suggestion that shared_ptr<T>() can be used in places
where we currently have to say "an empty shared_ptr that stores NULL".
I also like the shortened specification of the casts in terms of the
aliasing constructor.

The motivation for the use_count() == r.use_count() postcondition, and
the general preference of C++ expressions instead of English in the
postcondition clauses, is that it's easy to turn it into a conformance
test. There is typically no need to repeat "*this and r share
ownership" in the postcondition clause when the effects clause has
already stated the same thing. A C++ way to express "p shares
ownership with q" is !(p < q) && !(q < p) and one could add that as a
(redundant) postcondition, but p.use_count() == q.use_count() is still
useful since it tests the same property in a different way and can
find bugs the other test may miss.

David O

unread,
Aug 27, 2007, 2:45:46 PM8/27/07
to
On Aug 26, 3:00 pm, AlbertoBarb...@libero.it (Alberto Ganesh Barbati)
wrote:

> Alberto Ganesh Barbati ha scritto:
>
>
>
> > I am willing to write down a formal proposal, if there's any interest.
>
> I couldn't resist. I wrote down the proposal. You can download it from
> <http://barbati.net/c++/shared_ptr.pdf>. The paper is mainly proposing a
> change in the terminology, but also fixes the definition of get() and
> clarifies the value of the stored pointer in a few places (those pointed
> out by Joe Gottman in the OP, for example).
>
> Any feedback is appreciated. If there is any quantity of consensus about
> the proposal, I would kindly ask Peter Dimov and Beman Dawes to consider
> incorporating this proposal into a further revision of N2351, rather
> than formally submit a completely new paper.
>
> Ganesh
>
Many thanks, Ganesh!

"As a degenerate case, a shared_ptr object can have no ownership, such
an object implements the semantic of a regular pointer."

It might be worth documenting (as a note) how the degenerate cases can
occur:

1) Pointer to object with no ownership.
shared_ptr<T> p( shared_ptr<T>(), new T() );
// p has the semantics of a regular pointer.

A possible real-life usage:

extern T t;
void f( shared_ptr<T> );
{
shared_ptr<T> p( shared_ptr<T>(), &t );
// p is pseudo-regular pointer to 't'.
f( p ); // Operate on 't' safely.
// p's destructor does nothing.
}

I have used shared_ptrs with null-destructors to get a similar effect.

2) Null pointer with ownership.

shared_ptr<Huge> q( new (nothrow) Huge() );
// q is unique(), even if it is null because allocation fails.

Should be unsurprising as the nothrow is explicit.

shared_ptr<T> p( flag ? new T() : static_cast<T *>(null_ptr) );
// p is unique(), even if it is null.

This is unchanged behavior, and works as expected, assuming p is
converted to bool (or compared with null) to check its validity.

Best Regards,

David O.

Greg Herlihy

unread,
Aug 27, 2007, 11:35:52 PM8/27/07
to


On 8/26/07 2:34 PM, in article 3ChAi.97822$U01.8...@twister1.libero.it,


"Alberto Ganesh Barbati" <Alberto...@libero.it> wrote:

> Joe Gottman ha scritto:
>> Actually, I think we would be better off using the terms "empty" and
>> "with no ownership" to mean two different things. As you suggested,
>> "with no ownership" should mean that use_count() == 0. We can then
>> define "empty" to mean use_count() == 0 and get() == 0, so that empty
>> implies without ownership but not vice-versa. Then we can still
>> specify that certain functions return empty shared_ptr's and mean what
>> we want to mean, and we can also explain how to use the new aliasing
>> constructor to create a shared_ptr that has no ownership but is not empty.
>>
>
> I don't think there's much value in having both concepts. In fact, after
> re-reading the current draft, I realized that when it speaks of an empty
> shared_ptr, there's always an obvious choice of the value of get().

No, the current draft is clear: an empty shared_ptr must return NULL when
its get() method is called. After all, an empty shared_ptr stores no pointer
that get() could otherwise return. So every mention of an "empty" shared_ptr
in the current draft - should be taken to mean a shared_ptr with a
use_count() of 0 and a get() that returns NULL - every mention that is, save
for one.

So Joe's point above is valid. In fact, the only mistake that has been
brought to light in this entire thread is found in the aliasing
constructor's "Note". The Note uses "empty" when "use_count()==0" is
intended. So any other change to the section on shared_ptr's might be
justified as an improvement or clarification - but could not be justified as
a needed correction.



> In fact, in order to be more concise, I used in a few places the
> expression "p has the same ownership as r" with the meaning "if r has
> ownership, p shares ownership with r, otherwise p has no ownership"
> (same as the equivalence induced by operator<). For example, the
> definition of the copy constructor:
>
> "If r is empty, constructs an empty shared_ptr object; otherwise,
> constructs a shared_ptr object that shares ownership with r."
>
> becomes:
>
> "Constructs a shared_ptr object that has the same ownership as r and
> stores a copy of the pointer stored in r."

But if r is empty, then there is no pointer stored in r. The original text
carefully distinguished what happens in the case that r has a stored pointer
from the case when r has no stored pointer (that is, when r is empty). The
proposed wording conflates the two cases and implies that r always stores a
pointer.


> Note how the value of the stored pointer is always specified, as it
> should (and wasn't).

It makes little sense to specify the value of a shared_ptr's stored pointer
in the case that no stored pointer exists. So the revised text - when
applied to an empty shared_ptr - becomes confusing: how can a pointer that
does not exist have a specified value?

To make matters worse, the revisions are not always correct. For example,
the current draft describes shared_ptr equivalence in this way:

"Two shared_ptr instances are equivalent if and only if they share
ownership or are both empty"

With the proposed revision, shared_ptr equivalence becomes:

"Two shared_ptr instances are equivalent if and only if they share
ownership or both have no ownership."

Now consider two shared_ptrs, a and b, initialized like so:

using std::shared_ptr;

shared_ptr<int> e;
int i = 5;
int j = 7;

shared_ptr<int> a( e, &i );
shared_ptr<int> b( e, &j );

Are a and b equivalent shared_ptrs?

According to the current text, a and b are -not- equivalent (because they
neither share ownership nor are they both empty).

According to the revised text, a and b -are- equivalent (because both have
no ownership).

And changing the meaning of shared_ptr "equivalence" would not be a small
change to make.

Greg

Joe Gottman

unread,
Aug 27, 2007, 10:35:03 PM8/27/07
to
Alberto Ganesh Barbati wrote:
> Alberto Ganesh Barbati ha scritto:
>> I am willing to write down a formal proposal, if there's any interest.
>>
>
> I couldn't resist. I wrote down the proposal. You can download it from
> <http://barbati.net/c++/shared_ptr.pdf>. The paper is mainly proposing a
> change in the terminology, but also fixes the definition of get() and
> clarifies the value of the stored pointer in a few places (those pointed
> out by Joe Gottman in the OP, for example).
>
> Any feedback is appreciated. If there is any quantity of consensus about
> the proposal, I would kindly ask Peter Dimov and Beman Dawes to consider
> incorporating this proposal into a further revision of N2351, rather
> than formally submit a completely new paper.

This looks very nice. The one thing I would change is the effect of
the move constructor on the input shared_ptr:

shared_ptr(shared_ptr&& r);
template<class Y> shared_ptr(shared_ptr<Y>&& r);


I'm not sure how to word the standardeeze, but I would prefer if move
constructor made r.get() return 0. I have two main reasons for this:
1) Safety. While it's highly likely that the newly constructed
shared_ptr will outlive r, it's not guaranteed. If the newly
constructed shared_ptr does die first and it deletes it's owned pointer,
any access to r, even comparing it to NULL, can result in an access
violation and a very hard-to-find bug.

2) If the move constructor sets r.get() to NULL, it becomes possible
to tell whether r has been moved from. This would be useful if we have
a function foo that takes a shared_ptr by rvalue reference and might or
might not move from it. For instance foo might be one of several
potential consumers of the shared_ptr.

void foo(shared_ptr<int> &&r);

Then we can have code that looks like the following:

shared_ptr<int> r = make_shared<int>(1);
foo(move(r));
if (r) { //
bar(move(r)); foo didn't want it. Try the next consumer
} else {
return; // foo consumed r, so we are done.
}


Joe Gottman

Alberto Ganesh Barbati

unread,
Aug 28, 2007, 5:45:10 PM8/28/07
to
Greg Herlihy ha scritto:

> On 8/26/07 2:34 PM, in article 3ChAi.97822$U01.8...@twister1.libero.it,
> "Alberto Ganesh Barbati" <Alberto...@libero.it> wrote:
>
>
> So Joe's point above is valid. In fact, the only mistake that has been
> brought to light in this entire thread is found in the aliasing
> constructor's "Note". The Note uses "empty" when "use_count()==0" is
> intended. So any other change to the section on shared_ptr's might be
> justified as an improvement or clarification - but could not be justified as
> a needed correction.

I never called my proposal a "needed correction". In fact I titled it
"Improving Improved shared_ptr", so, although I take the opportunity to
make a few corrections, I think about it as an improvement.

>
>> In fact, in order to be more concise, I used in a few places the
>> expression "p has the same ownership as r" with the meaning "if r has
>> ownership, p shares ownership with r, otherwise p has no ownership"
>> (same as the equivalence induced by operator<). For example, the
>> definition of the copy constructor:
>>
>> "If r is empty, constructs an empty shared_ptr object; otherwise,
>> constructs a shared_ptr object that shares ownership with r."
>>
>> becomes:
>>
>> "Constructs a shared_ptr object that has the same ownership as r and
>> stores a copy of the pointer stored in r."
>
> But if r is empty, then there is no pointer stored in r. The original text
> carefully distinguished what happens in the case that r has a stored pointer
> from the case when r has no stored pointer (that is, when r is empty). The
> proposed wording conflates the two cases and implies that r always stores a
> pointer.

Exactly. In my proposal, a shared_ptr shall always store a pointer.
That's what the new aliasing constructor requires or at least that's the
intent of it, according to my interpretation of what Peter Dimov wrote
in a previous post:

"An empty shared_ptr is almost as efficient as a raw pointer; it
doesn't allocate on construction, and it doesn't maintain a reference
count on copy/destroy. This is what makes it useful in certain
scenarios and circles. :-)"

That's why I banned the term "empty", because it is misleading, once we
accept that a shared_ptr always stores something.

I understand that this is a departure from the original text where an
empty shared_ptr did not actually have a stored pointer. The fact is
that the strict concept of emptiness "empty implies get() == 0" can't
coexist with the new aliasing constructor that allows empty shared_ptrs
with get()!=0. You can't have both! That's why I believe the current
wording should be improved, by abandoning the strict concept of
emptiness in favor of the relaxed concept of non-ownership.

>
> It makes little sense to specify the value of a shared_ptr's stored pointer
> in the case that no stored pointer exists. So the revised text - when
> applied to an empty shared_ptr - becomes confusing: how can a pointer that
> does not exist have a specified value?

But there's no concept of emptiness in my proposal! It's clearly stated
that a shared_ptr with no ownership behaves like a regular pointer
(compare this with Peter's words above). Frankly I don't understand
where *in my proposal* did you get the idea that no stored pointer exist
in such case.

>
> To make matters worse, the revisions are not always correct. For example,
> the current draft describes shared_ptr equivalence in this way:
>
> "Two shared_ptr instances are equivalent if and only if they share
> ownership or are both empty"
>
> With the proposed revision, shared_ptr equivalence becomes:
>
> "Two shared_ptr instances are equivalent if and only if they share
> ownership or both have no ownership."
>
> Now consider two shared_ptrs, a and b, initialized like so:
>
> using std::shared_ptr;
>
> shared_ptr<int> e;
> int i = 5;
> int j = 7;
>
> shared_ptr<int> a( e, &i );
> shared_ptr<int> b( e, &j );
>
> Are a and b equivalent shared_ptrs?
>
> According to the current text, a and b are -not- equivalent (because they
> neither share ownership nor are they both empty).
>

In the current wording, the effect of the aliasing constructor is defined as

"Constructs a shared_ptr instance that stores p and shares ownership
with r."

So what is the effect if r is empty? I believe there is a small defect
in the wording here. Other constructors go to a greater length saying

"If r is empty, constructs an empty shared_ptr object; otherwise,
constructs a shared_ptr object that shares ownership with r.

Why the aliasing constructor did not include the "If r is empty, ..."
part? I believe it is just a mistake and that the intent is that to
construct an empty shared_ptr in that case. In fact the case is
sanctioned by the following note: "this constructor allows creation of
an empty shared_ptr instance with a non-NULL stored pointer." In fact
the reference implementation present in Boost, precisely does that.

So both pointers are indeed empty and therefore they are equivalent.

> According to the revised text, a and b -are- equivalent (because both have
> no ownership).

Exactly. As above.

>
> And changing the meaning of shared_ptr "equivalence" would not be a small
> change to make.
>

It didn't change.

I guess you find my proposal confusing because it's the current wording
which is confusing, IMHO, and you are judging my proposal from that
confused point of view. If you could get rid of the wrong notion of
emptiness I am pretty sure that you would find my proposal pretty
straightforward.

Ganesh

Alberto Ganesh Barbati

unread,
Aug 28, 2007, 11:16:36 PM8/28/07
to
Joe Gottman ha scritto:

>
> This looks very nice. The one thing I would change is the effect of
> the move constructor on the input shared_ptr:
>
> shared_ptr(shared_ptr&& r);
> template<class Y> shared_ptr(shared_ptr<Y>&& r);
>
>
> I'm not sure how to word the standardeeze, but I would prefer if move
> constructor made r.get() return 0.

I acknowledge that idea certainly has value. The current wording did not
specify a value, so I just thought it should be left unchanged.

I am not yet familiar with move constructors, but, from what I
understand, the source of a move should be left in an unspecified state,
only suitable for being destroyed or assigned to. If you access it in
any other way, you do it at your own risk. So maybe the correct wording
is that the value of r.get() after the move shall be unspecified,
possibly adding a note to encourage debug implementations to nullify it.

Curiously, I checked the reference implementation present in the Boost
cvs and actually it stores the null pointer, as you say...

Ganesh

Alberto Ganesh Barbati

unread,
Aug 28, 2007, 10:20:33 PM8/28/07
to
Peter Dimov ha scritto:

>
> The motivation for the use_count() == r.use_count() postcondition, and
> the general preference of C++ expressions instead of English in the
> postcondition clauses, is that it's easy to turn it into a conformance
> test. There is typically no need to repeat "*this and r share
> ownership" in the postcondition clause when the effects clause has
> already stated the same thing. A C++ way to express "p shares
> ownership with q" is !(p < q) && !(q < p) and one could add that as a
> (redundant) postcondition, but p.use_count() == q.use_count() is still
> useful since it tests the same property in a different way and can
> find bugs the other test may miss.
>

I have to admit that I never thought of post-conditions in terms of
conformance tests. Good to know, thanks! However, I still find the
expression use_count() == r.use_count() unsatisfying. The problem is
that the expression may be true even without *this and r sharing
ownership! Suppose your implementation contains a mistake and evaluates
incorrectly the expression shared_ptr<T>(r) as shared_ptr<T>(r.get()).
If, accidentally, r.use_count() == 1 then the post-condition is
satisfied, but the implementation is broken.

While sticking to evaluatable C++ expression, I would prefer being
redundant and use

use_count() == r.use_count() && !(p < q) && !(q < p)

but that's just my opinion, of course.

Ganesh

Reply all
Reply to author
Forward
0 new messages