Suponha que eu tenho esta função:
void my_test()
{
A a1 = A_factory_func();
A a2(A_factory_func());
double b1 = 0.5;
double b2(0.5);
A c1;
A c2 = A();
A c3(A());
}
Em cada agrupamento, essas instruções são idênticas? Ou existe uma cópia extra (possivelmente otimizável) em algumas das inicializações?
Eu já vi pessoas dizerem as duas coisas. Por favor, citar texto como prova. Adicione também outros casos, por favor.
c++
initialization
rlbond
fonte
fonte
A c1; A c2 = c1; A c3(c1);
.A
, a inicialização da cópia não exigiria a existência do construtor copy / move. É por isso questd::atomic<int> a = 1;
está tudo bem no C ++ 17, mas não antes.Respostas:
Atualização C ++ 17
No C ++ 17, o significado de
A_factory_func()
mudou de criar um objeto temporário (C ++ <= 14) para apenas especificar a inicialização de qualquer objeto para o qual essa expressão seja inicializada (falando livremente) no C ++ 17. Esses objetos (chamados "objetos de resultado") são as variáveis criadas por uma declaração (comoa1
), objetos artificiais criados quando a inicialização acaba sendo descartada ou se um objeto é necessário para a ligação de referência (como, por exemploA_factory_func();
. No último caso, um objeto é criado artificialmente, chamado "materialização temporária", porqueA_factory_func()
não possui uma variável ou referência que exigiria a existência de um objeto).Como exemplos em nosso caso, no caso de
a1
ea2
regras especiais dizem que em tais declarações, o objeto de resultado de um inicializador de prvalor do mesmo tipo quea1
é variávela1
e, portanto,A_factory_func()
inicializa diretamente o objetoa1
. QualquerA_factory_func(another-prvalue)
conversão intermediária no estilo funcional não teria nenhum efeito, porque apenas "passa" pelo objeto de resultado do valor externo para ser também o objeto de resultado do valor interno.Depende do tipo que
A_factory_func()
retorna. Suponho que ele retorne umA
- e faça o mesmo - exceto que, quando o construtor de cópias for explícito, o primeiro falhará. Leia 8.6 / 14Isso está fazendo o mesmo porque é um tipo interno (isso significa que não é um tipo de classe aqui). Leia 8.6 / 14 .
Isso não está fazendo o mesmo. O primeiro padrão inicializa se
A
for um não-POD e não inicializa para um POD (Leia 8.6 / 9 ). A segunda cópia inicializa: O valor inicializa um temporário e depois copia esse valor parac2
(Leia 5.2.3 / 2 e 8.6 / 14 ). Obviamente, isso exigirá um construtor de cópias não explícito (Leia 8.6 / 14 e 12.3.1 / 3 e 13.3.1.3/1 ). O terceiro cria uma declaração de função para uma funçãoc3
que retornaA
e que leva um ponteiro de função para uma função que retorna aA
(Leia 8.2 ).Explorando as Inicializações Direta e Inicialização de Cópia
Embora pareçam idênticos e devam fazer o mesmo, essas duas formas são notavelmente diferentes em certos casos. As duas formas de inicialização são diretas e copiam a inicialização:
Existe um comportamento que podemos atribuir a cada um deles:
T
(incluindoexplicit
uns) e o argumento éx
. A resolução de sobrecarga encontrará o melhor construtor correspondente e, quando necessário, fará qualquer conversão implícita necessária.x
em um objeto do tipoT
. (Ele pode copiar esse objeto para o objeto inicializado, portanto, também é necessário um construtor de cópias - mas isso não é importante abaixo)Como você vê, a inicialização de cópia é de alguma forma parte da inicialização direta em relação a possíveis conversões implícitas: Embora a inicialização direta tenha todos os construtores disponíveis para chamada e , além disso, possa fazer qualquer conversão implícita necessária para corresponder aos tipos de argumento, inicie a cópia pode apenas configurar uma sequência de conversão implícita.
Eu tentei muito e consegui o código a seguir para gerar texto diferente para cada um desses formulários , sem usar o "óbvio" por meio de
explicit
construtores.Como funciona e por que gera esse resultado?
Inicialização direta
Primeiro, ele não sabe nada sobre conversão. Apenas tentará chamar um construtor. Nesse caso, o seguinte construtor está disponível e é uma correspondência exata :
Não há conversão, muito menos uma conversão definida pelo usuário, necessária para chamar esse construtor (observe que nenhuma conversão de qualificação const também acontece aqui). E assim a inicialização direta o chamará.
Inicialização de cópia
Como dito acima, a inicialização de cópia construirá uma sequência de conversão quando
a
não tiver um tipoB
ou derivado (o que é claramente o caso aqui). Portanto, ele procurará maneiras de fazer a conversão e encontrará os seguintes candidatosObserve como eu reescrevi a função de conversão: O tipo de parâmetro reflete o tipo do
this
ponteiro, que em uma função de membro não-const é não-const. Agora, chamamos esses candidatos dex
como argumento. O vencedor é a função de conversão: porque, se tivermos duas funções candidatas, ambas aceitando uma referência para o mesmo tipo, menos const versão vence (a propósito, também é o mecanismo que prefere a função membro não const que exige objetos -const).Observe que, se mudarmos a função de conversão para uma função membro const, a conversão será ambígua (porque ambos têm um tipo de parâmetro
A const&
): O compilador do Comeau a rejeita corretamente, mas o GCC a aceita no modo não pedante. Ao mudar para,-pedantic
também gera o aviso de ambiguidade apropriado.Espero que isso ajude um pouco a esclarecer como essas duas formas diferem!
fonte
R() == R(*)()
eT[] == T*
. Ou seja, tipos de função são tipos de ponteiro de função e tipos de matriz são tipos de ponteiro para elemento. Isso é péssimo. Ele pode ser contornado porA c3((A()));
(parens em torno da expressão).A atribuição é diferente da inicialização .
As duas linhas a seguir inicializam . Uma única chamada de construtor é feita:
mas não é equivalente a:
No momento, não tenho um texto para provar isso, mas é muito fácil experimentar:
fonte
double b1 = 0.5;
é chamada implícita do construtor.double b2(0.5);
é uma chamada explícita.Veja o código a seguir para ver a diferença:
Se a sua classe não tiver construtores explícitos, as chamadas explícitas e implícitas são idênticas.
fonte
Primeiro agrupamento: depende do que
A_factory_func
retorna. A primeira linha é um exemplo de inicialização de cópia , a segunda linha é de inicialização direta . SeA_factory_func
retornar umA
objeto, eles serão equivalentes, ambos chamarão o construtor de cópiasA
; caso contrário, a primeira versão criará um rvalor do tipo aA
partir de um operador de conversão disponível para o tipo de retorno deA_factory_func
ouA
construtores apropriados e, em seguida, chamará o construtor de cópia para construir aa1
partir deste. temporário. A segunda versão tenta encontrar um construtor adequado que aceite qualquerA_factory_func
retornos ou que precise de algo em que o valor retornado possa ser implicitamente convertido.Segundo agrupamento: exatamente a mesma lógica é válida, exceto que os tipos incorporados não possuem construtores exóticos, portanto, na prática, são idênticos.
Terceiro agrupamento:
c1
é inicializado por padrão,c2
é inicializado por cópia a partir de um valor inicializado temporário. Qualquer membroc1
desse tipo de pod (ou membros de membros, etc., etc.) poderá não ser inicializado se o usuário fornecer construtores padrão (se houver) não os inicializar explicitamente. Parac2
isso, depende se existe um construtor de cópias fornecido pelo usuário e se isso inicializa adequadamente esses membros, mas todos os membros do temporário serão inicializados (inicializado com zero se não inicializado explicitamente). Como litb visto,c3
é uma armadilha. Na verdade, é uma declaração de função.fonte
De importância:
[12,2 / 1]
Temporaries of class type are created in various contexts: ... and in some initializations (8.5).
Ou seja, para inicialização de cópia.
[12,8 / 15]
When certain criteria are met, an implementation is allowed to omit the copy construction of a class object ...
Em outras palavras, um bom compilador não criará uma cópia para a inicialização da cópia quando puder ser evitada; em vez disso, chamará o construtor diretamente - ou seja, assim como na inicialização direta.
Em outras palavras, a inicialização de cópia é como a inicialização direta na maioria dos casos <opinion> onde código compreensível foi gravado. Como a inicialização direta potencialmente causa conversões arbitrárias (e, portanto, provavelmente desconhecidas), eu prefiro sempre usar a inicialização de cópia sempre que possível. (Com o bônus de que realmente se parece com a inicialização.) </opinion>
Goriness técnico: [12.2 / 1 cont de cima]
Even when the creation of the temporary object is avoided (12.8), all the semantic restrictions must be respected as if the temporary object was created.
Ainda bem que não estou escrevendo um compilador C ++.
fonte
Você pode ver sua diferença nos tipos de construtor
explicit
eimplicit
quando inicializa um objeto:Aulas :
E na
main
função:Por padrão, um construtor é
implicit
assim, você tem duas maneiras de inicializá-lo:E definindo uma estrutura como
explicit
apenas uma maneira direta:fonte
Respondendo em relação a esta parte:
Como a maioria das respostas é anterior ao c ++ 11, estou adicionando o que o c ++ 11 tem a dizer sobre isso:
Portanto, a otimização ou não são equivalentes conforme o padrão. Observe que isso está de acordo com o que outras respostas mencionaram. Apenas citando o que o padrão tem a dizer por uma questão de correção.
fonte
Muitos desses casos estão sujeitos à implementação de um objeto, por isso é difícil fornecer uma resposta concreta.
Considere o caso
Nesse caso, assumindo um operador de atribuição adequado e um construtor de inicialização que aceite um único argumento inteiro, a maneira como implemento os referidos métodos afeta o comportamento de cada linha. No entanto, é uma prática comum que um deles chame o outro na implementação, a fim de eliminar o código duplicado (embora, em um caso tão simples como esse, não haja um objetivo real).
Editar: Como mencionado em outras respostas, a primeira linha de fato chamará o construtor de cópia. Considere os comentários relacionados ao operador de atribuição como comportamento pertencente a uma atribuição independente.
Dito isto, como o compilador otimiza o código terá seu próprio impacto. Se eu tiver o construtor de inicialização chamando o operador "=" - se o compilador não fizer otimizações, a linha superior executará 2 saltos em oposição a um na linha inferior.
Agora, para as situações mais comuns, seu compilador otimizará esses casos e eliminará esse tipo de ineficiência. Então, efetivamente, todas as diferentes situações que você descreve serão as mesmas. Se você quiser ver exatamente o que está sendo feito, consulte o código do objeto ou uma saída de montagem do seu compilador.
fonte
operator =(const int)
e nãoA(const int)
. Veja a resposta de @jia3ep para mais detalhes.Isto é da linguagem de programação C ++ de Bjarne Stroustrup:
Uma inicialização com um = é considerada uma inicialização de cópia . Em princípio, uma cópia do inicializador (o objeto do qual estamos copiando) é colocada no objeto inicializado. No entanto, essa cópia pode ser otimizada (elided) e uma operação de movimentação (baseada na semântica de movimentação) pode ser usada se o inicializador for um rvalor. Deixar o = torna a inicialização explícita. Inicialização explícita é conhecida como inicialização direta .
fonte