3

Lets say I want to implement a smart pointer a_ptr which can be compared with other smart pointers.

Then I need to implement all permutations of the comparison operators:

template<class T, class U>
bool operator==(const a_ptr<T>& a, const a_ptr<U>& b)
{
    return a.get() == b.get();
}

template<class T, class U>
bool operator==(const std::shared_ptr<T>& a, const a_ptr<U>& b)
{
    return a.get() == b.get();
}

template<class T, class U>
bool operator==(const a_ptr<T>& a, const std::shared_ptr<U>& b)
{
    return a.get() == b.get();
} 

and etc... for the rest of the operators.

Then maybe I would like to implement another smart pointer b_ptr, which would give me 6 versions for every comparison operator (since I want it to also work with a_ptr), clearly not manageable.

Is there any way to get around this problem?

EDIT:

I should have probably mentioned that I want to create wrappers around smart pointers, in which case this question makes more sense, e.g.

template<typename T>
class a_ptr
{
public:
    const T* get() const {return p_.get();}
private:
    std::shared_ptr<T> p_;
};

template<typename T>
class b_ptr
{
public:
    const T* get() const {return p_.get();}
private:
    a_ptr<T> p_;
};
ronag
  • 49,529
  • 25
  • 126
  • 221

6 Answers6

5

If any of these, except the one comparing two a_ptr, ever hold true, your program has a bug. So, just drop it. Smart pointer is smart, because it's responsible of managing the memory behind it. And you just cannot have two different smart pointers managing one piece of memory.

Consider unique_ptr and shared_ptr. One destroys the pointer as soon as the owning smart pointer is destroyed. Second destroys the pointer only when all owning smart pointers are destroyed. I think it's fairly obvious it would quickly lead to double deletes and other fun stuff.

Cat Plus Plus
  • 125,936
  • 27
  • 200
  • 224
  • If the semantics of the smart pointer are like `shared_ptr`, couldn't you have any number of separate pointers that are essentially equivalent? – bobbymcr Jan 02 '12 at 18:20
  • 1
    @bobby What happens when the last `shared_ptr` is gone? You're left with a bunch of `a_ptr`s sharing what was deleted by the last `shared_ptr`. You can make them have the same semantics, but what you really need is to have them *share the same memory and bookkeeping data*. – R. Martinho Fernandes Jan 02 '12 at 18:21
  • @bobbymcr: You'd have to poke into `shared_ptr` and sync reference counters. Just don't try stuff like this. – Cat Plus Plus Jan 02 '12 at 18:22
  • Cat, that's a very good point. But it falls apart when you introduce `weak_ptr`. – Ben Voigt Jan 02 '12 at 18:22
  • @BenVoigt: `weak_ptr` doesn't own the memory. It really is just a complementary piece of `shared_ptr`, not a smart pointer on its own. – Cat Plus Plus Jan 02 '12 at 18:22
  • @Cat: And it's reasonable to compare a `weak_ptr` to a `shared_ptr`, isn't it? Or, `a_ptr` could be a wrapper around `shared_ptr`, that does some extra stuff like logging. That'd be compatible. – Ben Voigt Jan 02 '12 at 18:25
  • To be clear, I agree that what the OP is trying to do is not recommended. I'm just trying to get some clarity on your comment. – bobbymcr Jan 02 '12 at 18:25
  • @Ben `weak_ptr` doesn't even act like a pointer or a smart pointer. It has no `operator*`, no `operator->` and no `get()`. – R. Martinho Fernandes Jan 02 '12 at 18:26
  • @BenVoigt: `std::weak_ptr` is not comparable to anything, even itself, actually. – Cat Plus Plus Jan 02 '12 at 18:27
  • Ok, well, composition is still a valid case that leads to this problem. – Ben Voigt Jan 02 '12 at 18:28
4

Ben Voigt is on the right track -- except of course we don't have concepts.

template<
    typename Lhs
    , typename Rhs
    , typename = typename std::enable_if<
        /* magic */
    >::type
>
bool
operator==(Lhs const& lhs, Rhs const& rhs)
{
    return lhs.get() == rhs.get();
}

This must be in the same namespace of a_ptr and b_ptr for ADL to kick (so if they are in separate namespaces you need one version of each operator for each namespace).

There are several possibilities for the magic trait to work. The one that is conceptually the simplest is to have a trait that is specialized for each pointer type you care about:

template<typename T>
struct is_smart_pointer: std::false_type {};

template<typename T, typename D>
struct is_smart_pointer<std::unique_ptr<T, D>>: std::true_type {};
// and so on...

A more elaborate trait would check that the type supports a get member. Actually, we don't need a trait for that!

