unique_ptr<T>
não permite a construção da cópia, mas suporta a semântica de movimentação. No entanto, posso retornar a unique_ptr<T>
de uma função e atribuir o valor retornado a uma variável.
#include <iostream>
#include <memory>
using namespace std;
unique_ptr<int> foo()
{
unique_ptr<int> p( new int(10) );
return p; // 1
//return move( p ); // 2
}
int main()
{
unique_ptr<int> p = foo();
cout << *p << endl;
return 0;
}
O código acima compila e funciona como pretendido. Então, como é que essa linha 1
não invoca o construtor de cópia e resulta em erros do compilador? Se eu tivesse que usar a linha 2
, faria sentido (usar a linha também 2
funciona, mas não somos obrigados a fazê-lo).
Eu sei que o C ++ 0x permite essa exceção, unique_ptr
já que o valor de retorno é um objeto temporário que será destruído assim que a função sair, garantindo assim a exclusividade do ponteiro retornado. Estou curioso para saber como isso é implementado, é especial no compilador ou há alguma outra cláusula na especificação de linguagem que isso explora?
fonte
unique_ptr
. A questão toda é sobre 1 e 2, sendo duas maneiras diferentes de conseguir a mesma coisa.main
saída da função, mas não quando afoo
saída for concluída.Respostas:
Sim, veja 12.8 §34 e §35:
Só queria acrescentar mais um ponto de que o retorno por valor deve ser a opção padrão aqui, porque um valor nomeado na declaração de retorno, na pior das hipóteses, ou seja, sem elisões em C ++ 11, C ++ 14 e C ++ 17, é tratado como um rvalue. Por exemplo, a função a seguir é compilada com o
-fno-elide-constructors
sinalizadorCom o sinalizador definido na compilação, há dois movimentos (1 e 2) acontecendo nessa função e depois um movimento mais tarde (3).
fonte
foo()
também está prestes a ser destruído (se não tiver sido atribuído a nada), assim como o valor de retorno dentro da função e, portanto, faz sentido que o C ++ use um construtor de movimento ao fazer issounique_ptr<int> p = foo();
?std::unique_ptr
), existe uma regra especial para primeiro tratar os objetos como rvalues. Eu acho que isso concorda inteiramente com o que Nikola respondeu.Isso não é de forma alguma específico
std::unique_ptr
, mas se aplica a qualquer classe que seja móvel. É garantido pelas regras de idioma, pois você está retornando por valor. O compilador tenta excluir cópias, chama um construtor de movimentação se não pode remover cópias, chama um construtor de cópia se não pode mover e falha na compilação se não pode copiar.Se você tivesse uma função que aceite
std::unique_ptr
como argumento, não seria capaz de passar p para ela. Você precisaria invocar explicitamente o construtor move, mas nesse caso não deve usar a variável p após a chamada parabar()
.fonte
p
não seja temporário, o resultado dofoo()
que está sendo retornado é; portanto, é um rvalue e pode ser movido, o que torna a atribuiçãomain
possível. Eu diria que você estava errado, exceto que Nikola parece aplicar essa regra ap
si mesma, que está errada.1
e Linha2
? Na minha opinião é a mesma desde quando construirp
emmain
, ele só se preocupa com o tipo de tipo de retornofoo
, certo?unique_ptr não possui o construtor de cópias tradicional. Em vez disso, possui um "construtor de movimentação" que usa referências de rvalue:
Uma referência rvalue (o duplo e comercial) será vinculada apenas a um rvalue. É por isso que você recebe um erro ao tentar passar um lvalue unique_ptr para uma função. Por outro lado, um valor retornado de uma função é tratado como um rvalue, portanto, o construtor de movimentação é chamado automaticamente.
A propósito, isso funcionará corretamente:
O unique_ptr temporário aqui é um rvalue.
fonte
p
- obviamente - um lvalue - pode ser tratado como um rvalue na declaração de retornoreturn p;
na definição defoo
. Acho que não há nenhum problema com o fato de que o valor de retorno da própria função pode ser "movido".Eu acho que é perfeitamente explicado no item 25 do Effective Modern C ++ de Scott Meyers . Aqui está um trecho:
Aqui, o RVO se refere à otimização do valor de retorno e, se as condições para o RVO forem atendidas, significa retornar o objeto local declarado dentro da função que você esperaria executar o RVO , o que também é bem explicado no item 25 de seu livro, consultando o padrão (aqui o objeto local inclui os objetos temporários criados pela instrução de retorno). A maior retirada do trecho é a remoção da cópia ou
std::move
é aplicada implicitamente aos objetos locais que estão sendo retornados . Scott menciona no item 25 questd::move
é aplicado implicitamente quando o compilador decide não excluir a cópia e o programador não deve fazê-lo explicitamente.No seu caso, o código é claramente um candidato ao RVO , pois retorna o objeto local
p
e o tipo dep
é o mesmo que o tipo de retorno, o que resulta em elisão de cópia. E se o compilador optar por não excluir a cópia, por qualquer motivo,std::move
teria entrado em ação1
.fonte
Uma coisa que eu não vi em outras respostas éPara esclarecer outras respostas, há uma diferença entre o retorno de std :: unique_ptr que foi criado dentro de uma função e um dado a essa função.O exemplo pode ser assim:
fonte