According to cppreference.com, std::shared_ptr
provides a full set of relative operators (==, !=, <, ...), but the semantics of comparison aren\'t specified. I a
In general, it's not safe to inherit from anything who's destructor is not dynamic. It can be and is done commonly, you just have to be really careful. Instead of inheriting from the pointers, I'd just use composition, especially since the number of members is relatively small. You might be able to make a template class for this
template<class pointer_type>
class relative_ptr {
public:
typedef typename std::pointer_traits<pointer_type>::pointer pointer;
typedef typename std::pointer_traits<pointer_type>::element_type element_type;
relative_ptr():ptr() {}
template<class U>
relative_ptr(U&& u):ptr(std::forward<U>(u)) {}
relative_ptr(relative_ptr<pointer>&& rhs):ptr(std::move(rhs.ptr)) {}
relative_ptr(const relative_ptr<pointer>& rhs):ptr(std::move(rhs.ptr)) {}
void swap (relative_ptr<pointer>& rhs) {ptr.swap(rhs.ptr);}
pointer release() {return ptr.release();}
void reset(pointer p = pointer()) {ptr.reset(p);}
pointer get() const {return ptr.get();}
element_type& operator*() const {return *ptr;}
const pointer_type& operator->() const {return ptr;}
friend bool operator< (const relative_ptr& khs, const relative_ptr& rhs) const
{return std::less<element>(*lhs,*rhs);}
friend bool operator<=(const relative_ptr& khs, const relative_ptr& rhs) const
{return std::less_equal<element>(*lhs,*rhs);}
friend bool operator> (const relative_ptr& khs, const relative_ptr& rhs) const
{return std::greater<element>(*lhs,*rhs);}
friend bool operator>=(const relative_ptr& khs, const relative_ptr& rhs) const
{return std::greater_equal<element>(*lhs,*rhs);}
friend bool operator==(const relative_ptr& khs, const relative_ptr& rhs) const
{return *lhs==*rhs;}
friend bool operator!=(const relative_ptr& khs, const relative_ptr& rhs) const
{return *lhs!=*rhs;}
protected:
pointer_type ptr;
};
Obviously, the simplicity of the wrapper reduces you to the lowest common denominator for the smart pointers, but whatever. They're not exactly complicated, you could make one for each of the smart pointer classes.
I will provide a warning that I don't like the way ==
works, since it may return true for two pointers to different objects. But whatever. I also haven't tested the code, it might fail for certain tasks, like attempting to copy when it contains a unique_ptr.
It's hazardous to inherit from any class that supports assignment and copy-construction, due to the risk of cutting a derived-class instance in half by accidentally assigning it to a base-class variable. This affects most classes, and is pretty much impossible to prevent, thus requiring vigilance on the part the class's users whenever they copy instances around.
Because of this, classes intended to function as bases usually shouldn't support copying. When copying is necessary, they should provide something like Derived* clone() const override
instead.
The problem you are trying to solve is probably best addressed by leaving things as they are and providing custom comparators when working with such pointers.
std::vector<std::shared_ptr<int>> ii = …;
std::sort(begin(ii), end(ii),
[](const std::shared_ptr<int>& a, const std::shared_ptr<int>& b) {
return *a < *b;
});
The first thing, as others have already pointed out is that inheritance is not the way to go. But rather than the convoluted wrapper suggested by the accepted answer, I would do something much simpler: Implement your own comparator for your own types:
namespace myns {
struct mytype {
int value;
};
bool operator<( mytype const& lhs, mytype const& rhs ) {
return lhs.value < rhs.value;
}
bool operator<( std::shared_ptr<mytype> const & lhs, std::shared_ptr<mytype> const & rhs )
{
// Handle the possibility that the pointers might be NULL!!!
// ... then ...
return *lhs < *rhs;
}
}
The magic, which is not really magic at all is Argument Dependent Lookup (aka. Koening Lookup or ADL). When the compiler encounters a function call it will add the namespace of the arguments to lookup. If the objects are the instantiation of a template, then the compiler will also add the namespaces of the types used to instantiate the template. So in:
int main() {
std::shared_ptr<myns::mytype> a, b;
if ( a < b ) { // [1]
std::cout << "less\n";
} else {
std::cout << "more\n";
}
}
In [1], and because a
and b
are objects user defined types (*) ADL will kick in and it will add both std
and myns
to the lookup set. It will then find the standard definition of operator<
for std::shared_ptr
that is:
template<class T, class U>
bool std::operator<(shared_ptr<T> const& a, shared_ptr<U> const& b) noexcept;
And it will also add myns
and add:
bool myns::operator<( mytype const& lhs, mytype const& rhs );
Then, after lookup finishes, overload resolution kicks in, and it will determine that myns::operator<
is a better match than std::operator<
for the call, as it is a perfect match and in that case non-templates take preference. It will then call your own operator<
instead of the standard one.
This becomes a bit more convoluted if your type is actually a template, if it is, drop a comment and I will extend the answer.
(*) This is a slight simplification. Because operator<
can be implemented both as a member function or a free function, the compiler will check inside std::shared_ptr<>
for member operator<
(not present in the standard) and friends. It will also look inside mytype
for friend
functions... and so on. But at the end it will find the right one.