Porque é que a ctor cópia usada neste código?
-
06-09-2019 - |
Pergunta
class A
{
public:
A(const int n_);
A(const A& that_);
A& operator=(const A& that_);
};
A::A(const int n_)
{ cout << "A::A(int), n_=" << n_ << endl; }
A::A(const A& that_) // This is line 21
{ cout << "A::A(const A&)" << endl; }
A& A::operator=(const A& that_)
{ cout << "A::operator=(const A&)" << endl; }
int foo(const A& a_)
{ return 20; }
int main()
{
A a(foo(A(10))); // This is line 38
return 0;
}
Ao executar este código dá o / p:
A :: A (int), n_ = 10
A :: A (int), n_ = 20
Aparentemente, o construtor de cópia nunca é chamado.
class A
{
public:
A(const int n_);
A& operator=(const A& that_);
private:
A(const A& that_);
};
No entanto, se nós torná-lo privado, ocorre este erro de compilação:
Test.cpp: Em função ‘int main ()’:
Test.cpp: 21: erro: ‘A :: A (const A &)’ é
privada Test.cpp: 38: erro: neste contexto
Por que o compilador reclamar quando ele realmente não usar o construtor de cópia?
Eu estou usando gcc versão 4.1.2 20.070.925 (Red Hat 4.1.2-33)
Solução
Núcleo desertar 391 explica o problema.
Basicamente, o padrão actual C ++ requer um construtor de cópia para estar disponível quando passar um temporária do tipo de classe para uma referência constante.
Esta exigência será removido em C ++ 0x.
A lógica por trás exigindo um construtor de cópia vem deste caso:
C f();
const C& r = f(); // a copy is generated for r to refer to
Outras dicas
O padrão de 2003, no §12.2 / 1, estados:
Mesmo quando a criação do objeto temporário é evitada (12,8), todas as restrições semânticas deve ser respeitados como se o objeto temporário foi criado. [Exemplo: mesmo se o construtor de cópia não é chamado, todos as restrições semânticas, tais como acessibilidade (cláusula 11), será satisfeito. ]
Há exemplos semelhantes ao redor. Pelo que pude perceber, o compilador é livre para gerar temporários ou otimizar-los.
Como agora eu vejo que você não está usando a qualquer construtor de cópia. No foo(A(10))
declaração que você está criando um objeto temporário da classe A e passá-la como uma const referência para foo. O foo retorna um número inteiro que é usado na construção de objecto a
. Daí eu não vejo onde o construtor de cópia é se envolver aqui e como NRVO entra em cena. Além disso, eu compilado o código a seguir, fazendo o construtor de cópia privada e compilado muito bem no VS2008.
using namespace std;
class A
{
public:
A(const int n_);
private:
A(const A& that_);
A& operator=(const A& that_);
};
A::A(const int n_)
{ cout << "A::A(int), n_=" << n_ << endl; }
A::A(const A& that_) // This is line 21
{ cout << "A::A(const A&)" << endl; }
A& A::operator=(const A& that_)
{
cout << "A::operator=(const A&)" << endl;
return *this;
}
int foo(const A& a_)
{ return 20; }
int main(int argc,char *argv[])
{
A a(foo(A(10))); // This is line 38
return 0;
}
Apenas uma outra observação: o compilador faz uma coisa diferente quando se trabalha com um temporário. Portanto, não é sobre o construtor de cópia, é sobre o intermediário temporário.
A original(10);
foo( original ); // does compile
foo( A(10) ); // doesn't compile - needs a copy constructor
Na expressão:
A a(foo(A(10)));
O resultado da A(10)
sub-expressão é uma rvalue do tipo A
. (5.2.3 [expr.type.conv])
Quando inicializar uma referência constante a partir de uma rvalue o compilador pode criar um temporária do rvalue e ligam-se que para a referência. Mesmo que escolhe não, o construtor de cópia deve ser acessível. (8.5.3 [decl.init.ref]) Este não seria o caso se houver referência estavam sendo inicializado a partir de um referência compatível lvalue , onde a ligação direta é obrigatória.
Como foo
leva seu parâmetro por referência e não o valor, não há cópia mandatado para o argumento de inicialização em si.
foo
retorna um int, então não há nenhuma cópia de um A
aqui.
a
é direta inicializada a partir do int retornado por foo, então não há nenhuma cópia do A
aqui.
A cópia-construtor não é usado, mas para que o código para compilar a necessidade de copiar construtor para ser acessível.
EDIT: Comeau compilador C ++ relata o seguinte:
Comeau C/C++ 4.3.10.1 (Oct 6 2008 11:28:09) for ONLINE_EVALUATION_BETA2
Copyright 1988-2008 Comeau Computing. All rights reserved.
MODE:strict errors C++ noC++0x_extensions
"ComeauTest.c", line 38: error: "A::A(const A &)" (declared at line 17), required
for copy that was eliminated, is inaccessible
A a(foo(A(10))); // This is line 38
^
1 error detected in the compilation of "ComeauTest.c".
Note que, se C ++ 0x extensões são ativadas, ele compila bem no compilador Comeau C ++.
Em geral, você não deve chegar preocupado com se e quando o construtor de cópia é chamado. O padrão C ++ é muito relaxado sobre quando chamadas para o construtor de cópia serão removidos, ou para essa matéria acrescentou. Se sua classe logicamente precisa dele, fornecê-la (e não se esqueça o operador destructor e atribuição) é a regra sensata.
Ao chamar:
foo( A(10) );
um objeto temporário está sendo criado durante o tempo de vida da chamada. Um construtor de cópia está sendo sendo usado para preencher os dados. O objeto temporário é removido após a execução da chamada.
Ao chamar:
{
A original(10);
foo( original );
}
O original está sendo descartado depois de sair do bloco. Ele pode ser usado com segurança como um parâmetro.
Para a velocidade óptima, passar o objecto por referência, utilizando uma variável temporária que serão descartados pelo compilador durante a sua optimização.