Подтвердить что ты не робот

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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 для двух указателей на разные объекты. Но что угодно. Я также не тестировал код, он может не работать для определенных задач, например, при попытке копирования, когда он содержит уникальный_ptr.

Ответ 2

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

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

Магия, которая на самом деле не волшебна, - это зависящий от аргумента поиск (например, 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... и так далее. Но в конце он найдет правильный.

Ответ 3

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

Из-за этого классы, предназначенные для работы в качестве баз, обычно не должны поддерживать копирование. Когда копирование необходимо, они должны предоставить что-то вроде 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;
          });