Quando uma função recebe um shared_ptr
(de boost ou C ++ 11 STL), você a está passando:
por referência const:
void foo(const shared_ptr<T>& p)
ou por valor
void foo(shared_ptr<T> p)
:?
Eu preferiria o primeiro método porque suspeito que seria mais rápido. Mas isso realmente vale a pena ou existem outros problemas?
Poderia, por favor, fornecer os motivos de sua escolha ou, se for o caso, por que você acha que isso não importa.
c++
c++11
boost
shared-ptr
Danvil
fonte
fonte
shared_ptr
, e posso alterá-lo, se quiser.", Enquanto a versão de valor diz "Vou copiar o seushared_ptr
, portanto, enquanto posso alterá-lo, você nunca saberá. ) Um parâmetro const-reference é a solução real, que diz "eu vou usar um pseudônimoshared_ptr
e prometo não alterá-lo". (O que é extremamente semelhante à semântica de valor!)shared_ptr
aluno. Você faz isso por const-refs?Respostas:
Esta pergunta foi discutida e respondida por Scott, Andrei e Herb durante a sessão Ask Us Anything em C ++ e Beyond 2011 . Assista a partir de 4:34 sobre
shared_ptr
desempenho e correção .Logo, não há razão para passar por valor, a menos que o objetivo seja compartilhar a propriedade de um objeto (por exemplo, entre diferentes estruturas de dados ou entre diferentes threads).
A menos que você possa otimizá-lo conforme explicado por Scott Meyers no vídeo de discussão vinculado acima, mas isso está relacionado à versão real do C ++ que você pode usar.
Uma grande atualização desta discussão aconteceu durante o Painel Interativo da conferência GoingNative 2012 : Ask Us Anything! que vale a pena assistir, especialmente a partir das 22:50 .
fonte
Value*
é curto e legível, mas é ruim, então agora meu código está cheioconst shared_ptr<Value>&
e é significativamente menos legível e apenas ... menos arrumado. O que costumava servoid Function(Value* v1, Value* v2, Value* v3)
agoravoid Function(const shared_ptr<Value>& v1, const shared_ptr<Value>& v2, const shared_ptr<Value>& v3)
, e as pessoas estão bem com isso?class Value {...}; using ValuePtr = std::shared_ptr<Value>;
Então, sua função se torna mais simples:void Function(const ValuePtr& v1, const ValuePtr& v2, const ValuePtr& v3)
e você obtém o desempenho máximo. É por isso que você usa C ++, não é? :)Aqui está o exemplo de Herb Sutter
fonte
Pessoalmente, eu usaria uma
const
referência. Não há necessidade de incrementar a contagem de referência apenas para diminuí-la novamente por causa de uma chamada de função.fonte
shared_ptr
funciona, a única desvantagem possível de não passar por referência é uma pequena perda de desempenho. Existem duas causas aqui. a) o recurso de alias do ponteiro significa que os ponteiros valem dados mais um contador (talvez 2 para referências fracas) é copiado, por isso é um pouco mais caro copiar a rodada de dados. b) a contagem de referência atômica é um pouco mais lenta que o código antigo simples de incremento / decremento, mas é necessária para garantir a segurança do encadeamento. Além disso, os dois métodos são os mesmos para a maioria das intenções e propósitos.Passe por
const
referência, é mais rápido. Se você precisar armazená-lo, diga em algum recipiente, a ref. A contagem será incrementada automaticamente pela operação de cópia.fonte
Executei o código abaixo, uma vez com
foo
oshared_ptr
byconst&
e novamente comfoo
oshared_ptr
by.Usando o VS2015, versão x86 build, no meu processador intel core 2 quad (2.4GHz)
A cópia por versão de valor era uma ordem de magnitude mais lenta.
Se você estiver chamando uma função de forma síncrona a partir do encadeamento atual, prefira a
const&
versão.fonte
foo()
função nem deveria aceitar um ponteiro compartilhado em primeiro lugar, porque não está usando esse objeto: ela deve aceitar umint&
e fazerp = ++x;
, chamandofoo(*p);
demain()
. Uma função aceita um objeto ponteiro inteligente quando precisa fazer algo com ele e, na maioria das vezes, o que você precisa fazer é movê-lo (std::move()
) para outro lugar, para que um parâmetro por valor não tenha custo.Desde o C ++ 11, você deve valorizá- lo em const e mais frequentemente do que você imagina.
Se você estiver usando o std :: shared_ptr (em vez do tipo subjacente T), estará fazendo isso porque deseja fazer algo com ele.
Se você deseja copiá-lo em algum lugar, faz mais sentido pegá-lo por cópia e std :: movê-lo internamente, em vez de pegá-lo com const & e depois copiá-lo. Isso ocorre porque você permite ao chamador a opção std :: move o shared_ptr ao chamar sua função, economizando um conjunto de operações de incremento e decremento. Ou não. Ou seja, o chamador da função pode decidir se precisa ou não do std :: shared_ptr depois de chamar a função e dependendo de se mover ou não. Isso não é possível se você passar por const & e, portanto, é preferencialmente aceitá-lo por valor.
Obviamente, se o chamador precisa do seu shared_ptr por mais tempo (portanto, não pode std :: move-lo) e você não deseja criar uma cópia simples na função (digamos que você queira um ponteiro fraco, ou às vezes deseja apenas para copiá-lo, dependendo de alguma condição), então uma const & ainda pode ser preferível.
Por exemplo, você deve fazer
sobre
Porque nesse caso, você sempre cria uma cópia internamente
fonte
Sem saber o custo de tempo da operação de cópia shared_copy em que está o incremento e o decréscimo atômico, sofri um problema de uso da CPU muito maior. Eu nunca esperei que o incremento e o decréscimo atômicos pudessem custar muito.
Após o resultado do teste, o incremento e decremento at32 int32 levam 2 ou 40 vezes ao incremento e decremento não atômico. Comprei no Core i7 3GHz com o Windows 8.1. O primeiro resultado sai quando não ocorre contenção, o último quando ocorre alta possibilidade de contenção. Tenho em mente que as operações atômicas são, finalmente, o bloqueio baseado em hardware. Bloqueio é bloqueio. Mau para o desempenho quando ocorre contenção.
Experimentando isso, eu sempre uso byref (const shared_ptr &) do que byval (shared_ptr).
fonte
Houve uma postagem recente no blog: https://medium.com/@vgasparyan1995/pass-by-value-vs-pass-by-reference-to-const-c-f8944171e3ce
Portanto, a resposta para isso é: nunca (quase) nunca passe
const shared_ptr<T>&
.Simplesmente passe a classe subjacente.
Basicamente, os únicos tipos de parâmetros razoáveis são:
shared_ptr<T>
- Modifique e tome posseshared_ptr<const T>
- Não modifique, tome posseT&
- Modificar, sem propriedadeconst T&
- Não modifique, sem propriedadeT
- Não modifique, sem propriedade, Barato para copiarComo @accel apontou em https://stackoverflow.com/a/26197326/1930508, o conselho de Herb Sutter é:
Mas em quantos casos você não tem certeza? Então essa é uma situação rara
fonte
Sabe-se que a transmissão de shared_ptr por valor tem um custo e deve ser evitada, se possível.
O custo da passagem por shared_ptr
Na maioria das vezes, passaria shared_ptr por referência e, melhor ainda, por referência const.
A diretriz principal do cpp possui uma regra específica para transmitir shared_ptr
R.34: Pegue um parâmetro shared_ptr para expressar que uma função é o proprietário da parte
Um exemplo de quando é realmente necessário transmitir shared_ptr por valor é quando o chamador passa um objeto compartilhado para um receptor assíncrono - ou seja, o chamador fica fora do escopo antes que o receptor conclua seu trabalho. O chamado deve "estender" a vida útil do objeto compartilhado, assumindo um share_ptr por valor. Nesse caso, passar uma referência para shared_ptr não será suficiente.
O mesmo vale para passar um objeto compartilhado para um thread de trabalho.
fonte
shared_ptr não é grande o suficiente, nem seu construtor \ destrutor trabalha o suficiente para que haja uma sobrecarga suficiente da cópia para se preocupar com o desempenho de passagem por referência versus desempenho de passagem por cópia.
fonte
shared_ptr<int>
valor by leva mais de 100 instruções x86 (incluindolock
instruções ed caras para aumentar / diminuir atomicamente a contagem de ref). Passar por ref constante é o mesmo que passar um ponteiro para qualquer coisa (e, neste exemplo, no explorador do compilador Godbolt, a otimização de chamada de cauda transforma isso em um jmp simples em vez de em uma chamada: godbolt.org/g/TazMBU ).