Me sorprendió que esto no apareciera en mis resultados de búsqueda, pensé que alguien habría preguntado esto antes, dada la utilidad de la semántica de movimiento en C ++ 11:
¿Cuándo debo (o es una buena idea para mí) hacer una clase no movible en C ++ 11?
(Razones otros de los problemas de compatibilidad con el código existente, que es).
c++
c++11
move-semantics
c++-faq
usuario541686
fuente
fuente
+1
de mí) con una respuesta muy completa de Herb (o su gemelo, como parece ), así que lo hice una entrada de preguntas frecuentes. Si alguien se opone, solo hágame ping en la sala de estar , entonces esto puede discutirse allí.T x = std::move(anotherT);
ser legal" no son equivalentes. La última es una solicitud de movimiento que puede recurrir al copiador en caso de que T no tenga movimiento. Entonces, ¿qué significa exactamente "móvil"?Respuestas:
La respuesta de hierba (antes de que fuera editado) en realidad le dio un buen ejemplo de un tipo que no debe ser móvil:
std::mutex
.El tipo de mutex nativo del sistema operativo (por ejemplo,
pthread_mutex_t
en plataformas POSIX) podría no ser "invariante de ubicación", lo que significa que la dirección del objeto es parte de su valor. Por ejemplo, el sistema operativo puede mantener una lista de punteros a todos los objetos mutex inicializados. Sistd::mutex
contiene un tipo de mutex del sistema operativo nativo como miembro de datos y la dirección del tipo nativo debe permanecer fija (porque el sistema operativo mantiene una lista de punteros a sus mutex), entonces cualquiera destd::mutex
los dos tendría que almacenar el tipo de mutex nativo en el montón para que permaneciera en la misma ubicación cuando se mueve entrestd::mutex
objetos ostd::mutex
no debe moverse. No es posible almacenarlo en el montón, porque astd::mutex
tiene unconstexpr
constructor y debe ser elegible para una inicialización constante (es decir, una inicialización estática) para questd::mutex
se garantiza que se construirá antes de que comience la ejecución del programa, por lo que su constructor no puede usarlonew
. Entonces, la única opción que queda esstd::mutex
ser inamovible.El mismo razonamiento se aplica a otros tipos que contienen algo que requiere una dirección fija. Si la dirección del recurso debe permanecer fija, ¡no la mueva!
Hay otro argumento para no moverse,
std::mutex
que es que sería muy difícil hacerlo de manera segura, porque necesitaría saber que nadie está tratando de bloquear el mutex en el momento en que se está moviendo. Dado que los mutexes son uno de los bloques de construcción que puedes usar para prevenir las carreras de datos, ¡sería desafortunado si no estuvieran a salvo contra las propias carreras! Con un inmueble,std::mutex
usted sabe que lo único que cualquiera puede hacerle una vez que se ha construido y antes de que se haya destruido es bloquearlo y desbloquearlo, y esas operaciones están explícitamente garantizadas para ser seguras y no introducir carreras de datos. Este mismo argumento se aplica a losstd::atomic<T>
objetos: a menos que se puedan mover atómicamente, no sería posible moverlos con seguridad, otro hilo podría estar intentando llamarcompare_exchange_strong
en el objeto justo en el momento en que se mueve. Por lo tanto, otro caso en el que los tipos no deberían ser móviles es donde son bloques de construcción de bajo nivel de código concurrente seguro y deben garantizar la atomicidad de todas las operaciones en ellos. Si el valor del objeto se pudiera mover a un nuevo objeto en cualquier momento, necesitaría usar una variable atómica para proteger cada variable atómica para que sepa si es seguro usarlo o si se ha movido ... y una variable atómica para proteger esa variable atómica, y así sucesivamente ...Creo que generalizaría para decir que cuando un objeto es solo un recuerdo puro, no un tipo que actúa como titular de un valor o abstracción de un valor, no tiene sentido moverlo. Tipos fundamentales como
int
no poder mover: moverlos es solo una copia. No puede extraer las agallas de unint
, puede copiar su valor y luego establecerlo en cero, pero sigueint
siendo un valor, solo son bytes de memoria. Pero unint
todavía es móvilen los términos del lenguaje porque una copia es una operación de movimiento válida. Sin embargo, para los tipos que no se pueden copiar, si no quiere o no puede mover la pieza de memoria y tampoco puede copiar su valor, entonces no es móvil. Un mutex o una variable atómica es una ubicación específica de la memoria (tratada con propiedades especiales), por lo que no tiene sentido moverse, y tampoco es copiable, por lo que no se puede mover.fuente
Respuesta corta: si un tipo es copiable, también debe ser movible. Sin embargo, lo contrario no es cierto: algunos tipos como
std::unique_ptr
son movibles, pero no tiene sentido copiarlos; estos son, naturalmente, tipos de solo movimiento.La respuesta un poco más larga sigue ...
Hay dos tipos principales de tipos (entre otros más especiales, como los rasgos):
Tipos de valor, como
int
ovector<widget>
. Estos representan valores y, naturalmente, deberían ser copiables. En C ++ 11, por lo general, debe pensar en mover como una optimización de copia, por lo que todos los tipos copiables deberían ser naturalmente movibles ... mover es solo una forma eficiente de hacer una copia en el caso a menudo común que no hace ' Ya no necesito el objeto original y de todos modos lo destruiremos.Tipos de referencia que existen en las jerarquías de herencia, como las clases base y las clases con funciones miembro virtuales o protegidas. Normalmente se mantienen con puntero o referencia, a menudo una
base*
obase&
, por lo que no proporcionan una construcción de copia para evitar el corte; si desea obtener otro objeto como uno existente, generalmente llama a una función virtual comoclone
. Estos no necesitan construcción o asignación de movimiento por dos razones: no son copiables y ya tienen una operación de "movimiento" natural aún más eficiente: simplemente copie / mueva el puntero al objeto y el objeto en sí no tiene que moverse a una nueva ubicación de memoria en absoluto.La mayoría de los tipos se dividen en una de esas dos categorías, pero también hay otros tipos de tipos que también son útiles, pero más raros. En particular, aquí, los tipos que expresan la propiedad exclusiva de un recurso, como
std::unique_ptr
, son tipos de solo movimiento natural, porque no tienen un valor similar (no tiene sentido copiarlos) pero los usa directamente (no siempre por puntero o referencia) y, por lo tanto, desea mover objetos de este tipo de un lugar a otro.fuente
std::mutex
era inamovible, ya que la dirección usa mutexes POSIX.En realidad, cuando busco, descubrí que algunos tipos en C ++ 11 no son móviles:
mutex
tipos (recursive_mutex
,timed_mutex
,recursive_timed_mutex
,condition_variable
type_info
error_category
locale::facet
random_device
seed_seq
ios_base
basic_istream<charT,traits>::sentry
basic_ostream<charT,traits>::sentry
atomic
tiposonce_flag
Aparentemente hay una discusión sobre Clang: https://groups.google.com/forum/?fromgroups=#!topic/comp.std.c++/pCO1Qqb3Xa4
fuente
iterators / iterator adaptors
debería ser editado ya que C ++ 11 tiene move_iterator?std::reference_wrapper
. Ok, los otros parecen ser inmóviles.ios_base
,type_info
,facet
), 3. una variedad de cosas raras (sentry
). Probablemente las únicas clases inamovibles que escribirá un programador promedio están en la segunda categoría.Otra razón que he encontrado es el rendimiento. Digamos que tiene una clase 'a' que tiene un valor. Desea generar una interfaz que permita a un usuario cambiar el valor por un tiempo limitado (para un alcance).
Una forma de lograr esto es devolviendo un objeto 'guardián de alcance' desde 'a' que establece el valor nuevamente en su destructor, de esta manera:
Si hiciera change_value_guard movible, tendría que agregar un 'if' a su destructor que verificaría si el protector se ha movido, eso es un if adicional y un impacto en el rendimiento.
Sí, claro, probablemente puede ser optimizado por cualquier optimizador sensato, pero aún así es bueno que el lenguaje (sin embargo, esto requiere C ++ 17, para poder devolver un tipo no móvil requiere una elisión de copia garantizada) pagar eso si no vamos a mover el guardia de cualquier otra forma que no sea devolverlo de la función de creación (el principio de no pagar por lo que no usa).
fuente