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?

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 Jul 29 '16 at 14:00
  • @Sjoerd: Which requires a large series of using statements. – Nicol Bolas Jul 29 '16 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. – Jerry Coffin Jul 29 '16 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; – Jerry Coffin Jul 29 '16 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 Jul 31 '16 at 11:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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