7

I would like to define a shared pointer whose operator== compares the pointed to values instead of the pointers, something like this:

template <typename T>
struct deref_shared_ptr: std::shared_ptr<T> {
    bool operator==(const deref_shared_ptr rhs) const { 
        return (**this == *rhs);
    }
};

This type has two potential problems:

  • It derives from a standard library type.

  • It violates the Liskov substitution principle, since it changes the semantics of operator==.

However, I am thinking that neither of the two is a problem unless one uses the base type pointer/reference to refer to the derived type. In particular, these problems might never occur in template code. For shared pointers the situation is even better: shared pointers are stored and passed by value.

Is my logic correct? Are there still good reasons to avoid the above definition in favor of composition?

1 Answer 1

12

I won't say your logic is definitively wrong, but it gives me a queasy feeling -- e.g. what happens if someone accidentally uses it as a shared_ptr anyway?

In this sort of situation I make my own class that contains the shared_ptr. E.g.

class CustomPtr {
    shared_ptr<XType> m_xp;
public:
    ... some stuff ....
    operator bool()const{return (bool)m_xp;}
    void reset() {m_xp.reset();}

    Transport *operator->(){return m_xp.get();}
    const Transport *operator->()const{return m_xp.get();}
    ... more stuff ....
};

I have to proxy or otherwise wrap any interface that I want to use -- but I consider this an advantage. I generally only use a handful of access methods, and I like to be confident that no-one is using unexpected methods.

10
  • 3
    Private inheritance combined with using to make selected members visible has the same effect.
    – Sjoerd
    Commented Jul 29, 2016 at 14:00
  • @Sjoerd: Which requires a large series of using statements. Commented Jul 29, 2016 at 17:40
  • 2
    @NicolBolas: It (potentially) does, but there are still a couple of advantages. First, it's half (or so) the syntactic overhead, and second, even a compiler that sets new records for lousy optimization still isn't going to turn a using declaration into an extra level of function calls. Commented Jul 29, 2016 at 21:39
  • 1
    @NicolBolas: Yup, no argument from me on that. It'd also be nice (at times) to be able to say (in essence) using everything but x, y, z; Commented Jul 29, 2016 at 22:10
  • 1
    @AlwaysLearning First of all, I wouldn't call it std::make_shared as it isn't a shared pointer - it's a derefed shared pointer. So it should be named differently, e.g. foo::make_deref_shared or foo::MakeCustomPtr. To answer your question: declare this template a friend, so it has full knowledge of the private inheritance, and no casts will be needed. (Exact details are left as an exercise to the reader)
    – Sjoerd
    Commented Jul 31, 2016 at 11:22

Not the answer you're looking for? Browse other questions tagged or ask your own question.