Estive pesquisando o código-fonte do Clang e encontrei este trecho:
void CompilerInstance::setInvocation(
std::shared_ptr<CompilerInvocation> Value) {
Invocation = std::move(Value);
}
Por que eu iria querer std::move
um std::shared_ptr
?
Existe algum ponto em transferir a propriedade de um recurso compartilhado?
Por que eu não faria isso?
void CompilerInstance::setInvocation(
std::shared_ptr<CompilerInvocation> Value) {
Invocation = Value;
}
fonte
Ao usar,
move
você evita aumentar e diminuir imediatamente o número de compartilhamentos. Isso pode economizar algumas operações atômicas caras na contagem de uso.fonte
As operações de movimentação (como o construtor de movimentação)
std::shared_ptr
são baratas , pois basicamente são "indicadores de roubo" (da origem para o destino; para ser mais preciso, todo o bloco de controle de estado é "roubado" da origem para o destino, incluindo as informações da contagem de referência) .Em vez disso, as operações de cópia ao
std::shared_ptr
invocar o aumento da contagem de referência atômica (ou seja, não apenas++RefCount
em umRefCount
membro de dados inteiro , mas, por exemplo, chamando oInterlockedIncrement
Windows), que é mais caro do que apenas roubar ponteiros / estado.Portanto, analisando a dinâmica da contagem de ref deste caso em detalhes:
Se você passar
sp
por valor e tirar uma cópia dentro doCompilerInstance::setInvocation
método, você terá:shared_ptr
parâmetro é construído com cópia: ref count incremento atômico .shared_ptr
parâmetro no membro de dados: ref count incremento atômico .shared_ptr
parâmetro é destruído: ref count decrement atomic .Você tem dois incrementos atômicos e um decréscimo atômico, para um total de três operações atômicas .
Em vez disso, se você passar o
shared_ptr
parâmetro por valor e depoisstd::move
dentro do método (conforme feito corretamente no código de Clang), você terá:shared_ptr
parâmetro é construído com cópia: ref count incremento atômico .std::move
oshared_ptr
parâmetro no membro de dados: ref count não muda! Você está apenas roubando ponteiros / estado: não há operações caras de contagem atômica de ref.shared_ptr
parâmetro é destruído; mas desde que você avançou na etapa 2, não há nada para destruir, pois oshared_ptr
parâmetro não está mais apontando para nada. Novamente, nenhum decréscimo atômico acontece neste caso.Conclusão: neste caso, você obtém apenas um incremento atômico de contagem de ref, ou seja, apenas uma operação atômica .
Como você pode ver, isso é muito melhor do que dois incrementos atômicos mais um decréscimo atômico (para um total de três operações atômicas) para o caso da cópia.
fonte
compilerInstance.setInvocation(std::move(sp));
, não haverá incremento . Você pode obter o mesmo comportamento adicionando uma sobrecarga que requer umashared_ptr<>&&
duplicação, mas por que duplicar quando não é necessário.setInvocation(new CompilerInvocation)
, de ou como mencionado catracasetInvocation(std::move(sp))
,. Desculpe se meu primeiro comentário não foi claro, eu realmente o publiquei por acidente, antes de terminar de escrever, e decidi simplesmente deixá-lo.Copiar a
shared_ptr
envolve copiar seu ponteiro de objeto de estado interno e alterar a contagem de referência. Movê-lo envolve apenas a troca de ponteiros para o contador de referência interno e o objeto de propriedade, por isso é mais rápido.fonte
Há duas razões para usar std :: move nessa situação. A maioria das respostas abordou a questão da velocidade, mas ignorou a questão importante de mostrar a intenção do código com mais clareza.
Para um std :: shared_ptr, std :: move indica inequivocamente uma transferência de propriedade do apontador, enquanto uma operação de cópia simples adiciona um proprietário adicional. Obviamente, se o proprietário original posteriormente abandonar sua propriedade (como permitir que seu std :: shared_ptr seja destruído), uma transferência de propriedade será realizada.
Quando você transfere a propriedade com std :: move, é óbvio o que está acontecendo. Se você usar uma cópia normal, não é óbvio que a operação pretendida é uma transferência até que você verifique se o proprietário original renuncia imediatamente à propriedade. Como bônus, é possível uma implementação mais eficiente, uma vez que uma transferência atômica de propriedade pode evitar o estado temporário em que o número de proprietários aumentou em um (e o assistente muda nas contagens de referência).
fonte
Pelo menos com o libstdc ++, você deve obter o mesmo desempenho com movimentação e atribuição, porque
operator=
chamastd::move
o ponteiro recebido. Consulte: https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/shared_ptr.h#L384fonte