Meu entendimento é que no C ++ 11, quando você retorna uma variável local de uma função por valor, o compilador pode tratar essa variável como uma referência de valor r e 'movê-la para fora da função para retorná-la (se O RVO / NRVO não acontece, é claro).
Minha pergunta é: isso não pode quebrar o código existente?
Considere o seguinte código:
#include <iostream>
#include <string>
struct bar
{
bar(const std::string& str) : _str(str) {}
bar(const bar&) = delete;
bar(bar&& other) : _str(std::move(other._str)) {other._str = "Stolen";}
void print() {std::cout << _str << std::endl;}
std::string _str;
};
struct foo
{
foo(bar& b) : _b(b) {}
~foo() {_b.print();}
bar& _b;
};
bar foobar()
{
bar b("Hello, World!");
foo f(b);
return std::move(b);
}
int main()
{
foobar();
return EXIT_SUCCESS;
}
Eu pensava que seria possível para um destruidor de um objeto local fazer referência ao objeto que é implicitamente movido e, portanto, inesperadamente ver um objeto 'vazio'. Eu tentei testar isso (ver http://ideone.com/ZURoeT ), mas eu tenho o resultado 'correta' sem a explícita std::move
no foobar()
. Suponho que isso se deva ao NRVO, mas não tentei reorganizar o código para desativá-lo.
Estou certo de que essa transformação (causando uma saída da função) ocorre implicitamente e pode quebrar o código existente?
ATUALIZAÇÃO Aqui está um exemplo que ilustra o que estou falando. Os dois links a seguir são para o mesmo código. http://ideone.com/4GFIRu - C ++ 03 http://ideone.com/FcL2Xj - C ++ 11
Se você olhar para a saída, é diferente.
Então, acho que agora essa pergunta se torna: foi considerada ao adicionar uma mudança implícita ao padrão e foi decidido que não havia problema em adicionar essa alteração, pois esse tipo de código é raro o suficiente? Gostaria também de saber se algum compilador irá avisar em casos como este ...
Respostas:
Scott Meyers postou no comp.lang.c ++ (agosto de 2010) sobre um problema em que a geração implícita de construtores de movimentação poderia quebrar os invariantes da classe C ++ 03:
Aqui o problema é que no C ++ 03,
X
havia um invariante de que seuv
membro sempre teve 5 elementos.X::~X()
contava com esse invariante, mas o construtor de movimentação recém-introduzido saiu dev
, definindo seu comprimento para zero.Isso está relacionado ao seu exemplo, pois a invariante quebrada é detectada apenas no
X
destruidor do (como você diz que é possível que um destruidor de um objeto local faça referência ao objeto que é movido implicitamente e, portanto, inesperadamente vê um objeto vazio ).O C ++ 11 tenta alcançar um equilíbrio entre quebrar parte do código existente e fornecer otimizações úteis com base em construtores de movimentação.
O comitê decidiu inicialmente que os construtores de movimentação e os operadores de atribuição de movimentação deveriam ser gerados pelo compilador quando não fornecidos pelo usuário.
Decidiu então que isso realmente era motivo de alarme e restringiu a geração automática de construtores de movimentação e operadores de atribuição de movimentação de tal maneira que é muito menos provável, embora não impossível, que o código existente seja quebrado (por exemplo, destruidor explicitamente definido).
É tentador pensar que impedir a geração de construtores de movimentação implícitos quando um destruidor definido pelo usuário está presente é suficiente, mas não é verdade ( N3153 - Movimento implícito deve ir para obter mais detalhes).
No N3174 - Mover ou não Mover Stroupstrup diz:
fonte