¿Reutilizando un contenedor movido?

84

¿Cuál es la forma correcta de reutilizar un contenedor movido?

std::vector<int> container;
container.push_back(1);
auto container2 = std::move(container);

// ver1: Do nothing
//container2.clear(); // ver2: "Reset"
container = std::vector<int>() // ver3: Reinitialize

container.push_back(2);
assert(container.size() == 1 && container.front() == 2);

Por lo que he leído en el borrador estándar C ++ 0x; ver3 parece ser la forma correcta, ya que un objeto después del movimiento está en un

"A menos que se especifique lo contrario, dichos objetos de origen se colocarán en un estado válido pero no especificado".

Nunca he encontrado ninguna instancia donde esté "especificado de otra manera".

Aunque encuentro ver3 un poco indirecto y preferiría ver1 mucho, aunque vec3 puede permitir alguna optimización adicional, pero por otro lado puede conducir fácilmente a errores.

¿Es correcta mi suposición?

ronag
fuente
4
Podría simplemente llamar clear, ya que no tiene condiciones previas (y, por lo tanto, no depende del estado del objeto).
Nicol Bolas
@Nicol: Digamos que hubo una std::vectorimplementación que almacenó un puntero a su tamaño (parece tonto, pero legal). Moverse desde ese vector podría dejar el puntero NULL, después de lo cual clearfallaría. operator=también podría fallar.
Ben Voigt
9
@Ben: Creo que eso violaría la parte "válida" de "válido pero no especificado".
ildjarn
1
@ildjarn: Pensé que solo significaba que era seguro ejecutar el destructor.
Ben Voigt
Supongo que la pregunta es ¿qué es "válido"?
Ronag

Respuestas:

97

De la sección 17.3.26 de la especificación "estado válido pero no especificado":

un estado de objeto que no se especifica excepto que se cumplen los invariantes del objeto y las operaciones en el objeto se comportan como se especifica para su tipo [Ejemplo: si un objeto xde tipo std::vector<int>está en un estado válido pero no especificado, x.empty()se puede llamar incondicionalmente y x.front()se puede llamar solo si x.empty()devuelve falso. —Ejemplo final]

Por tanto, el objeto está vivo. Puede realizar cualquier operación que no requiera una condición previa (a menos que primero verifique la condición previa).

clear, por ejemplo, no tiene condiciones previas. Y devolverá el objeto a un estado conocido. Así que límpialo y úsalo normalmente.

Nicol Bolas
fuente
¿En qué parte del estándar puedo leer sobre "condiciones previas" para, por ejemplo, métodos std :: vector?
Ronag
1
@ronag: §23.2 contiene tablas donde se enumeran.
Grizzly
2
Encontré lo siguiente que es interesante, open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3241.html , escriben "los contenedores pueden estar 'más vacíos que vacíos'".
Ronag
4
@ronag: 1) Si el contenedor está en un estado válido, la llamada cleares válida. 2) Mientras el contenedor estaba en un estado no especificado, la llamada clearpone al contenedor en un estado específico porque tiene postcondiciones obligatorias en la norma (§23.2.3 tabla 100). std::vector<T>tiene una clase invariante que push_back()siempre es válida (siempre que lo Tsea CopyInsertable).
ildjarn
3
@ronag: open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3241.html estaba citando uno de los comentarios del organismo nacional sobre la cita "más vacío que vacío". El comentario del organismo nacional fue incorrecto. N3241 no propuso tal estado. Si una implementación de un std :: container tiene un estado "más vacío que vacío" resultante de un movimiento desde, entonces ese estado debe ser un estado válido (es decir, puede hacer cualquier cosa con ese objeto que no requiera condiciones previas).
Howard Hinnant
11

El hecho de que el objeto esté en un estado válido, pero no definido, básicamente significa que, si bien el estado exacto del objeto no está garantizado, es válido y, como tales funciones miembro (o funciones no miembro) están garantizadas para funcionar siempre y cuando no dependan en el objeto que tiene un cierto estado.

La clear()función miembro no tiene condiciones previas sobre el estado del objeto (aparte de que es válida, por supuesto) y, por lo tanto, puede invocarse en objetos movidos desde. Por otro lado, por ejemplo, front()depende de que el contenedor no esté vacío y, por lo tanto, no se puede llamar, ya que no se garantiza que no esté vacío.

Por lo tanto, tanto ver2 como ver3 deberían estar bien.

Oso pardo
fuente
Un vector siempre estará vacío, pero eso no es cierto en el caso general, (matriz IE)
Mooing Duck
"Un vector siempre estará vacío", ¿en qué lo basa?
Ronag
1
@ronag: quise decir ver2 y ver3, por supuesto (como debería quedar claro en el texto, se corrigió ese error tipográfico
Grizzly
Curiosamente, las condiciones previas para front()se indican solo para std::array, e incluso no en la tabla.
Ben Voigt
1
@Ben: §23.2.3 tabla 100 dice que la semántica operativa de front()are *a.begin(), §23.2.1 / 6 dice " Si el contenedor está vacío, entoncesbegin() == end() ", y §24.2.1 / 5 dice " La biblioteca nunca asume que el pasado- los valores finales son desreferenciables. ". En consecuencia, creo que front()se pueden inferir las condiciones previas para , aunque ciertamente podría aclararse más.
ildjarn
-8

No creo que puedas hacer NADA con un objeto movido (excepto destruirlo).

¿No se puede usar swapen su lugar, para obtener todas las ventajas de mudarse pero dejar el contenedor en un estado conocido?

Ben Voigt
fuente
+1. swap es una buena idea, aunque no funcionará en todos los casos, por ejemplo, usar auto no funcionará. ¿Quizás un safe_move, que usa swap internamente, podría ser una idea?
Ronag
5
Es un objeto en vivo, y puede usar cualquier función que no tenga condiciones previas (aparte de las invariantes)
Mooing Duck
La plantilla principal para std::swaptiene 2 asignaciones de movimiento, y los objetivos de esas asignaciones son valores de movimiento. Eso cuenta como "hacer algo con un objeto movido" para mí
Caleth