2012-07-15 14 views
14

Recibí un error extraño de gcc y no puedo entender por qué. Hice el siguiente código de ejemplo para aclarar el problema. Básicamente, hay una clase definida, para la cual hago que el constructor de copias y el operador de asignación de copias sean privados, para evitar llamarlos accidentalmente.vector :: push_back insiste en usar el constructor de copia aunque se proporciona un constructor de movimiento

#include <vector> 
#include <cstdio> 
using std::vector; 

class branch 
{ 
public: 
    int th; 

private: 
    branch(const branch& other); 
    const branch& operator=(const branch& other); 

public: 

    branch() : th(0) {} 

    branch(branch&& other) 
    { 
    printf("called! other.th=%d\n", other.th); 
    } 

    const branch& operator=(branch&& other) 
    { 
    printf("called! other.th=%d\n", other.th); 
    return (*this); 
    } 

}; 



int main() 
{ 
    vector<branch> v; 
    branch a; 
    v.push_back(std::move(a)); 

    return 0; 
} 

Espero que este código se compile, pero falla con gcc. En realidad, gcc se queja de que "branch :: branch (const branch &) es privada", lo cual, como entiendo, no debería llamarse.

funciona El operador de asignación, ya que si puedo reemplazar el cuerpo de main() con

branch a; 
branch b; 
b = a; 

Será compilar y ejecutar como se esperaba.

¿Es este un comportamiento correcto de gcc? Si es así, ¿qué pasa con el código anterior? Cualquier sugerencia es útil para mí. ¡Gracias!

+0

Funciona para mí con gcc-4.6.1. –

+0

Estaba usando gcc 4.7.1-2. Voy a intentar 4.6.1. ¡Gracias! – BreakDS

+2

Al leer N3242, este código debe permitirse (pero si el constructor de movimientos arroja una excepción, el programa tiene un comportamiento no definido). – aschepler

Respuesta

16

Intenta agregar "noexcept" a la declaración del constructor de movimientos.

No puedo citar el estándar, pero las versiones recientes de gcc parecen requerir que el constructor de copias sea público o que el constructor de movimientos se declare "noexcept". Independientemente del calificador "noexcept", si hace que el constructor de copias sea público, se comportará como espera en el tiempo de ejecución.

+3

Si hace que el constructor de copias sea público, el objeto se copiará, que es claramente lo que intenta evitar. En cualquier caso, hacer que el constructor de movimiento 'noexcept' sea la solución correcta aquí, entonces +1. – ildjarn

+0

Gracias a ambos, noexcept y el constructor de copia pública son correctos. Sin embargo, es un poco contra-intuitivo que el constructor de copia no puede hacerse privado. – BreakDS

+0

@BreakDS: el constructor de copia se puede hacer privado (suponiendo una implementación de biblioteca estándar correcta) si el constructor de movimiento es 'noexcept'. – ildjarn

9

A diferencia de lo sugerido por la respuesta anterior, gcc 4.7 fue incorrecto para rechazar este código, un error que ha sido corrected in gcc 4.8.

El comportamiento completo que sigan los estándares de vector<T>::push_back es:

  • Si sólo hay un constructor de copia y ningún constructor movimiento, push_back copiará su argumento y le dará a la fuerte garantía de seguridad es una excepción. Es decir, si el push_back falla debido a una excepción desencadenada por la reasignación del almacenamiento del vector, el vector original permanecerá inalterado y utilizable. Este es el comportamiento conocido de C++ 98 y también es la razón del desastre que sigue.
  • Si hay un noexcept constructor de movimiento para T, push_backmoverá de su argumento y dará la fuerte garantía de excepción. No hay sorpresas aquí.
  • Si hay un constructor de movimiento que es nonoexcept y también hay un constructor de copia, se push_back copia del objeto y dar a la fuerte garantía de seguridad es una excepción. Esto es inesperado a primera vista. Mientras que push_back podría moverse aquí, eso solo sería posible a costa de sacrificar la fuerte garantía de excepción. Si transfirió el código de C++ 98 a C++ 11 y su tipo es movible, eso cambiaría silenciosamente el comportamiento de las llamadas existentes push_back. Para evitar este inconveniente y mantener la compatibilidad con el código C++ 98, C++ 11 vuelve a la copia más lenta. De esto se trata el comportamiento de gcc 4.7. Pero hay más ...
  • Si hay un constructor movimiento que no es noexcept pero ningún constructor de copia en absoluto - es decir, el elemento sólo se puede mover y no se copian - push_back llevará a cabo el movimiento, pero se no dar el fuerte garantía de seguridad es una excepción. Aquí es donde gcc 4.7 salió mal. En C++ 98 no hay push_back s para los tipos que son movibles pero no se pueden copiar. Así que sacrificar la fuerte excepción de seguridad aquí no rompe el código existente. Es por eso que está permitido y el código original es de hecho legal C++ 11.

Ver cppreference.com en push_back:

Si se produce una excepción, esta función no tiene ningún efecto (fuerte garantía excepción).

Si el constructor de movimientos de T no es noexcept y el constructor de copias no es accesible, el vector usará el constructor de lanzar . Si se lanza, la garantía no se aplica y los efectos son no especificados.

O un poco más complicado §23.3.6.5 del estándar C++ 11 (énfasis añadido por mí):

Causas reasignación si el nuevo tamaño es mayor que la antigua capacidad. Si no se realiza ninguna reasignación, todos los iteradores y referencias anteriores al permanecen válidos. Si el constructor copia una excepción que no sea , move constructor, operador de asignación o operador de asignación de movimiento de T o cualquier operación InputIterator allí no tiene efectos. Si el constructor de movimientos de una que no es CopyInsertable T lanza una excepción, los efectos no se especifican.

O si no te gusta leer, Scott Meyer's Going Native 2013 talk (a partir de 0:30:20 con la parte interesante en aproximadamente 0:42:00).

Cuestiones relacionadas