c++ - Por que eu std:: move um std:: shared_ptr?




c++11 shared-ptr (4)

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;
}

Ao usar o 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.


Copiar um 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.


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 renunciar à 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 altera as contagens de referência).


As operações de movimentação (como o construtor de movimentação) para std::shared_ptr são baratas , pois basicamente são "ponteiros 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 o informações de contagem de referência).

Em vez disso, as operações de cópia em std::shared_ptr invocam um aumento na contagem de referência atômica (ou seja, não apenas ++RefCount em um membro de dados RefCount inteiro, mas, por exemplo, chamar InterlockedIncrement no Windows), que é mais caro do que roubar ponteiros / estado.

Portanto, analisando a dinâmica da contagem de ref deste caso em detalhes:

// shared_ptr<CompilerInvocation> sp;
compilerInstance.setInvocation(sp);

Se você passar sp por valor e, em seguida, tirar uma cópia dentro do método CompilerInstance::setInvocation , você terá:

  1. Ao inserir o método, o parâmetro shared_ptr é construído com cópia: ref count incremento atômico .
  2. Dentro do corpo do método, você copia o parâmetro shared_ptr no membro de dados: ref count incremento atômico .
  3. Ao sair do método, o parâmetro shared_ptr é destruído: ref count atomic decrement .

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 parâmetro shared_ptr por valor e std::move dentro do método (conforme feito corretamente no código de Clang), você terá:

  1. Ao inserir o método, o parâmetro shared_ptr é construído com cópia: ref count incremento atômico .
  2. Dentro do corpo do método, você std::move o parâmetro shared_ptr para o 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.
  3. Ao sair do método, o parâmetro shared_ptr é destruído; mas desde que você avançou na etapa 2, não há nada para destruir, pois o parâmetro shared_ptr 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.





move-semantics