Boost logo

Boost :

From: Peter Dimov (pdimov_at_[hidden])
Date: 2003-10-23 10:57:18


Michael Grundberg wrote:
> Hi,
>
> I'm not sure whether this is supposed to go to this mailing list or to
> the user mailing list, I'm sorry if I posted it to the wrong list. I
> have also searched the mailing list archive and looked through the
> docs but not finding an answer to my question.
>
> Anyway, I'm wondering how it comes that the operator < of
> boost::shared_ptr is comparing the pointers to the shared_counters
> instead of the stored pointers themselves? The docs states that it
> allows shared_ptr to be used as keys in associative containers. But
> wouldn't a less operator defined as comparing the stored pointers also
> allow the shared_ptr be used as keys in associative containers?

Yes, it would, with a slightly different behavior.

A very common use for associatives of shared_ptr is to track objects. You
just create set< shared_ptr<void> > or map< shared_ptr<void>, Data > and use
a shared_ptr<X> as a key to find the associated information; note that this
works regardless of X.

In a typical scenario, both operator< implementations behave identically.
The differences arise when an object has several addresses (usually with
multiple inheritance, but not always) or, as in your case, several
associated counts.

Neither implementation is inherently more correct, but if the default
operator< compared values, you'd have no way of getting the current
behavior, while you can easily write a value-comparing predicate yourself.

You should also consider shared_ptr's relationship with weak_ptr. weak_ptr's
operator< can't compare values, since a weak_ptr can expire anytime, and
then there would be no value to compare (accessing the value of a deleted
pointer is undefined behavior in addition to being unsafe ;-)). You can't
just use 0 as the value since operator< must be stable or the std::set's
invariant will break as its elements start to expire.

For this reason, weak_ptr's operator< compares count pointers, and there is
an additional reason - consistency - for shared_ptr's operator< to match
this behavior.

> The reason I am asking this is that at work we recently switched from
> using another pointer library to only use the smart pointers of boost,
> and then mostly shared_ptr. It's being used amongst other things as an
> interface pointer between libraries; a library wants a resource but
> doesn't care how it's allocated, thus the library takes a shared_ptr to
> the resource as parameter and the user can define whether the resource
> will be dynamically allocated or statically allocated (and shared to
> the library using a shared_ptr with a null_deleter). The problem we have
is
> that the libraries often uses std::set to store these resource
> pointers. Now, if the user has given a library a pointer to a statically
> allocated resource and wants the library to stop using the resouce,
> he will call some function in the library, giving the library a new
shared_ptr
> pointing to the same statically allocated element. The library will
> call std::set::erase, which will not find the pointer since the
> shared_ptr's aren't the same even if the pointers are.
>
> In other words, we do:
>
> // Our resource
> int i;
>
> // Give it to the library
> boost::shared_ptr<int> p1(&i, null_deleter());
> std::set<boost::shared_ptr<int> > s; s.insert(p1);
>
> // A bit later
> boost::shared_ptr<int> p2(&i, null_deleter());
> s.erase(p2);
>
> Which doesn't remove the pointer from s. This is causing us alot of
> bugs, since the compilations succeed but the code is broken.

I see three ways to deal with the problem, one hi-tech and two low-tech. :-)

Hi-tech:

Use something like the following function to create the shared_ptr instances
associated with a resource:

template<class R, R * pr> shared_ptr<R> make_resource_pointer()
{
    static shared_ptr<R> q(pr, null_deleter());
    return q;
}

Low-tech 1:

Add

    static shared_ptr<R> pi(&i, null_deleter());

after every

    int i;

resource and use pi consistently. Or even hide 'int i' someplace safe and
only expose pi to people.

Low-tech 2:

Comment out shared_ptr's operator<, compile, add the necessary predicate to
every std::set pointed to by the compile errors. :-)

> Also, the definition of the less operator for shared_ptr causes
> std::set::find and std::find to operate in different ways, since the
> later uses the == operator instead of <. Was this the intent?

Yes, this is intentional, if not especially elegant. :-)


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