c++ initialization language-lawyer c++17 copy-elision

Inicialización de miembro para variable no copiable en C++ 17



initialization language-lawyer (2)

Al realizar la inicialización de miembros para una variable no copiable (como std::atomic<int> ), se requiere utilizar direct-initialization lugar de la direct-initialization de copy-initialization según la respuesta here . Sin embargo, cuando -std=c++17 en g++ 7.4.0 , parece que este último también funciona bien.

#include <atomic> class A { std::atomic<int> a = 0; // copy-initialization std::atomic<int> b{0}; // direct-initialization };

$ g++ -c atomic.cc -std=c++11 // or c++14 atomic.cc:4:26: error: use of deleted function ‘std::atomic<int>::atomic(const std::atomic<int>&)’ std::atomic<int> a = 0; // copy-initialization $ g++ -c atomic.cc -std=c++17 // no error

También falló al compilar con g++ 6.5.0 incluso con -std=c++17 . ¿Cuál es el correcto aquí?



El comportamiento cambió desde C ++ 17, lo que requiere que los compiladores omitan la construcción copiar / mover en std::atomic<int> a = 0; , es decir, copia garantizada de elisión .

(énfasis mío)

En las siguientes circunstancias, se requiere que los compiladores omitan la construcción de copiar y mover objetos de clase, incluso si el constructor de copiar / mover y el destructor tienen efectos secundarios observables. Los objetos se construyen directamente en el almacenamiento donde, de lo contrario, se copiarían / ​​moverían. Los constructores de copiar / mover no necesitan estar presentes o accesibles, ya que las reglas del lenguaje aseguran que no se realice ninguna operación de copiar / mover, incluso conceptualmente :

En detalles, std::atomic<int> a = 0; realiza la inicialización de la copia :

Si T es un tipo de clase, y la versión no calificada por cv del tipo de otro no es T o derivada de T, o si T es un tipo que no es de clase, pero el tipo de otro es un tipo de clase, secuencias de conversión definidas por el usuario que pueden convertirse del tipo de otro a T (o a un tipo derivado de T si T es un tipo de clase y hay una función de conversión disponible) se examinan y se selecciona el mejor mediante resolución de sobrecarga. El resultado de la conversión, que es una prvalue temporary (until C++17) prvalue expression (since C++17) si se utilizó un constructor de conversión, se usa para inicializar directamente el objeto.

y

(énfasis mío)

si T es un tipo de clase y el inicializador es una expresión prvalue cuyo tipo no calificado de cv es la misma clase que T, la expresión inicializadora en sí, en lugar de una materializada temporalmente, se utiliza para inicializar el objeto de destino

Eso significa que a se inicializa desde 0 directamente, no hay que construir temporalmente y luego ya no es temporal para copiar / mover.

Antes de C ++ 17, en concepto std::atomic<int> a = 0; requiere que se construya un std::atomic temporal a partir de 0 , luego el temporal se usa para copiar-construir a .

Incluso se permite copiar elisión antes de C ++ 17, se considera como una optimización:

(énfasis mío)

Esta es una optimización: incluso cuando se lleva a cabo y no se llama al constructor copiar / move (since C++11) , todavía debe estar presente y accesible (como si no hubiera sucedido ninguna optimización), de lo contrario, el programa está mal formado :

Es por eso que gcc activa el diagnóstico en modo pre-c ++ 17 para std::atomic<int> a = 0; .

(énfasis mío)

Nota: la regla anterior no especifica una optimización: la especificación del lenguaje central de C ++ 17 de los valores y temporales es fundamentalmente diferente de la de las revisiones anteriores de C ++: ya no hay un temporal para copiar / mover . Otra forma de describir la mecánica de C ++ 17 es el "paso de valor no materializado": los valores se devuelven y se utilizan sin materializar nunca un temporal .

Por cierto: supongo que hubo un error en g++ 6.5.0 con -std=c++17 ; y se ha solucionado en una versión posterior.