A elisão da cópia foi permitida em várias circunstâncias. No entanto, mesmo que fosse permitido, o código ainda precisava funcionar como se a cópia não tivesse sido omitida. Ou seja, deve haver um construtor de cópia e / ou movimentação acessível.
Garantido elisão cópia redefine uma série de conceitos C ++, de tal forma que determinadas circunstâncias em que cópias / movimentos poderiam ser elidido na verdade, não provocam uma cópia / movimento em tudo . O compilador não está omitindo uma cópia; o padrão diz que nenhuma cópia desse tipo poderia acontecer.
Considere esta função:
T Func() {return T();}
Sob regras de elisão de cópia não garantida, isso criará um temporário e, em seguida, moverá desse temporário para o valor de retorno da função. Essa operação de movimento pode ser omitida, mas Tainda deve ter um construtor de movimento acessível, mesmo que nunca seja usado.
Similarmente:
T t = Func();
Esta é a inicialização de cópia de t. Isso copiará a inicialização tcom o valor de retorno de Func. No entanto, Tainda precisa ter um construtor de movimento, embora ele não seja chamado.
A elisão de cópia garantida redefine o significado de uma expressão prvalue . Pré-C ++ 17, prvalues são objetos temporários. No C ++ 17, uma expressão prvalue é meramente algo que pode materializar um temporário, mas ainda não é temporário.
Se você usar um prvalue para inicializar um objeto do tipo do prvalue, nenhum temporário será materializado. Ao fazer return T();isso, o valor de retorno da função será inicializado por meio de um prvalue. Uma vez que essa função retornaT , nenhum temporário é criado; a inicialização do prvalue simplesmente inicializa diretamente o valor de retorno.
O que se deve entender é que, como o valor de retorno é um prvalue, ainda não é um objeto . É apenas um inicializador para um objeto, assim como T()é.
Ao fazer isso T t = Func();, o prvalue do valor de retorno inicializa diretamente o objeto t; não há um estágio "criar um temporário e copiar / mover". Visto que Func()o valor de retorno de é um prvalue equivalente a T(), té inicializado diretamente por T(), exatamente como se você tivesse feito T t = T().
Se um prvalue for usado de qualquer outra forma, o prvalue materializará um objeto temporário, que será usado naquela expressão (ou descartado se não houver expressão). Então, se você o fizesse const T &rt = Func();, o prvalue materializaria um temporário (usando T()como inicializador), cuja referência seria armazenada rt, junto com o material de extensão de vida útil temporária usual.
Uma coisa que a elisão garantida permite que você faça é retornar objetos que estão imóveis. Por exemplo,lock_guard não pode ser copiado ou movido, então você não poderia ter uma função que o retornasse por valor. Mas com a eliminação de cópia garantida, você pode.
A elisão garantida também funciona com inicialização direta:
new T(FactoryFunction());
Se FactoryFunctionretornar Tpor valor, esta expressão não copiará o valor de retorno para a memória alocada. Em vez disso, ele alocará memória e usará a memória alocada como a memória de valor de retorno para a chamada de função diretamente.
Portanto, as funções de fábrica que retornam por valor podem inicializar diretamente a memória alocada no heap, mesmo sem saber sobre isso. Contanto que funcionem internamente, sigam as regras de eliminação de cópia garantida, é claro. Eles precisam retornar um prvalue do tipo T.
Claro, isso também funciona:
new auto(FactoryFunction());
Caso você não goste de escrever nomes de tipo.
É importante reconhecer que as garantias acima só funcionam para prvalues. Ou seja, você não tem garantia ao retornar uma variável nomeada :
T Func()
{
T t = ...;
...
return t;
}
Neste caso, t ainda deve haver um construtor de copiar / mover acessível. Sim, o compilador pode escolher otimizar a cópia / movimentação. Mas o compilador ainda deve verificar a existência de um construtor de cópia / movimentação acessível.
Portanto, nada muda para a otimização do valor de retorno nomeado (NRVO).