Por que esse aviso do IBM XL C/C ++ Compiler?
-
06-07-2019 - |
Pergunta
Aqui está um exemplo de código mínimo que ilustra o problema:
#include <iostream>
class Thing
{
// Non-copyable
Thing(const Thing&);
Thing& operator=(const Thing&);
int n_;
public:
Thing(int n) : n_(n) {}
int getValue() const { return n_;}
};
void show(const Thing& t)
{
std::cout << t.getValue() << std::endl;
}
int main()
{
show(3);
}
Isso produz o mesmo erro:
int main()
{
show( Thing(3) );
}
IBM XL C/C ++ 8.0 Compilador sob AIX emite esses avisos:
"testWarning.cpp", line 24.9: 1540-0306 (W) The "private" copy constructor "Thing(const Thing &)" cannot be accessed.
"testWarning.cpp", line 24.9: 1540-0308 (I) The semantics specify that a temporary object must be constructed.
"testWarning.cpp", line 24.9: 1540-0309 (I) The temporary is not constructed, but the copy constructor must be accessible.
Eu também tentei G ++ 4.1.2 com "-wall" e "-Pedantic" e não tenho diagnóstico. Por que o acesso ao construtor de cópias é necessário aqui? Como posso eliminar o aviso, além de fazer com que o objeto seja copiado (que está fora do meu controle) ou fazer uma cópia explícita (quando o objeto da vida real é caro de copiar)?
Solução
As regras para isso estão em §8.5.3/5 do padrão. Existem três situações básicas identificadas. O primeiro envolve o inicializador ('3' no seu caso) sendo um LValue ou com o tipo de classe. Como nenhum deles é verdadeiro, o que você tem é o terceiro caso: inicializar uma referência const com um RValue que não possui um tipo de classe. Este caso é coberto pela bala final em 8.5.3/5:
Caso contrário, um temporário do tipo “CV1 T1” é criado e inicializado a partir da expressão inicializadora usando as regras para uma inicialização de cópia de não referência (8.5). A referência é então vinculada ao temporário. Se T1 estiver relacionado a referência a T2, o CV1 deverá ser a mesma qualificação CV que, ou maior qualificação CV que, CV2; Caso contrário, o programa é mal formado.
EDIT: Relembrando, acho que a IBM está certa. Eu estava pensando anteriormente na possibilidade de ter que copiar o temporário, mas essa não é a fonte do problema. Para criar o temporário usando a inicialização da cópia de não referência, conforme especificado em §8.5, ele precisa do ctor. Em particular, neste ponto, é equivalente a uma expressão como:
T x = a;
Isso é basicamente equivalente a:
T x = t (a);
Ou seja, é necessário criar um temporário e copiar o temporário para o objeto que está sendo inicializado (que, neste caso, é também um temporário). Para resumir o processo necessário, é aproximadamente equivalente a codificar como:
T temp1(3);
T temp2(temp1); // requires copy ctor
show(temp2); // show's reference parameter binds directly to temp2
Outras dicas
C ++ permite compiladores suficientemente inteligentes para evitar copiar objetos temporários, a única violação do Até parece regra permitida pelo padrão. Não estou familiarizado com o compilador AIX C ++ da IBM, mas parece que pensa o show(3)
A chamada exige que uma coisa temporária seja copiada. Nesse caso, o C ++ exige que você tenha um construtor de cópia acessível, mesmo que seu compilador seja inteligente o suficiente para evitar usá -lo.
Mas por que faz show(3)
requer uma cópia em primeiro lugar? Que eu não consigo descobrir. Com sorte, o LITB estará junto a pouco.
Meu pressentimento é que Jerry's responda está correto, mas ainda existem algumas perguntas.
O interessante é que existe uma questão central que cobre o parágrafo anterior dessa seção (391). Esse problema se relaciona com quando o argumento é o mesmo tipo de classe. Especificamente:
int main () {
show ( Thing (3) ); // not allowed under current wording
// but allowed with Core Issue 391
show ( 3 ); // Still illegal with 391
}
A mudança na questão central 391 afeta apenas quando o Rvalue temporário tem o mesmo tipo de classe. A redação anterior tinha:
Se a expressão do inicializador for um rvalue, com T2 a tipo de classe e
cv1 T1
é compatível com referência comcv2 T2,
A referência está vinculada da seguinte forma:[...]
O construtor que seria usado para fazer a cópia deve ser chamável se a cópia está realmente concluída.
Essa última linha é o que faria show(Thing(3))
ilegal conforme o padrão atual. A redação proposta para esta seção é:
Se a expressão do inicializador for um Rvalue, com T2 A TIPO DE CLASSE, e "CV1 T1" é compatível com "CV2 T2", a referência está ligada ao objeto representado pelo RValue (ver 3.10 [Basic.lval]) ou para um subobjeto dentro desse objeto.
Neste ponto, eu considerei que o G ++ pode ter atualizado seu comportamento conforme 391 mas que a mudança incluiu acidentalmente o caso de inicialização de cópia. No entanto, isso não é demonstrado pelas versões de G ++ com as quais testei:
class A{
public:
A ();
A (int);
private:
A (A const &);
};
void foo (A const &);
void foo ()
{
A a = 3 ; // 3.2.3 (ERROR), 3.4.6(ERROR), 4.4.0(ERROR), Comeau(ERROR)
foo ( 3 ) ; // 3.2.3 (OK), 3.4.6(OK), 4.4.0(OK), Comeau(OK)
foo ( A() ); // 3.2.3 (OK), 3.4.6(ERROR), 4.4.0(OK), Comeau(OK)
foo ( A(3) ); // 3.2.3 (OK), 3.4.6(ERROR), 4.4.0(OK), Comeau(OK)
}
Não consigo encontrar falhas na interpretação de Jerry para o foo (3)
Caso, no entanto, tenho dúvidas devido à discrepência entre os diferentes comportamentos do compilador.
O que acontece se você tentar nomear a coisa temporária?
Thing temp(3);
show(temp);