Inicialização de membro para variável não copiável em C++ 17




initialization language-lawyer (2)

Ao executar a inicialização do membro para variável não copiável (como std::atomic<int> ), é necessário usar direct-initialization vez da direct-initialization de copy-initialization acordo com a resposta here . No entanto, quando ligo -std=c++17 no g++ 7.4.0 , parece que o último também funciona bem.

#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

Também falhou ao compilar com o g++ 6.5.0 mesmo com -std=c++17 . Qual é o correto aqui?



O comportamento mudou desde o C ++ 17, que requer que os compiladores omitam a construção de copiar / mover em std::atomic<int> a = 0; , ou seja, cópia garantida .

(ênfase minha)

Nas seguintes circunstâncias, os compiladores devem omitir a cópia e mover a construção de objetos de classe, mesmo que o construtor de copiar / mover e o destruidor tenham efeitos colaterais observáveis. Os objetos são construídos diretamente no armazenamento para onde seriam copiados / movidos. Os construtores de copiar / mover não precisam estar presentes ou acessíveis, pois as regras de idioma garantem que nenhuma operação de copiar / mover ocorra, mesmo conceitualmente :

Em detalhes, std::atomic<int> a = 0; executa inicialização de cópia :

Se T é um tipo de classe e a versão não qualificada de cv do tipo de outro não é T ou é derivada de T, ou se T é do tipo não-classe, mas o tipo do outro é um tipo de classe, sequências de conversão definidas pelo usuário que podem ser convertidos do tipo de outro para T (ou para um tipo derivado de T se T é um tipo de classe e uma função de conversão está disponível) são examinados e o melhor é selecionado através da resolução de sobrecarga. O resultado da conversão, que é uma prvalue temporary (until C++17) prvalue expression (since C++17) se um construtor de conversão foi usado, é usado para inicializar diretamente o objeto.

e

(ênfase minha)

se T é um tipo de classe e o inicializador é uma expressão de pré-valor cujo tipo não qualificado de cv é a mesma classe que T, a própria expressão do inicializador, em vez de temporariamente materializada, é usada para inicializar o objeto de destino

Isso significa que a é inicializado diretamente de 0 , não há temporário a ser construído e, em seguida, não é mais temporário para copiar / mover.

Antes do C ++ 17, no conceito std::atomic<int> a = 0; requer que um std::atomic temporário seja construído a partir de 0 , o temporário é usado para copiar-construir a .

Até a remoção de cópias é permitida antes do C ++ 17, é considerada uma otimização:

(ênfase minha)

Essa é uma otimização: mesmo quando ocorre e o construtor copy / move (since C++11) não é chamado, ele ainda deve estar presente e acessível (como se nenhuma otimização tivesse acontecido), caso contrário, o programa está incorreto. formado :

É por isso que o gcc aciona o diagnóstico no modo pré-c ++ 17 para std::atomic<int> a = 0; .

(ênfase minha)

Nota: a regra acima não especifica uma otimização: a especificação de pré-valores e temporários da linguagem C ++ 17 é fundamentalmente diferente da das revisões anteriores em C ++: não há mais um temporário para copiar / mover . Outra maneira de descrever a mecânica do C ++ 17 é a "passagem de valor não materializada": os pré - valores são retornados e usados ​​sem nunca materializar temporariamente .

BTW: Suponho que houve um erro no g++ 6.5.0 com -std=c++17 ; e foi corrigido na versão posterior.







copy-elision