Eu sou novo em mover semântica no C ++ 11 e não sei muito bem como lidar com unique_ptr
parâmetros em construtores ou funções. Considere esta classe fazendo referência própria:
#include <memory>
class Base
{
public:
typedef unique_ptr<Base> UPtr;
Base(){}
Base(Base::UPtr n):next(std::move(n)){}
virtual ~Base(){}
void setNext(Base::UPtr n)
{
next = std::move(n);
}
protected :
Base::UPtr next;
};
É assim que eu devo escrever funções usando unique_ptr
argumentos?
E preciso usar std::move
o código de chamada?
Base::UPtr b1;
Base::UPtr b2(new Base());
b1->setNext(b2); //should I write b1->setNext(std::move(b2)); instead?
c++
arguments
c++11
unique-ptr
codablank1
fonte
fonte
Respostas:
Aqui estão as maneiras possíveis de usar um ponteiro exclusivo como argumento, bem como seu significado associado.
(A) Por valor
Para que o usuário chame isso, ele deve executar um dos seguintes procedimentos:
Tomar um ponteiro exclusivo por valor significa que você está transferindo a propriedade do ponteiro para a função / objeto / etc em questão. Depois de
newBase
construído,nextBase
é garantido que ele esteja vazio . Você não possui o objeto e nem sequer tem um ponteiro para ele. Foi-se.Isso é garantido porque tomamos o parâmetro por valor.
std::move
na verdade, não move nada; é apenas um elenco chique.std::move(nextBase)
retorna aBase&&
que é uma referência de valor r paranextBase
. É tudo o que faz.Como
Base::Base(std::unique_ptr<Base> n)
leva seu argumento por valor e não por referência de valor-r, o C ++ automaticamente criará um temporário para nós. Ele cria um astd::unique_ptr<Base>
partir doBase&&
qual fornecemos a função viastd::move(nextBase)
. É a construção desse temporário que realmente move o valor denextBase
para o argumento da funçãon
.(B) Por referência não constante do valor l
Isso deve ser chamado em um valor l real (uma variável nomeada). Não pode ser chamado com um temporário como este:
O significado disso é o mesmo de qualquer outro uso de referências que não sejam const: a função pode ou não reivindicar a propriedade do ponteiro. Dado este código:
Não há garantia de que
nextBase
está vazio. Ele pode estar vazia; não pode. Realmente depende do queBase::Base(std::unique_ptr<Base> &n)
quer fazer. Por isso, não é muito evidente apenas a partir da assinatura da função o que vai acontecer; você precisa ler a implementação (ou a documentação associada).Por causa disso, eu não sugeriria isso como uma interface.
(C) Por referência de valor constante l
Eu não mostro uma implementação, porque você não pode passar de a
const&
. Ao passar aconst&
, você está dizendo que a função pode acessar oBase
via ponteiro, mas não pode armazená- lo em nenhum lugar. Não pode reivindicar a propriedade dele.Isso pode ser útil. Não necessariamente para o seu caso específico, mas sempre é bom poder entregar um ponteiro a alguém e saber que ele não pode (sem violar as regras do C ++, como não deixar escapar
const
) reivindicar a propriedade dele. Eles não podem guardar. Eles podem transmiti-lo a outros, mas esses outros devem cumprir as mesmas regras.(D) Por referência de valor r
Isso é mais ou menos idêntico ao caso "por referência não constante do valor l". As diferenças são duas coisas.
Você pode passar um temporário:
Você deve usar
std::move
ao transmitir argumentos não temporários.O último é realmente o problema. Se você vir esta linha:
Você tem uma expectativa razoável de que, após a conclusão dessa linha,
nextBase
esteja vazia. Deveria ter sido movido de. Afinal, você tem aquelestd::move
sentado ali, dizendo que o movimento ocorreu.O problema é que não tem. Não é garantido que tenha sido movido de. Ele pode ter sido movido a partir, mas você só vai saber de olhar para o código-fonte. Você não pode distinguir apenas da assinatura da função.
Recomendações
unique_ptr
, aceite-a por valor.unique_ptr
durante a execução dessa função, aceite-aconst&
. Como alternativa, passe a&
ouconst&
para o tipo real apontado, em vez de usar aunique_ptr
.&&
. Mas eu recomendo fortemente que não faça isso sempre que possível.Como manipular unique_ptr
Você não pode copiar a
unique_ptr
. Você só pode movê-lo. A maneira correta de fazer isso é com astd::move
função de biblioteca padrão.Se você escolher um
unique_ptr
valor, poderá movê-lo livremente. Mas o movimento realmente não acontece por causa destd::move
. Tome a seguinte declaração:Estas são realmente duas afirmações:
(nota: o código acima não é tecnicamente compilado, pois as referências não temporárias ao valor r não são na verdade valores r. Ele está aqui apenas para fins de demonstração).
O
temporary
é apenas uma referência de valor r paraoldPtr
. É no construtor denewPtr
onde o movimento acontece.unique_ptr
O construtor de movimentos (um construtor que leva um&&
para si) é o que faz o movimento real.Se você tem um
unique_ptr
valor e deseja armazená-lo em algum lugar, deve usástd::move
-lo para fazer o armazenamento.fonte
std::move
não nomeia seu valor de retorno. Lembre-se de que as referências nomeadas rvalue são lvalues. ideone.com/VlEM3unique_ptr
; talvez outros chamadores precisem da mesma funcionalidade, mas estejam retendo umashared_ptr
chamada] (2) chamada por referência de valor poderia ser útil se a função chamada modificar o ponteiro, por exemplo, adicionar ou remover nós (pertencentes à lista) de uma lista vinculada.unique_ptr
valores por referência rvalue (por exemplo, ao transformá-los emshared_ptr
). A justificativa para isso pode ser que ela é um pouco mais eficiente (nenhuma mudança para ponteiros temporários é feita), ao mesmo tempo em que concede exatamente os mesmos direitos para o chamador (pode passar rvalores ou lvalores envolvidosstd::move
, mas não nus).Deixe-me tentar indicar os diferentes modos viáveis de passar ponteiros para objetos cuja memória é gerenciada por uma instância do
std::unique_ptr
modelo de classe; isso também se aplica aostd::auto_ptr
modelo de classe mais antiga (que acredito permitir todos os usos que o ponteiro exclusivo faz, mas para os quais, além disso, lvalues modificáveis serão aceitos onde rvalues são esperados, sem a necessidade de chamarstd::move
), e até certo ponto tambémstd::shared_ptr
.Como exemplo concreto para a discussão, considerarei o seguinte tipo de lista simples
As instâncias dessa lista (que não podem compartilhar partes com outras instâncias ou serem circulares) pertencem inteiramente a quem detém o
list
ponteiro inicial . Se o código do cliente souber que a lista que ele armazena nunca estará vazia, ele também poderá optar por armazenar a primeiranode
diretamente, em vez de alist
. Nenhum destruidornode
precisa ser definido: como os destruidores de seus campos são chamados automaticamente, a lista inteira será excluída recursivamente pelo destruidor de ponteiro inteligente assim que o tempo de vida do ponteiro ou nó inicial terminar.Esse tipo recursivo oferece a oportunidade de discutir alguns casos que são menos visíveis no caso de um ponteiro inteligente para dados simples. Além disso, as próprias funções ocasionalmente fornecem (recursivamente) um exemplo de código do cliente.
list
É claro que o typedef for é tendenciosounique_ptr
, mas a definição pode ser alterada para usoauto_ptr
ou, emshared_ptr
vez disso, sem muita necessidade de mudar para o que é dito abaixo (principalmente com relação à segurança de exceções sendo garantida sem a necessidade de escrever destruidores).Modos de passar ponteiros inteligentes
Modo 0: passa um ponteiro ou argumento de referência em vez de um ponteiro inteligente
Se sua função não estiver relacionada à propriedade, este é o método preferido: não faça com que seja necessário um ponteiro inteligente. Nesse caso, sua função não precisa se preocupar com quem é o proprietário do objeto apontado ou por que meio a propriedade é gerenciada; portanto, passar um ponteiro bruto é perfeitamente seguro e a forma mais flexível, pois, independentemente da propriedade, o cliente sempre pode produza um ponteiro bruto (chamando o
get
método ou a partir do endereço do operador&
).Por exemplo, a função para calcular o comprimento dessa lista não deve ser um
list
argumento, mas um ponteiro bruto:Um cliente que possui uma variável
list head
pode chamar essa função comolength(head.get())
, enquanto um cliente que optou por armazenar umanode n
lista que não esteja vazia pode chamarlength(&n)
.Se for garantido que o ponteiro não é nulo (o que não é o caso aqui, pois as listas podem estar vazias), pode-se preferir passar uma referência ao invés de um ponteiro. Pode ser um ponteiro / referência para non-
const
se a função precisar atualizar o conteúdo do (s) nó (s), sem adicionar ou remover nenhum deles (o último envolveria propriedade).Um caso interessante que se enquadra na categoria modo 0 é fazer uma cópia (profunda) da lista; embora uma função que faça isso deva obviamente transferir a propriedade da cópia criada, ela não se preocupa com a propriedade da lista que está copiando. Portanto, pode ser definido da seguinte forma:
Esse código merece uma análise mais detalhada, tanto para a questão de por que ele é compilado (o resultado da chamada recursiva para
copy
na lista do inicializador se liga ao argumento de referência rvalue no construtor move deunique_ptr<node>
, akalist
, ao inicializar onext
campo do geradonode
), e para a pergunta sobre por que ela é protegida por exceção (se durante o processo de alocação recursiva a memória acabar e alguma chamada denew
arremessosstd::bad_alloc
, nesse momento, um ponteiro para a lista parcialmente construída é mantido anonimamente em um tipo temporáriolist
criado para a lista de inicializadores e seu destruidor limpará essa lista parcial). A propósito, alguém deve resistir à tentação de substituir (como eu fiz inicialmente) o segundonullptr
porp
, que afinal é nulo nesse ponto: não é possível construir um ponteiro inteligente de um ponteiro (bruto) para constante , mesmo quando se sabe que ele é nulo.Modo 1: passe um ponteiro inteligente por valor
Uma função que assume um valor de ponteiro inteligente como argumento toma posse do objeto apontado imediatamente: o ponteiro inteligente que o chamador reteve (seja em uma variável nomeada ou temporária anônima) é copiado no valor do argumento na entrada da função e no chamador O ponteiro tornou-se nulo (no caso de um temporário, a cópia pode ter sido eliminada, mas, em qualquer caso, o chamador perdeu o acesso ao objeto apontado). Eu gostaria de chamar esse modo de chamada em dinheiro : o chamador paga antecipadamente pelo serviço chamado e não pode ter ilusões sobre a propriedade após a chamada. Para deixar isso claro, as regras de idioma exigem que o chamador envolva o argumento em
std::move
se o ponteiro inteligente for mantido em uma variável (tecnicamente, se o argumento for um valor l); nesse caso (mas não no modo 3 abaixo), essa função faz o que o nome sugere, movendo o valor da variável para uma temporária, deixando a variável nula.Nos casos em que a função chamada assume incondicionalmente a propriedade de (pilfers) o objeto apontado, esse modo é usado com
std::unique_ptr
oustd::auto_ptr
é uma boa maneira de passar um ponteiro junto com sua propriedade, o que evita qualquer risco de vazamento de memória. No entanto, acho que há poucas situações em que o modo 3 abaixo não deve ser preferido (nem um pouco) sobre o modo 1. Por esse motivo, não fornecerei exemplos de uso desse modo. (Mas veja oreversed
exemplo do modo 3 abaixo, onde é observado que o modo 1 faria pelo menos também.) Se a função usar mais argumentos do que apenas esse ponteiro, pode acontecer que haja, além disso, uma razão técnica para evitar o modo 1 (comstd::unique_ptr
oustd::auto_ptr
): como uma operação de movimento real ocorre ao passar uma variável de ponteirop
pela expressãostd::move(p)
, não se pode presumir quep
possui um valor útil ao avaliar os outros argumentos (a ordem da avaliação não é especificada), o que poderia levar a erros sutis; por outro lado, o uso do modo 3 garante que nenhuma mudançap
ocorra antes da chamada da função, para que outros argumentos possam acessar com segurança um valorp
.Quando usado com
std::shared_ptr
, esse modo é interessante porque, com uma única definição de função, permite ao chamador escolher se deseja manter uma cópia de compartilhamento do ponteiro enquanto cria uma nova cópia de compartilhamento a ser usada pela função (isso acontece quando um valor lvalue o argumento é fornecido; o construtor de cópia para ponteiros compartilhados usado na chamada aumenta a contagem de referência) ou apenas fornece à função uma cópia do ponteiro sem reter um ou tocar na contagem de referência (isso acontece quando um argumento rvalue é fornecido, possivelmente um lvalue envolvido em uma chamada destd::move
). Por exemploO mesmo poderia ser alcançado definindo separadamente
void f(const std::shared_ptr<X>& x)
(para o caso lvalue) evoid f(std::shared_ptr<X>&& x)
(para o caso rvalue), com os corpos de função diferindo apenas no fato de a primeira versão chamar semântica de cópia (usando construção / atribuição de cópia ao usarx
), mas a segunda versão mover semântica (escrevendo emstd::move(x)
vez disso, como no código de exemplo). Portanto, para ponteiros compartilhados, o modo 1 pode ser útil para evitar duplicação de código.Modo 2: passe um ponteiro inteligente por referência de valor (modificável)
Aqui, a função requer apenas uma referência modificável ao ponteiro inteligente, mas não fornece indicação do que fará com ele. Eu gostaria de chamar esse método de chamada com cartão : o chamador garante o pagamento fornecendo um número de cartão de crédito. A referência pode ser usada para se apropriar do objeto apontado, mas não é necessário. Esse modo requer o fornecimento de um argumento lvalue modificável, correspondendo ao fato de que o efeito desejado da função pode incluir deixar um valor útil na variável de argumento. Um chamador com uma expressão rvalue que deseja passar para essa função seria forçado a armazená-la em uma variável nomeada para poder fazer a chamada, pois o idioma apenas fornece conversão implícita em uma constantelvalue reference (referente a um temporário) de um rvalue. (Diferentemente da situação oposta tratada por
std::move
, uma conversão deY&&
paraY&
, comY
o tipo de ponteiro inteligente, não é possível; no entanto, essa conversão pode ser obtida por uma função de modelo simples, se realmente desejado; consulte https://stackoverflow.com/a/24868376 / 1436796 ). No caso em que a função chamada pretende tomar posse incondicionalmente do objeto, roubando o argumento, a obrigação de fornecer um argumento lvalue está dando o sinal errado: a variável não terá valor útil após a chamada. Portanto, o modo 3, que oferece possibilidades idênticas em nossa função, mas solicita que os chamadores forneçam um rvalor, deve ser preferido para esse uso.No entanto, há um caso de uso válido para o modo 2, ou seja, funções que podem modificar o ponteiro ou o objeto apontado de uma maneira que envolva propriedade . Por exemplo, uma função que prefixa um nó a
list
fornece um exemplo desse uso:Claramente, seria indesejável aqui forçar o uso de chamadores
std::move
, já que o ponteiro inteligente ainda possui uma lista bem definida e não vazia após a chamada, embora diferente do que antes.Novamente, é interessante observar o que acontece se a
prepend
chamada falhar por falta de memória livre. Então anew
chamada será lançadastd::bad_alloc
; neste momento, como não foinode
possível alocar, é certo que a referência de rvalor passado (modo 3) destd::move(l)
ainda não pode ter sido roubada, pois isso seria feito para construir onext
campo donode
que não pôde ser alocado. Portanto, o ponteiro inteligente originall
ainda mantém a lista original quando o erro é gerado; essa lista será destruída adequadamente pelo destruidor de ponteiro inteligente ou, casol
sobreviva graças a umacatch
cláusula suficientemente cedo , ainda manterá a lista original.Esse foi um exemplo construtivo; com uma piscadela para esta pergunta, também é possível dar o exemplo mais destrutivo de remover o primeiro nó que contém um determinado valor, se houver:
Novamente, a correção é bastante sutil aqui. Notavelmente, na declaração final, o ponteiro
(*p)->next
mantido dentro do nó a ser removido é desvinculado (porrelease
, que retorna o ponteiro, mas torna o original nulo) antesreset
(implicitamente) destrói esse nó (quando destrói o valor antigo mantido porp
), garantindo que um e apenas um nó é destruído naquele momento. (Na forma alternativa mencionada no comentário, esse momento seria deixado para os internos da implementação do operador de atribuição de movimento dastd::unique_ptr
instâncialist
; a norma diz 20.7.1.2.3; 2 que esse operador deve agir "como se chamandoreset(u.release())
", de onde o timing deve ser seguro aqui também.)Observe que
prepend
eremove_first
não pode ser chamado pelos clientes que armazenam umanode
variável local para uma lista sempre não-vazia, e com razão, pois as implementações fornecidas não podem funcionar nesses casos.Modo 3: passar um ponteiro inteligente por referência de valor (modificável)
Este é o modo preferido para usar quando simplesmente se apropriar do ponteiro. Gostaria de chamar esse método de chamada por cheque : o chamador deve aceitar renunciar à propriedade, como se estivesse fornecendo dinheiro, assinando o cheque, mas a retirada real é adiada até que a função chamada realmente ofereça o ponteiro (exatamente como usaria o modo 2 ) A "assinatura do cheque" significa que os chamadores precisam envolver um argumento
std::move
(como no modo 1) se for um lvalue (se for um rvalue, a parte "desistir da propriedade" é óbvia e não requer código separado).Observe que tecnicamente o modo 3 se comporta exatamente como o modo 2, portanto a função chamada não precisa assumir a propriedade; no entanto, eu insistiria que, se houver alguma incerteza sobre a transferência de propriedade (em uso normal), o modo 2 deve ser preferido ao modo 3, de modo que o uso do modo 3 seja implicitamente um sinal para os chamadores de que estão desistindo da propriedade. Pode-se replicar que apenas a passagem do argumento do modo 1 realmente indica perda forçada de propriedade para os chamadores. Porém, se um cliente tiver alguma dúvida sobre as intenções da função chamada, ele deve conhecer as especificações da função que está sendo chamada, o que deve remover qualquer dúvida.
É surpreendentemente difícil encontrar um exemplo típico envolvendo nosso
list
tipo que usa a passagem de argumentos do modo 3. Mover uma listab
para o final de outra listaa
é um exemplo típico; no entantoa
(que sobrevive e mantém o resultado da operação) é melhor passado usando o modo 2:Um exemplo puro de passagem de argumento do modo 3 é o seguinte que pega uma lista (e sua propriedade) e retorna uma lista que contém os nós idênticos na ordem inversa.
Essa função pode ser chamada
l = reversed(std::move(l));
para inverter a lista em si mesma, mas a lista invertida também pode ser usada de maneira diferente.Aqui, o argumento é imediatamente movido para uma variável local para eficiência (pode-se usar o parâmetro
l
diretamente no lugar dep
, mas acessá-lo sempre que envolver um nível extra de indireção); portanto, a diferença na passagem de argumentos do modo 1 é mínima. De fato, usando esse modo, o argumento poderia ter servido diretamente como variável local, evitando assim o movimento inicial; essa é apenas uma instância do princípio geral de que, se um argumento passado por referência serve apenas para inicializar uma variável local, é melhor passá-lo por valor e usar o parâmetro como variável local.O uso do modo 3 parece preconizado pelo padrão, como testemunha o fato de que todas as funções de biblioteca fornecidas transferem a propriedade de ponteiros inteligentes usando o modo 3. Um caso convincente em particular é o construtor
std::shared_ptr<T>(auto_ptr<T>&& p)
. Esse construtor usou (instd::tr1
) para obter uma referência lvalue modificável (assim como oauto_ptr<T>&
construtor de cópia) e, portanto, poderia ser chamado com umauto_ptr<T>
lvaluep
como emstd::shared_ptr<T> q(p)
, após o qualp
foi redefinido como nulo. Devido à alteração do modo 2 para o 3 na passagem de argumentos, esse código antigo deve agora ser reescritostd::shared_ptr<T> q(std::move(p))
e continuará a funcionar. Entendo que o comitê não gostou do modo 2 aqui, mas eles tiveram a opção de mudar para o modo 1, definindostd::shared_ptr<T>(auto_ptr<T> p)
em vez disso, eles poderiam garantir que o código antigo funcionasse sem modificação, porque (diferentemente dos ponteiros exclusivos) os ponteiros automáticos podem ser silenciosamente desreferenciados para um valor (o próprio objeto ponteiro sendo redefinido como nulo no processo). Aparentemente, o comitê preferiu o modo de defesa 3 ao invés do modo 1, que optou por quebrar ativamente o código existente, em vez de usar o modo 1 mesmo para um uso já reprovado.Quando preferir o modo 3 ao invés do modo 1
O modo 1 é perfeitamente utilizável em muitos casos e pode ser preferido em relação ao modo 3 nos casos em que assumir a propriedade assumiria a forma de mover o ponteiro inteligente para uma variável local, como no
reversed
exemplo acima. No entanto, vejo duas razões para preferir o modo 3 no caso mais geral:É um pouco mais eficiente passar uma referência do que criar um ponteiro temporário e nix o antigo (lidar com dinheiro é um tanto trabalhoso); em alguns cenários, o ponteiro pode ser passado várias vezes inalterado para outra função antes de ser realmente furtado. Essa passagem geralmente exige gravação
std::move
(a menos que o modo 2 seja usado), mas observe que este é apenas um elenco que na verdade não faz nada (em particular, sem referência), portanto, tem custo zero associado.Deveria ser concebível que alguma coisa gere uma exceção entre o início da chamada de função e o ponto em que ela (ou alguma chamada contida) realmente move o objeto apontado para outra estrutura de dados (e essa exceção ainda não está capturada dentro da própria função ), ao usar o modo 1, o objeto referido pelo ponteiro inteligente será destruído antes que uma
catch
cláusula possa manipular a exceção (porque o parâmetro de função foi destruído durante o desenrolamento da pilha), mas não ao usar o modo 3. O último fornece o o chamador tem a opção de recuperar os dados do objeto nesses casos (capturando a exceção). Observe que o modo 1 aqui não causa vazamento de memória , mas pode levar a uma perda irrecuperável de dados para o programa, o que também pode ser indesejável.Retornando um ponteiro inteligente: sempre por valor
Para concluir uma palavra sobre o retorno de um ponteiro inteligente, provavelmente aponte para um objeto criado para uso pelo chamador. Este não é realmente um caso comparável ao passar ponteiros para funções, mas, para ser completo, eu gostaria de insistir que, nesses casos, sempre retorne por valor (e não use
std::move
nareturn
declaração). Ninguém quer obter uma referência a um ponteiro que provavelmente acabou de ser nixado.fonte
unique_ptr
ter sido movido ou não, ele ainda excluirá bem o valor se ainda o mantiver sempre que destruído ou reutilizado.unique_ptr
impede um vazamento de memória (e, portanto, em certo sentido, cumpre seu contrato), mas aqui (ou seja, usando o modo 1), pode causar (em circunstâncias específicas) algo que pode ser considerado ainda mais prejudicial , ou seja, uma perda de dados (destruição do valor apontado) que poderia ter sido evitada utilizando modo 3.Sim, é necessário se você pegar o
unique_ptr
valor by no construtor. Explicidade é uma coisa agradável. Comounique_ptr
é impossível de copiar (cópia privada), o que você escreveu deve gerar um erro do compilador.fonte
Edit: Esta resposta está errada, mesmo que, estritamente falando, o código funcione. Só vou deixar aqui porque a discussão é muito útil. Esta outra resposta é a melhor resposta dada na última vez que editei esta: Como passo um argumento unique_ptr para um construtor ou uma função?
A idéia básica
::std::move
é que as pessoas que estão passando por vocêunique_ptr
devem usá-lo para expressar o conhecimento de que sabemunique_ptr
que estão passando perderão a propriedade.Isso significa que você deve usar uma referência rvalue para a
unique_ptr
em seus métodos, não paraunique_ptr
si mesma. De qualquer forma, isso não funcionará, porque a passagem deunique_ptr
uma cópia antiga exigiria uma cópia, e isso é explicitamente proibido na interfaceunique_ptr
. Curiosamente, usando uma referência rvalue chamado transforma-lo de volta em um lvalue de novo, então você precisa usar::std::move
dentro de seus métodos também.Isso significa que seus dois métodos devem ficar assim:
Então as pessoas que usam os métodos fariam o seguinte:
Como você vê, ele
::std::move
expressa que o ponteiro perderá a propriedade no ponto em que é mais relevante e útil saber. Se isso acontecesse de forma invisível, seria muito confuso para as pessoas que usam sua classeobjptr
perderem de repente a propriedade sem motivo aparente.fonte
Base fred(::std::move(objptr));
e nãoBase::UPtr fred(::std::move(objptr));
?std::move
na implementação do construtor e do método. E mesmo quando você passa por valor, o chamador ainda deve usarstd::move
para passar lvalues. A principal diferença é que, com a passagem por valor, essa interface torna a propriedade clara perdida. Veja Nicol Bolas comentar sobre outra resposta.deve ser muito melhor como
e
deveria estar
com o mesmo corpo.
E ... o que é
evt
nohandle()
??fonte
std::forward
aqui:Base::UPtr&&
é sempre um tipo de referência rvalue e ostd::move
passa como um rvalue. Já foi encaminhado corretamente.unique_ptr
valor por, você tem a garantia de que um construtor de movimentação foi chamado no novo valor (ou simplesmente que você recebeu um temporário). Isso garante que aunique_ptr
variável que o usuário tenha esteja agora vazia . Se você o aceitar&&
, ele será esvaziado apenas se o seu código chamar uma operação de movimentação. Do seu jeito, é possível que a variável da qual o usuário não tenha sido movido. O que torna o usuáriostd::move
suspeito e confuso. O usostd::move
deve sempre garantir que algo foi movido .Para o topo votou a resposta. Eu prefiro passar por referência rvalue.
Entendo qual é o problema de passar por referência rvalue pode causar. Mas vamos dividir esse problema em dois lados:
Eu devo escrever código
Base newBase(std::move(<lvalue>))
ouBase newBase(<rvalue>)
.O autor da biblioteca deve garantir que realmente moverá o unique_ptr para inicializar o membro, se desejar possuir a propriedade.
Isso é tudo.
Se você passar por referência de rvalue, ele chamará apenas uma instrução "mover", mas se passar por valor, serão dois.
Sim, se o autor da biblioteca não for especialista nisso, ele não poderá mover unique_ptr para inicializar o membro, mas é o problema do autor, não você. Seja o que for que passe por referência de valor ou valor, seu código é o mesmo!
Se você está escrevendo uma biblioteca, agora sabe que deve garantir, basta fazê-lo, passar por referência de rvalue é uma escolha melhor que valor. O cliente que usar sua biblioteca apenas escreverá o mesmo código.
Agora, para sua pergunta. Como passo um argumento unique_ptr para um construtor ou função?
Você sabe qual é a melhor escolha.
http://scottmeyers.blogspot.com/2014/07/should-move-only-types-ever-be-passed.html
fonte