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 (como a1
), 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 exemplo A_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
e a2
regras especiais dizem que em tais declarações, o objeto de resultado de um inicializador de prvalor do mesmo tipo que a1
é variável a1
e, portanto, A_factory_func()
inicializa diretamente o objeto a1
. Qualquer A_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.
A a1 = A_factory_func();
A a2(A_factory_func());
Depende do tipo que A_factory_func()
retorna. Suponho que ele retorne um A
- e faça o mesmo - exceto que, quando o construtor de cópias for explícito, o primeiro falhará. Leia 8.6 / 14
double b1 = 0.5;
double b2(0.5);
Isso está fazendo o mesmo porque é um tipo interno (isso significa que não é um tipo de classe aqui). Leia 8.6 / 14 .
A c1;
A c2 = A();
A c3(A());
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 para c2
(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ção c3
que retorna A
e que leva um ponteiro de função para uma função que retorna a A
(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:
T t(x);
T t = x;
Existe um comportamento que podemos atribuir a cada um deles:
- A inicialização direta se comporta como uma chamada de função para uma função sobrecarregada: As funções, neste caso, são os construtores de
T
(incluindo explicit
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.
- A inicialização de cópia constrói uma sequência implícita de conversão: tenta converter
x
em um objeto do tipo T
. (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.
#include <iostream>
struct B;
struct A {
operator B();
};
struct B {
B() { }
B(A const&) { std::cout << "<direct> "; }
};
A::operator B() { std::cout << "<copy> "; return B(); }
int main() {
A a;
B b1(a); // 1)
B b2 = a; // 2)
}
// output: <direct> <copy>
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 :
B(A const&)
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 tipo B
ou derivado (o que é claramente o caso aqui). Portanto, ele procurará maneiras de fazer a conversão e encontrará os seguintes candidatos
B(A const&)
operator B(A&);
Observe 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 de x
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!
A c1; A c2 = c1; A c3(c1);
.