overload operator assignment c++ inheritance c++11 operator-overloading smart-pointers

operator - ¿Está bien heredar de los punteros inteligentes de C++ 11 y anular los operadores relativos?



overload operator c++ (3)

En general, no es seguro heredar de un destructor que no sea dinámico. Puede ser y se hace comúnmente, solo hay que tener mucho cuidado. En lugar de heredar de los punteros, solo uso la composición, especialmente porque el número de miembros es relativamente pequeño. Usted podría ser capaz de hacer una clase de plantilla para esto

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

Obviamente, la simplicidad de la envoltura lo reduce al denominador común más bajo para los punteros inteligentes, pero lo que sea. No son exactamente complicados, podría hacer uno para cada una de las clases de puntero inteligente.

Enviaré una advertencia de que no me gusta la forma en que funciona == , ya que puede devolver verdadero para dos punteros a diferentes objetos. Pero lo que sea. Tampoco he probado el código, puede fallar para ciertas tareas, como intentar copiar cuando contiene un unique_ptr.

Según cppreference.com , std::shared_ptr proporciona un conjunto completo de operadores relativos (== std::shared_ptr =, <, ...), pero no se especifica la semántica de comparación. Supongo que comparan los punteros en bruto subyacentes con los objetos a los que se hace referencia, y que std :: weak_ptr y std :: unique_ptr hacen lo mismo.

Para algunos propósitos, preferiría tener operadores relativos que ordenen los punteros inteligentes basados ​​en la comparación de los objetos referenciados (en lugar de los punteros a ellos). Esto ya es algo que hago mucho, pero con mis propios "punteros tontos" que se comportan principalmente como punteros sin procesar, excepto por los operadores relativos. Me gustaría hacer lo mismo con los punteros inteligentes estándar de C ++ 11 también. Asi que...

  1. ¿Está bien heredar de los punteros inteligentes de C ++ 11 (shared_ptr, weak_ptr y unique_ptr) y anular los operadores relativos?

  2. ¿Hay algún problema que deba vigilar? Por ejemplo, ¿hay otros métodos que necesito implementar o usar para asegurar que las cosas funcionen correctamente?

  3. Para lo último en pereza, ¿hay una plantilla de biblioteca disponible que lo haga por mí automáticamente?

Espero que esto sea un "por supuesto que puedes hacer eso, idiota!" tipo de cosas, pero no estoy seguro porque hay algunas clases en la biblioteca estándar (contenedores como std::map al menos) de las que no se debe heredar.


Es peligroso heredar de cualquier clase que admita asignación y construcción de copias, debido al riesgo de cortar una instancia de clase derivada a la mitad al asignarla accidentalmente a una variable de clase base. Esto afecta a la mayoría de las clases, y es prácticamente imposible de prevenir, por lo que requiere vigilancia por parte de los usuarios de la clase cada vez que copian instancias.

Debido a esto, las clases destinadas a funcionar como bases generalmente no deberían admitir la copia. Cuando es necesario copiar, deberían proporcionar algo como Derived* clone() const override lugar.

El problema que está tratando de resolver probablemente se resuelva mejor dejando las cosas como están y proporcionando comparadores personalizados cuando trabaje con dichos indicadores.

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


Lo primero, como ya han dicho otros, es que la herencia no es el camino a seguir. Pero en lugar de la envoltura complicada sugerida por la respuesta aceptada, haría algo mucho más simple: Implementar su propio comparador para sus propios tipos:

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

La magia , que no es realmente mágica, es la búsqueda dependiente de argumentos (también conocida como búsqueda de Koening o ADL). Cuando el compilador encuentra una llamada a una función, agregará el espacio de nombres de los argumentos a buscar. Si los objetos son la creación de instancias de una plantilla, el compilador también agregará los espacios de nombres de los tipos utilizados para instanciar la plantilla. Así que en

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

En [1], y porque a y b son objetos tipos definidos por el usuario (*) ADL se activará y agregará tanto std como myns al conjunto de búsqueda. Luego encontrará la definición estándar de operator< para std::shared_ptr que es:

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

Y también agregará myns y agregará:

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

Luego, una vez que finaliza la búsqueda, se activa la resolución de sobrecarga y se determinará que myns::operator< es una mejor coincidencia que std::operator< para la llamada, ya que es una coincidencia perfecta y en ese caso las no plantillas tienen preferencia. . Entonces llamará a su propio operator< lugar del estándar.

Esto se vuelve un poco más complicado si su tipo es en realidad una plantilla, si lo es, deje un comentario y extenderé la respuesta.

(*) Esta es una ligera simplificación. Debido a que el operator< puede implementarse como una función miembro o como una función libre, el compilador verificará dentro de std::shared_ptr<> para el operator< miembro operator< (no está presente en el estándar) y amigos. También buscará en mytype funciones de friend ... y así sucesivamente. Pero al final encontrará la correcta.