template<typename Lhs, typename Rhs>
auto operator==(Lhs const& lhs, Rhs const& rhs)
-> decltype( lhs.get() == rhs.get() )
{
    return lhs.get() == rhs.get();
}

This simple operator will be picked up by ADL, except that it will SFINAE itself out if one of the type doesn't support get, or if the comparison doesn't work.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • I like the approach although it can also be done without relying on C++2011 feature (see my response: std::true_type and std::false_type can easily be implemented with C++2003). – Dietmar Kühl Jan 02 '12 at 18:37
  • I like the last approach, however I'm uncertain whether it is a good idea? It will allow it to work with any type which implement .get(), intended or not. – ronag Jan 02 '12 at 18:43
  • 1
    @ronag This all depends on the trait you want to use. Elaborate type traits + SFINAE is the closest we get to concepts. You could use a trait that detects `get` and checks that the return type is a pointer type for instance. In imaginary C++11 + concepts this would be the equivalent of `template bool operator==(Lhs const&, Rhs const&);` or some such. How lax or how restrictive the SmartPointer concept needs to be depends on your needs (and as you point out, here it's quite lax). – Luc Danton Jan 02 '12 at 18:47
  • Problem with this is that it can match objects which aren't smart pointers, as long as they have a `get()` member. That's why I prefer to have a helper free function in the same namespace. – Ben Voigt Jan 02 '12 at 19:50
  • @BenVoigt Your approach is morally equivalent to the one where a trait is explicitly specialized for each type of interest. – Luc Danton Jan 02 '12 at 20:00
  • @Luc: yes, except that it both provides the trait and the method for extracting the pointer. – Ben Voigt Jan 02 '12 at 20:11
3

How about:

template<typename T> struct you_need_a_pointer_to_do_that{} unwrap_ptr(const T&);

template<typename U> U* unwrap_ptr(U* p) { return p; }

template<typename U> U* unwrap_ptr(const a_ptr<U>& p) { return p.get(); }

template<typename U> U* unwrap_ptr(const unique_ptr<U>& p) { return p.get(); }

...

template<typename T, typename PU> auto operator<(const a_ptr<T>& a, const PU& b) -> decltype(unwrap_ptr(a) < unwrap_ptr(b)) { return unwrap_ptr(a) < unwrap_ptr(b); }

The decltype bit rejects non-pointers due to SFINAE. And the unwrap_ptr helper homogenizes the syntax for pointer access, with complexity only linear in the number of pointer types.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • This seems to work, although without `compares_to_nothing` which purpose I am unsure of. – ronag Jan 02 '12 at 19:07
  • @ronag: It just guarantees that you get an error if `PU` infers as say `double`. And now it's renamed so you get a better error. – Ben Voigt Jan 02 '12 at 19:46
3

You can just tag your smart pointer types and then implement a generic version which relies on at least one of the passed arguments to be tagged:

template <typename T> struct is_my_smartpointer: std::false_type {};
template <typename T> struct is_my_smartpointer<a_ptr<T> >: std::true_type {};

template <typename S, typename T>
typename std::enable_if<is_my_smartpointer<S>::value || is_my_smartpointer<T>::value, bool>::type
operator== (S const& s, T const& t) {
    return s.get() == t.get();
}
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
0

You could inherit your custom smart pointer from an existing smart pointer. This way you won't have to implement any operators at all.
Also as as Cat Plus Plus said. You cannot have two smart pointer managing one piece of memory. So If you're implementing a smart pointer you should make sure that holds true. You should make operator= private so it cannot be copied into another location.

atoMerz
  • 7,534
  • 16
  • 61
  • 101
  • "Favor composition over inheritance". Not everyone agrees, but anyone following that rule will need to implement (forwarding) comparison. – Ben Voigt Jan 02 '12 at 18:27
0

It can be as difficult as writing a sfinae trait for smart pointers. As soon as you get it, the solution is pretty straight forward, you don't need c++11 for this:

#include <memory>
#include <type_traits>
#include <iostream>

namespace my {
template<class T>
class a_ptr
{
   T* t_;
public:
   typedef T element_type;
   element_type* get() const throw() { return t_; }
};

template<class T> class is_smart_ptr; // sfinae test for T::get()?

template<class T, class U>
typename std::enable_if<
   is_smart_ptr<T>::value &&
   is_smart_ptr<U>::value,
   bool
>::type
operator==(const T& a, const U& b)
{
    return a.get() == b.get();
}
}

int main()
{
   my::a_ptr<int> a;
   std::shared_ptr<int> b;
   a == b;
}

And since std::shared_ptr already have operator==, you need to put your comparison operator in the same namespace where your smart pointer type is.

Gene Bushuyev
  • 5,512
  • 20
  • 19