2011-08-16 23 views
11

que estoy tratando de poner el idioma de copiar y de intercambio en un mixin reutilizable:reutilizar el idioma de copiar y de intercambio

template<typename Derived> 
struct copy_and_swap 
{ 
    Derived& operator=(Derived copy) 
    { 
     Derived* derived = static_cast<Derived*>(this); 
     derived->swap(copy); 
     return *derived; 
    } 
}; 

pretendo que pueda ser mezclado en medio de CRTP:

struct Foo : copy_and_swap<Foo> 
{ 
    Foo() 
    { 
     std::cout << "default\n"; 
    } 

    Foo(const Foo& other) 
    { 
     std::cout << "copy\n"; 
    } 

    void swap(Foo& other) 
    { 
     std::cout << "swap\n"; 
    } 
}; 

sin embargo, una prueba simple muestra que no está funcionando:

Foo x; 
Foo y; 
x = y; 

Este "defecto" sólo se imprime dos veces, ni se imprime "copia" ni "intercambio". ¿Que me estoy perdiendo aqui?

+1

Tal vez el compilador está proporcionando su propia versión de 'operador =' ya que eres falta uno en tu clase 'Foo'? Tal vez tengas que hacer 'Foo :: operator =() {return copy_and_swap();}' (pseudocode)? – RedX

Respuesta

0

Me temo que esta es un área donde es necesaria una macro, debido a las complejas reglas sobre los operadores de copia y asignación generados automáticamente.

No importa lo que haces, estás en cualquiera de los dos casos:

  • usted ha proporcionado (explícitamente) una declaración del operador de asignación, en cuyo caso se espera que proporcionen una definición demasiado
  • No ha proporcionado (explícitamente) una declaración del operador de asignación, en cuyo caso el compilador generará una si las clases base y miembros no estáticos tienen una disponible.

La siguiente pregunta, por lo tanto, es: ¿Vale la pena automatizar dicha escritura?

Copy-And-Swap solo se usa para clases muy específicas. No creo que valga la pena.

1

No puede heredar los operadores de asignación como un caso especial, si la memoria funciona correctamente. Creo que pueden ser explícitamente using 'd in si es necesario.

Además, tenga cuidado con el uso excesivo de copiar y cambiar. Produce resultados no ideales donde el original tiene recursos que podrían reutilizarse para hacer la copia, como los contenedores. La seguridad está garantizada pero el rendimiento óptimo no lo es.

+0

Agregar el 'using copy_and_swap :: operator =;' en 'struct Foo', no corrige la situación. Ver [respuesta de Alexandre C.] (http://stackoverflow.com/questions/7080137/reusing-the-copy-and-swap-idiom/7080212#7080212). –

7

Este:

Derived& operator=(Derived copy) 

no declarar un operador de asignación de copia para la clase base (tiene la firma incorrecta). Por lo tanto, el operador de asignación generado por defecto en Foo no usará este operador.

Recuerde 12,8:

Una asignación de copia operador X-declarado usuario :: operador = es un no estático función miembro no molde de la clase X con exactamente un parámetro de tipo X, X & , const X &, volátil X & o const volátil X &.) [Nota: un operador de asignación sobrecargado debe declararse que tiene un solo parámetro ; ver 13.5.3. ] [Nota: se puede declarar para una clase más de una forma de asignación de copia operador. ] [Nota: si una clase X sólo tiene un operador de asignación copia con un parámetro de tipo X &, una expresión de tipo const X no puede ser asignado a un objeto de tipo X.

EDITAR don 't hacer esto (puede ver por qué?):

que puede hacer:

template<typename Derived> 
struct copy_and_swap 
{ 
    void operator=(const copy_and_swap& copy) 
    { 
     Derived copy(static_cast<const Derived&>(copy)); 
     copy.swap(static_cast<Derived&>(*this)); 
    } 
}; 

pero se pierde el potencial de optimización de copia elisión.

De hecho, esto asignaría el doble de miembros de las clases derivadas: una vez a través del operador de asignación copy_and_swap<Derived>, y una vez a través del operador de asignación generado de la clase derivada.Para corregir la situación, habría que hacer (y no se olvida de hacer):

struct Foo : copy_and_swap<Foo> 
{ 

    Foo& operator=(const Foo& x) 
    { 
     static_cast<copy_and_swap<Foo>&>(*this) = x; 
     return *this; 
    } 

private: 
    // Some stateful members here 
} 

La moraleja de la historia: no escribir una clase CRTP para la copia y el intercambio de idioma.

+0

¿Qué sucede si '' delete' el operador de asignación generado por el compilador para Foo (suponiendo C++ 0x)? –

+0

'= delete' prohíbe llamar a' operator = 'en Foo, lo que es contraproducente para esto. –

+0

¿Está cortando el problema que condujo a su edición? –

0

El compilador genera automáticamente un operador de asignación de copia para Foo, ya que no hay ninguno. Si agrega un

using copy_and_swap<Foo>::operator=; 

a Foo verá un error que le dice acerca de la ambigüedad en g ++.

+0

Intenté esto y no da ninguna ambigüedad. Sin embargo, el operador todavía no se invoca. –

+0

@ André Caron: Lo revisé de nuevo: g ++ no hace msvc. – mmmmmmmm

0

Tal vez podría volver a escribir para que se vea así:

template<class Derived> 
struct CopySwap 
{ 
    Dervied &operator=(Derived const &other) 
    { 
    return AssignImpl(other); 
    } 

    Derived &operator=(Dervied &&other) 
    { 
    return AssignImpl(std::move(other)); 
    } 

private: 
    Derived &AssignImpl(Derived other) 
    { 
    auto self(static_cast<Derived*>(this)); 
    self->swap(other); 
    return *self; 
    } 
}; 

Es probable que todos nos inline y probablemente no habrá más lento que el código original.

0

Esto en realidad no responde a la pregunta (@Alexandre C. already did), pero si se invierte la herencia, que podría hacer que funcione:

template<typename Base> 
struct copy_and_swap : Base 
{ 
    copy_and_swap& operator=(copy_and_swap copy) 
    { 
     swap(copy); 
     return *this; 
    } 
}; 

struct Foo_ 
{ 
    Foo_() 
    { 
     std::cout << "default\n"; 
    } 

    Foo_(const Foo_& other) 
    { 
     std::cout << "copy\n"; 
    } 

    void swap(Foo_& other) 
    { 
     std::cout << "swap\n"; 
    } 
}; 

typedef copy_and_swap<Foo_> Foo; 

int main() 
{ 
    Foo x; 
    Foo y; 
    x = y; 
}