c++ c++11 perfect-forwarding rvo

Operador de asignación unificada de C++ movimiento-semántica



c++11 perfect-forwarding (3)

EDITAR: resuelto ver comentarios - no sé cómo marcar como resuelto sin una respuesta.

Después de ver un video de Channel 9 sobre la semántica Perfect Forwarding / Move en c ++ 0x, fui lo que me llevó a creer que esta era una buena manera de escribir los nuevos operadores de asignación.

#include <string> #include <vector> #include <iostream> struct my_type { my_type(std::string name_) : name(name_) {} my_type(const my_type&)=default; my_type(my_type&& other) { this->swap(other); } my_type &operator=(my_type other) { swap(other); return *this; } void swap(my_type &other) { name.swap(other.name); } private: std::string name; void operator=(const my_type&)=delete; void operator=(my_type&&)=delete; }; int main() { my_type t("hello world"); my_type t1("foo bar"); t=t1; t=std::move(t1); }

Esto debería permitir que tanto r-valores como constantes se le asignen. Construyendo un nuevo objeto con el constructor apropiado y luego intercambiando los contenidos con * esto. Esto me parece acertado ya que no se copia ningún dato más de lo necesario. Y la aritmética de punteros es barata.

Sin embargo mi compilador no está de acuerdo. (g ++ 4.6) Y me sale este error.

copyconsttest.cpp: In function ‘int main()’: copyconsttest.cpp:40:4: error: ambiguous overload for ‘operator=’ in ‘t = t1’ copyconsttest.cpp:40:4: note: candidates are: copyconsttest.cpp:18:11: note: my_type& my_type::operator=(my_type) copyconsttest.cpp:30:11: note: my_type& my_type::operator=(const my_type&) <deleted> copyconsttest.cpp:31:11: note: my_type& my_type::operator=(my_type&&) <near match> copyconsttest.cpp:31:11: note: no known conversion for argument 1 from ‘my_type’ to ‘my_type&&’ copyconsttest.cpp:41:16: error: ambiguous overload for ‘operator=’ in ‘t = std::move [with _Tp = my_type&, typename std::remove_reference< <template-parameter-1-1> >::type = my_type]((* & t1))’ copyconsttest.cpp:41:16: note: candidates are: copyconsttest.cpp:18:11: note: my_type& my_type::operator=(my_type) copyconsttest.cpp:30:11: note: my_type& my_type::operator=(const my_type&) <deleted> copyconsttest.cpp:31:11: note: my_type& my_type::operator=(my_type&&) <deleted>

¿Estoy haciendo algo mal? ¿Es esta mala práctica (no creo que exista una forma de comprobar si se auto asigna)? ¿El compilador simplemente no está listo todavía?

Gracias


¿Leíste atentamente el mensaje de error? Ve dos errores, que tiene múltiples operadores de asignación de copia y múltiples operadores de asignación de movimiento. ¡Y es exactamente correcto!

Los miembros especiales deben especificarse a lo sumo una vez, sin importar si están predeterminados, eliminados, definidos de manera convencional o manejados de forma implícita si se omiten. Tiene dos operadores de asignación de copias (uno toma my_type , el otro toma my_type const & ) y dos operadores de asignación de movimientos (uno toma my_type , el otro toma my_type && ). Tenga en cuenta que el operador de asignación que toma my_type puede manejar las referencias lvalue y rvalue, por lo que actúa como asignación tanto de copia como de movimiento.

La firma de la función de la mayoría de los miembros especiales tiene múltiples formas. Debes escoger uno; no puede usar una inusual y luego eliminar la convencional, ya que será una doble declaración. El compilador utilizará automáticamente un miembro especial de forma inusual y no sintetizará un miembro especial con la firma convencional.

(Observe que en los errores se mencionan tres candidatos. Para cada tipo de asignación, ve el método eliminado apropiado, el método que toma my_type y luego el otro método eliminado como una coincidencia de emergencia cercana).


¿Se supone que debes eliminar esas sobrecargas del operador de asignación? ¿No debería su declaración del operador de asignación ser una plantilla o algo? Realmente no veo cómo se supone que funciona.

Tenga en cuenta que incluso si eso funcionó, al implementar el operador de asignación de movimiento de esa manera, los recursos retenidos por el objeto que se acaba de mover se liberarán en el momento en que finalice su vida útil, y no en el punto de la asignación. Vea aquí para más detalles:

http://cpp-next.com/archive/2009/09/your-next-assignment/


Tenga mucho cuidado con el lenguaje de asignación de copia / intercambio. Puede ser subóptimo, especialmente cuando se aplica sin un análisis cuidadoso. Incluso si necesita una fuerte seguridad de excepción para el operador de asignación, esa funcionalidad se puede obtener de otra manera.

Para tu ejemplo te recomiendo:

struct my_type { my_type(std::string name_) : name(std::move(name_)) {} void swap(my_type &other) { name.swap(other.name); } private: std::string name; };

Esto le dará una copia implícita y moverá la semántica que reenviará a los miembros de la copia y movimiento de std :: string. Y el autor de std :: string sabe mejor cómo realizar esas operaciones.

Si su compilador todavía no admite la generación de movimientos implícitos, pero sí admite miembros especiales predeterminados, puede hacer esto en su lugar:

struct my_type { my_type(std::string name_) : name(std::move(name_)) {} my_type(const mytype&) = default; my_type& operator=(const mytype&) = default; my_type(mytype&&) = default; my_type& operator=(mytype&&) = default; void swap(my_type &other) { name.swap(other.name); } private: std::string name; };

También puede elegir hacer lo anterior si simplemente quiere ser explícito acerca de sus miembros especiales.

Si está tratando con un compilador que aún no admite miembros especiales predeterminados (o miembros de movimientos implícitos), entonces puede proporcionar explícitamente lo que el compilador debería predeterminar cuando sea totalmente compatible con C ++ 11:

struct my_type { my_type(std::string name_) : name(std::move(name_)) {} my_type(const mytype& other) : name(other.name) {} my_type& operator=(const mytype& other) { name = other.name; return *this; } my_type(mytype&& other) : name(std::move(other.name)) {} my_type& operator=(mytype&& other) { name = std::move(other.name); return *this; } void swap(my_type &other) { name.swap(other.name); } private: std::string name; };

Si realmente necesita una fuerte excepción de seguridad para la asignación, diseñe una vez y sea explícito (edite para incluir la sugerencia de Luc Danton):

template <class C> typename std::enable_if < std::is_nothrow_move_assignable<C>::value, C& >::type strong_assign(C& c, C other) { c = std::move(other); return c; } template <class C> typename std::enable_if < !std::is_nothrow_move_assignable<C>::value, C& >::type strong_assign(C& c, C other) { using std::swap; static_assert(std::is_nothrow_swappable_v<C>, // C++17 only "Not safe if you move other into this function"); swap(c, other); return c; }

Ahora sus clientes pueden elegir entre eficiencia (mi tipo :: operador =), o una fuerte excepción de seguridad usando strong_assign .