studio programacion móviles libros desarrollo desarrollar curso aprende aplicaciones c++ c++11 move-semantics c++-faq

c++ - programacion - minecraft splash



¿Cuándo hacer un tipo no movible en C++ 11? (4)

De hecho, cuando busco, descubrí que algunos tipos en C ++ 11 no son móviles:

  • todos los tipos mutex ( 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
  • todos atomic tipos atomic
  • once_flag

Aparentemente hay un debate sobre Clang: https://groups.google.com/forum/?fromgroups=#!topic/comp.std.c++/pCO1Qqb3Xa4

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 movimientos en C ++ 11:

¿Cuándo tengo que (o es una buena idea para mí) hacer una clase no móvil en C ++ 11?

(Razones distintas de los problemas de compatibilidad con el código existente, eso es).


La respuesta de Herb (antes de que fuera editada) de hecho dio un buen ejemplo de un tipo que no debería ser movible: std::mutex .

El tipo mutex nativo del SO (p. Ej. pthread_mutex_t en plataformas POSIX) puede 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. Si std::mutex contenía un tipo 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 std::mutex tendría que almacenar el tipo mutex nativo en el montón para que permanezca en el mismo lugar cuando se mueve entre los objetos std::mutex o el std::mutex no debe moverse. No es posible almacenarlo en el montón porque un std::mutex tiene un constructor constexpr y debe ser elegible para la inicialización constante (es decir, inicialización estática) para garantizar que se construya un std::mutex global antes de que comience la ejecución del programa , por lo que su constructor no puede usar new . Entonces, la única opción que queda es que std::mutex sea ​​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 mover std::mutex que es que sería muy difícil hacerlo de manera segura, porque necesitaría saber que nadie está intentando bloquear el mutex en el momento en que se mueve. Como los mutexes son uno de los componentes básicos que puede utilizar para evitar las carreras de datos, sería desafortunado que no estuvieran seguros contra las carreras mismas. Con un std::mutex inamovible, usted sabe que las únicas cosas que cualquiera puede hacerle una vez que se ha construido y antes de que se destruya es bloquearlo y desbloquearlo, y esas operaciones están garantizadas explícitamente que son seguras para hilos y no introducen datos carreras Este mismo argumento se aplica a std::atomic<T> objetos std::atomic<T> : a menos que puedan moverse de forma atómica, no sería posible moverlos con seguridad, otro hilo podría estar intentando llamar compare_exchange_strong en el objeto en el momento en que se mueve. Entonces, otro caso en el que los tipos no deberían ser móviles es cuando se trata de bloques de construcción de bajo nivel de código concurrente seguro y debe garantizar la atomicidad de todas las operaciones sobre 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 saber si es seguro usarlo o si se movió ... 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 simplemente una pieza pura de memoria, no un tipo que actúa como un soporte para un valor o abstracción de un valor, no tiene sentido moverlo. Los tipos fundamentales como int no se pueden mover: moverlos es solo una copia. No puedes extraer las agallas de un int , puedes copiar su valor y luego establecerlo en cero, pero sigue siendo un int con un valor, solo son bytes de memoria. Pero una int todavía se puede mover en los términos del idioma porque una copia es una operación de movimiento válida. Sin embargo, para los tipos que no pueden copiarse, si no desea mover la pieza de memoria y tampoco puede copiar su valor, entonces no se puede mover. Una 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 se puede copiar, por lo que no se puede mover.


Otra razón que he encontrado - 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 devolver un objeto ''guardián de alcance'' de ''a'' que restablece el valor en su destructor, así:

class a { int value = 0; public: struct change_value_guard { friend a; private: change_value_guard(a& owner, int value) : owner{ owner } { owner.value = value; } change_value_guard(change_value_guard&&) = delete; change_value_guard(const change_value_guard&) = delete; public: ~change_value_guard() { owner.value = 0; } private: a& owner; }; change_value_guard changeValue(int newValue) { return{ *this, newValue }; } }; int main() { a a; { auto guard = a.changeValue(2); } }

Si hiciera que change_value_guard se moviera, tendría que agregar un ''si'' a su destructor que verificaría si se ha movido el protector - eso es un extra si, y un impacto en el rendimiento.

Sí, claro, probablemente pueda ser optimizado por cualquier optimizador sano, pero aún así es bueno que el lenguaje (esto requiere C ++ 17, sin embargo, para poder devolver un tipo no movible requiera elisión de copia garantizada) no nos requiera para pagar eso si, de todos modos, no vamos a mover el guardia de otra forma que devolverlo desde la función de creación (el principio de no pagar por lo que no se usa).


Respuesta corta: si se puede copiar un tipo, 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 solo de movimiento.

Le sigue una respuesta un poco más larga ...

Hay dos tipos principales de tipos (entre otros más especiales como los rasgos):

  1. Tipos de valor, como int o vector<widget> . Estos representan valores, y naturalmente deben ser copiables. En C ++ 11, en general, debería pensar en mover como una optimización de copia, por lo que todos los tipos de copiado deberían ser movibles de forma natural ... el movimiento es solo una forma eficiente de hacer una copia en el caso frecuente de que no lo haga Necesito el objeto original más y lo voy a destruir de todos modos.

  2. Tipos similares a los de referencia que existen en las jerarquías de herencia, como las clases base y las clases con funciones de miembro virtual o protegido. Normalmente se mantienen por puntero o referencia, a menudo una base* o base& , y por lo tanto no proporcionan una construcción de copia para evitar cortar; si desea obtener otro objeto igual que uno existente, generalmente llama a una función virtual como clone . No es necesario mover la construcción o la asignación por dos motivos: no se pueden copiar, 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 lo hace tiene que moverse a una nueva ubicación de memoria.

La mayoría de los tipos pertenecen a una de esas dos categorías, pero también existen otros tipos de tipos que también son útiles, solo que más raros. En particular, aquí, los tipos que expresan propiedad exclusiva de un recurso, como std::unique_ptr , son tipos de solo movimiento natural, porque no tienen valor (no tiene sentido copiarlos) pero los usa directamente (no siempre por puntero o referencia) y por eso quiere mover objetos de este tipo de un lugar a otro.