Можно ли наследовать от интеллектуальных указателей C ++ 11 и переопределять относительные операторы?

В соответствии с cppreference.com, std::shared_ptr предоставляет полный набор относительных операторов (==,! =, <, …), но семантика сравнения не указана. Я предполагаю, что они сравнивают исходные необработанные указатели с объектами, на которые ссылаются, и что std :: weak_ptr и std :: unique_ptr делают то же самое.

Для некоторых целей я бы предпочел иметь относительные операторы, которые упорядочивают умные указатели, основываясь на сравнении ссылочных объектов (а не указателей на них). Это уже кое-что, что я делаю много, но с моими «тупыми указателями», которые ведут себя в основном как необработанные указатели, за исключением относительных операторов. Я хотел бы сделать то же самое со стандартными интеллектуальными указателями C ++ 11. Так…

  1. Можно ли наследовать от интеллектуальных указателей C ++ 11 (shared_ptr, weak_ptr и unique_ptr) и переопределить относительные операторы?

  2. Есть ли какие-нибудь хитрые проблемы, на которые мне нужно обратить внимание? Например, есть ли другие методы, которые мне нужно реализовать или использовать using чтобы убедиться, что все работает правильно?

  3. Для лени, есть ли шаблон библиотеки, который сделает это автоматически?

Я надеюсь, что это «конечно, ты можешь сделать это, идиот!» но я немного не уверен, потому что в стандартной библиотеке есть классы std::map по крайней мере) от которого ты не должен наследовать.

17

Решение

В общем, небезопасно наследовать от чего-либо, чей деструктор не является динамическим. Это может быть и делается обычно, просто нужно быть очень осторожным.
Вместо того, чтобы наследовать от указателей, я бы просто использовал композицию, тем более что число членов относительно невелико.
Вы могли бы сделать класс шаблона для этого

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;
};

Очевидно, что простота обертки сводит вас к наименьшему общему знаменателю для умных указателей, но неважно. Они не совсем сложные, вы можете сделать по одному для каждого из классов умных указателей.

Я предоставлю предупреждение, что мне не нравится, как == работает, так как он может возвращать true для двух указателей на разные объекты. Но что угодно. Я также не тестировал код, он может не работать для определенных задач, таких как попытка копирования, когда он содержит unique_ptr.

9

Другие решения

Во-первых, как уже отмечали другие, это то, что наследование — это не путь. Но вместо извилистой обертки, предложенной в принятом ответе, я бы сделал кое-что гораздо проще: реализовать свой собственный компаратор для своих типов:

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;
}
}

магия, что на самом деле совсем не волшебно, так это Argument Dependent Lookup (aka. Koening Lookup или ADL). Когда компилятор встречает вызов функции, он добавляет пространство имен аргументов для поиска. Если объекты являются экземплярами шаблона, то компилятор также добавит пространства имен типов, используемых для создания шаблона. Итак, в:

int main() {
std::shared_ptr<myns::mytype> a, b;
if ( a < b ) {                       // [1]
std::cout << "less\n";
} else {
std::cout << "more\n";
}
}

В [1], а потому a а также b являются объектами пользовательские типы (*) ADL сработает и добавит оба std а также myns к поисковому набору. Затем он найдет стандартное определение operator< за std::shared_ptr то есть:

template<class T, class U>
bool std::operator<(shared_ptr<T> const& a, shared_ptr<U> const& b) noexcept;

И это также добавит myns и добавить:

bool myns::operator<( mytype const& lhs, mytype const& rhs );

Затем, после завершения поиска, разрешение перегрузки запускается, и он будет определять, что myns::operator< это лучший матч, чем std::operator< для вызова, так как он идеально подходит и в этом случае предпочтение отдается шаблонам. Затем он будет называть ваш собственный operator< вместо стандартного.

Это становится немного более запутанным, если ваш тип на самом деле является шаблоном, если это так, оставьте комментарий, и я расширю ответ.


(*) Это небольшое упрощение. Так как operator< может быть реализован как функция-член или свободная функция, компилятор будет проверять внутри std::shared_ptr<> для участника operator< (нет в стандарте) и друзья. Также заглянет внутрь mytype за friend функции … и так далее. Но в конце он найдет правильный.

11

Опасно наследовать от любого класса, который поддерживает присваивание и конструирование копирования, из-за риска разрезания экземпляра производного класса пополам, случайно назначив его переменной базового класса. Это влияет на большинство классов и практически невозможно предотвратить, поэтому требуется бдительность со стороны пользователей класса, когда они копируют экземпляры вокруг.

Из-за этого классы, предназначенные для функционирования в качестве баз, обычно не должны поддерживать копирование. Когда копирование необходимо, они должны предоставить что-то вроде Derived* clone() const override вместо.

Вероятно, проблему, которую вы пытаетесь решить, лучше всего решить, оставив вещи такими, какие они есть, и предоставив пользовательские компараторы при работе с такими указателями.

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;
});
4
По вопросам рекламы [email protected]