C ++ - passando referências para std :: shared_ptr ou boost :: shared_ptr

115

Se eu tenho uma função que precisa trabalhar com um shared_ptr, não seria mais eficiente passar uma referência a ela (para evitar a cópia do shared_ptrobjeto)? Quais são os possíveis efeitos colaterais ruins? Eu imagino dois casos possíveis:

1) dentro da função é feita uma cópia do argumento, como em

ClassA::take_copy_of_sp(boost::shared_ptr<foo> &sp)  
{  
     ...  
     m_sp_member=sp; //This will copy the object, incrementing refcount  
     ...  
}  

2) dentro da função o argumento é usado apenas, como em

Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here  
{    
    ...  
    sp->do_something();  
    ...  
}  

Não vejo em ambos os casos um bom motivo para passar boost::shared_ptr<foo>por valor em vez de por referência. Passar por valor aumentaria apenas "temporariamente" a contagem de referência devido à cópia e, em seguida, diminuiria ao sair do escopo da função. Estou esquecendo algo?

Só para esclarecer, depois de ler várias respostas: Concordo perfeitamente com as preocupações de otimização prematura e sempre tento primeiro o perfil-e-depois-trabalho-nos-hotspots. Minha pergunta era mais do ponto de vista de código puramente técnico, se é que você me entende.

abigagli
fonte
Não sei se você pode modificar as tags da sua pergunta, mas tente adicionar uma tag boost lá. Tentei procurar por esta pergunta, mas não consegui encontrar porque procurei as tags boost e smart-pointer. Então eu encontrei sua pergunta logo após escrever minha própria pergunta
Edison Gustavo Muenz

Respostas:

113

O objetivo de uma shared_ptrinstância distinta é garantir (na medida do possível) que, enquanto shared_ptrestiver no escopo, o objeto para o qual aponta ainda existirá, porque sua contagem de referência será pelo menos 1.

Class::only_work_with_sp(boost::shared_ptr<foo> sp)
{
    // sp points to an object that cannot be destroyed during this function
}

Portanto, ao usar uma referência a a shared_ptr, você desativa essa garantia. Então, em seu segundo caso:

Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here  
{    
    ...  
    sp->do_something();  
    ...  
}

Como você sabe que sp->do_something()não explodirá devido a um ponteiro nulo?

Tudo depende do que está nessas seções '...' do código. E se você chamar algo durante o primeiro '...' que tem o efeito colateral (em algum lugar em outra parte do código) de limpar um shared_ptrpara esse mesmo objeto? E se ele for o único remanescente distinto shared_ptrdaquele objeto? Tchau, objeto, exatamente onde você está prestes a tentar usá-lo.

Portanto, existem duas maneiras de responder a essa pergunta:

  1. Examine a origem de todo o programa com muito cuidado até ter certeza de que o objeto não morrerá durante o corpo da função.

  2. Altere o parâmetro de volta para um objeto distinto em vez de uma referência.

Um conselho geral que se aplica aqui: não se preocupe em fazer alterações arriscadas em seu código por causa do desempenho até que tenha cronometrado seu produto em uma situação realista em um criador de perfil e medido de forma conclusiva que a mudança que você deseja fazer causará um diferença significativa para o desempenho.

Atualização para comentarista JQ

Aqui está um exemplo artificial. É deliberadamente simples, então o erro será óbvio. Em exemplos reais, o erro não é tão óbvio porque está oculto em camadas de detalhes reais.

Temos uma função que enviará uma mensagem a algum lugar. Pode ser uma mensagem grande, então, em vez de usar um std::stringque provavelmente é copiado conforme é passado para vários lugares, usamos um shared_ptrpara uma string:

void send_message(std::shared_ptr<std::string> msg)
{
    std::cout << (*msg.get()) << std::endl;
}

(Apenas "enviamos" para o console neste exemplo).

Agora queremos adicionar uma facilidade para lembrar a mensagem anterior. Queremos o seguinte comportamento: deve existir uma variável que contém a mensagem enviada mais recentemente, mas enquanto uma mensagem está sendo enviada, não deve haver nenhuma mensagem anterior (a variável deve ser redefinida antes do envio). Portanto, declaramos a nova variável:

std::shared_ptr<std::string> previous_message;

Em seguida, alteramos nossa função de acordo com as regras que especificamos:

void send_message(std::shared_ptr<std::string> msg)
{
    previous_message = 0;
    std::cout << *msg << std::endl;
    previous_message = msg;
}

Portanto, antes de iniciarmos o envio, descartamos a mensagem anterior atual e, depois que o envio for concluído, podemos armazenar a nova mensagem anterior. Tudo bom. Aqui estão alguns códigos de teste:

send_message(std::shared_ptr<std::string>(new std::string("Hi")));
send_message(previous_message);

E como esperado, isso é impresso Hi!duas vezes.

Agora vem o Sr. Maintainer, que olha o código e pensa: Ei, esse parâmetro para send_messageé um shared_ptr:

void send_message(std::shared_ptr<std::string> msg)

Obviamente, isso pode ser alterado para:

void send_message(const std::shared_ptr<std::string> &msg)

Pense no aprimoramento de desempenho que isso trará! (Não importa que estejamos prestes a enviar uma mensagem tipicamente grande por algum canal, então a melhoria de desempenho será tão pequena a ponto de ser incomensurável).

Mas o verdadeiro problema é que agora o código de teste exibirá um comportamento indefinido (em compilações de depuração do Visual C ++ 2010, ele trava).

O Sr. Maintainer fica surpreso com isso, mas adiciona uma verificação defensiva send_messageem uma tentativa de impedir que o problema aconteça:

void send_message(const std::shared_ptr<std::string> &msg)
{
    if (msg == 0)
        return;

Mas é claro que ele segue em frente e trava, porque msgnunca é nulo quando send_messageé chamado.

Como eu disse, com todo o código tão próximo em um exemplo trivial, é fácil encontrar o erro. Mas em programas reais, com relacionamentos mais complexos entre objetos mutáveis ​​que contêm ponteiros uns para os outros, é fácil cometer o erro e difícil construir os casos de teste necessários para detectar o erro.

A solução fácil, onde você deseja que uma função possa contar com a shared_ptrcontinuação de ser não nula, é que a função aloque seu próprio verdadeiro shared_ptr, em vez de depender de uma referência a um existente shared_ptr.

A desvantagem é que uma cópia copiada shared_ptrnão é gratuita: mesmo as implementações "livres de bloqueio" precisam usar uma operação interligada para honrar as garantias de threading. Portanto, pode haver situações em que um programa pode ser significativamente acelerado transformando de a shared_ptrem a shared_ptr &. Mas essa não é uma alteração que possa ser feita com segurança em todos os programas. Ele muda o significado lógico do programa.

Observe que um bug semelhante ocorreria se usássemos std::stringem vez de std::shared_ptr<std::string>e em vez de:

previous_message = 0;

para limpar a mensagem, dissemos:

previous_message.clear();

Então, o sintoma seria o envio acidental de uma mensagem vazia, em vez de um comportamento indefinido. O custo de uma cópia extra de uma string muito grande pode ser muito mais significativo do que o custo de copiar um shared_ptr, portanto, a compensação pode ser diferente.

Daniel Earwicker
fonte
16
O shared_ptr que é passado já reside em um escopo, no site da chamada. Você pode ser capaz de criar um cenário elaborado onde o código nesta questão explodiria devido a um ponteiro pendente, mas então suponho que você tenha problemas maiores do que o parâmetro de referência!
Magnus Hoff
10
Ele pode ser armazenado em um membro. Você pode ligar para algo que aconteça para limpar aquele membro. O objetivo de smart_ptr é evitar a necessidade de coordenar vidas em hierarquias ou escopos que se aninham em torno da pilha de chamadas, por isso é melhor presumir que as vidas não fazem isso em tais programas.
Daniel Earwicker
7
Não é realmente meu ponto de vista! Se você acha que o que estou dizendo é algo específico a ver com meu código, pode não ter me entendido. Estou falando sobre uma implicação inevitável da razão de shared_ptr existir em primeiro lugar: muitas vidas úteis de objeto não estão simplesmente relacionadas a chamadas de função.
Daniel Earwicker
8
@DanielEarwicker concorda totalmente com todos os seus pontos e estou surpreso com o nível de oposição. Algo que torna suas preocupações ainda mais relevantes é o threading, quando este se envolve as garantias sobre a validade de um objeto tornam-se muito mais importantes. Boa resposta.
radman
3
Não muito tempo atrás, eu persegui um bug muito sério que era devido à passagem de uma referência a um ponteiro compartilhado. O código estava lidando com a mudança de estado de um objeto e quando percebeu que o estado do objeto havia mudado, ele o removeu da coleção de objetos no estado anterior e o moveu para a coleção de objetos no novo estado. A operação de remoção destruiu o último ponteiro compartilhado para o objeto. A função de membro foi chamada em uma referência ao ponteiro compartilhado na coleção. Estrondo. Daniel Earwicker está certo.
David Schwartz
115

Eu me descobri discordando da resposta mais votada, então fui procurar opiniões de especialistas e aqui estão eles. De http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2011-Scott-Andrei-and-Herb-Ask-Us-Anything

Herb Sutter: "quando você passa por shared_ptrs, as cópias são caras"

Scott Meyers: "Não há nada de especial sobre shared_ptr quando se trata de passar por valor ou por referência. Use exatamente a mesma análise que você usa para qualquer outro tipo definido pelo usuário. As pessoas parecem ter essa percepção de que shared_ptr de alguma forma resolve todos os problemas de gestão, e que por ser pequeno é necessariamente barato passar por valor. Tem que ser copiado e tem um custo associado a isso ... é caro repassar por valor, então se eu puder com a semântica adequada no meu programa, vou passar por referência para const ou referência em vez disso "

Herb Sutter: "sempre os passe por referência para const, e muito ocasionalmente talvez porque você sabe que o que você chamou pode modificar a coisa de onde você obteve uma referência, talvez então você possa passar por valor ... se você copiá-los como parâmetros, oh meu Deus, você quase nunca precisa superar essa contagem de referência porque ela está sendo mantida viva de qualquer maneira, e você deveria estar passando por referência, então, por favor, faça isso "

Atualização: Herb expandiu isso aqui: http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/ , embora a moral da história seja que você não deveria estar passando shared_ptrs at all "a menos que você queira usar ou manipular o ponteiro inteligente em si, como para compartilhar ou transferir propriedade."

Jon
fonte
8
Belo achado! É ótimo ver dois dos maiores especialistas no assunto refutando publicamente a sabedoria convencional sobre SO.
Stephan Tolksdorf 01 de
3
"Não há nada de especial sobre shared_ptr quando se trata de passar por valor ou por referência" - eu realmente não concordo com isso. É especial. Pessoalmente, prefiro jogar pelo seguro e sofrer um leve impacto no desempenho. Se houver uma área específica do código que eu preciso otimizar, com certeza, eu examinaria os benefícios de desempenho de shared_ptr pass por const ref.
JasonZ
3
Também é interessante notar que, embora houvesse acordo sobre o uso excessivo de shared_ptrs, não havia acordo sobre a questão da passagem por valor versus referência.
Nicol Bolas
2
Scott Meyers: "então se eu puder me safar com a semântica adequada em meu programa ..." ou seja, não contradiz minha resposta, que aponta que descobrir se a alteração de parâmetros const &afetará a semântica só é fácil em muito programas simples.
Daniel Earwicker
7
Herb Sutter: "muito ocasionalmente, talvez porque você sabe que o que você chamou pode modificar o que você tem uma referência" Novamente, aquela pequena isenção para um caso menor, portanto, não contradiz minha resposta. A questão permanece: como você sabe que é seguro usar uma referência constante? Muito fácil de provar em um programa simples, não tão fácil em um programa complexo. Mas hey, isso é C ++, então nós favorecemos a micro-otimização prematura sobre quase todas as outras preocupações de engenharia, certo ?! :)
Daniel Earwicker
22

Eu desaconselho essa prática, a menos que você e os outros programadores com quem trabalha realmente saibam o que estão fazendo.

Primeiro, você não tem ideia de como a interface da sua classe pode evoluir e deseja evitar que outros programadores façam coisas ruins. Passar um shared_ptr por referência não é algo que um programador deveria esperar ver, porque não é idiomático e isso torna mais fácil usá-lo incorretamente. Programe defensivamente: torne a interface difícil de usar incorretamente. Passar por referência só vai atrair problemas mais tarde.

Em segundo lugar, não otimize até saber que esta classe em particular será um problema. Primeiro crie um perfil e, depois, se seu programa realmente precisar do impulso dado ao passar por referência, então talvez. Caso contrário, não se preocupe com as pequenas coisas (ou seja, as N instruções extras que são necessárias para passar por valor), em vez disso, se preocupe com o projeto, estruturas de dados, algoritmos e capacidade de manutenção de longo prazo.

Mark Kegel
fonte
Embora a resposta de litb seja tecnicamente correta, nunca subestime a "preguiça" dos programadores (eu também sou preguiçoso!). A resposta do littlenag é melhor, que uma referência a shared_ptr será inesperada e, possivelmente (provavelmente) uma otimização desnecessária que torna a manutenção futura mais desafiadora.
netjeff
18

Sim, é bom fazer uma referência. Você não pretende conceder propriedade compartilhada ao método; quer apenas trabalhar com ele. Você poderia pegar uma referência para o primeiro caso também, desde que você a copie de qualquer maneira. Mas, para o primeiro caso, ele assume a propriedade. Existe este truque para ainda copiá-lo apenas uma vez:

void ClassA::take_copy_of_sp(boost::shared_ptr<foo> sp) {
    m_sp_member.swap(sp);
}

Você também deve copiar ao devolvê-lo (ou seja, não retornar uma referência). Porque sua classe não sabe o que o cliente está fazendo com ela (ela poderia armazenar um ponteiro para ela e então o big bang aconteceria). Se mais tarde descobrir que é um gargalo (primeiro perfil!), Você ainda pode retornar uma referência.


Edit : Claro, como outros apontam, isso só é verdade se você conhece seu código e sabe que não redefiniu o ponteiro compartilhado passado de alguma forma. Na dúvida, basta passar por valor.

Johannes Schaub - litb
fonte
11

É sensato passar shared_ptrpor aqui const&. Provavelmente não causará problemas (exceto no caso improvável de que o referenciado shared_ptrseja excluído durante a chamada da função, conforme detalhado por Earwicker) e provavelmente será mais rápido se você passar muitos deles. Lembrar; o padrão boost::shared_ptré thread-safe, portanto, copiá-lo inclui um incremento thread-safe.

Tente usar em const&vez de apenas &, porque objetos temporários não podem ser passados ​​por referência não const. (Mesmo que uma extensão de idioma no MSVC permita que você faça isso de qualquer maneira)

Magnus Hoff
fonte
3
Sim, sempre uso referências const, apenas esqueci de colocá-lo no meu exemplo. De qualquer forma, o MSVC permite vincular referências não const a temporárias não por bug, mas porque por padrão tem a propriedade "C / C ++ -> Linguagem -> Desabilitar Extensão de Linguagem" definida como "NÃO". Habilite-o e ele não irá compilá-los ...
abigagli
abigagli: Sério? Doce! Vou fazer cumprir isso no trabalho amanhã cedo;)
Magnus Hoff
10

No segundo caso, fazer isso é mais simples:

Class::only_work_with_sp(foo &sp)
{    
    ...  
    sp.do_something();  
    ...  
}

Você pode chamá-lo de

only_work_with_sp(*sp);
Greg Rogers
fonte
3
Se você adotar a convenção de usar referências de objeto quando não precisar fazer uma cópia do ponteiro, isso também servirá para documentar sua intenção. Também oferece a oportunidade de usar uma referência const.
Mark Ransom
Sim, concordo com o uso de referências a objetos como um meio de expressar que a função chamada não está "lembrando" nada sobre esse objeto. Normalmente, eu uso argumentos formais de ponteiro se a função estiver "acompanhando" o objeto
abigagli
3

Eu evitaria uma referência "simples", a menos que a função explicitamente modifique o ponteiro.

A const &pode ser uma micro-otimização sensata ao chamar funções pequenas - por exemplo, para permitir otimizações adicionais, como inserir algumas condições em linha. Além disso, o incremento / decremento - uma vez que é seguro para thread - é um ponto de sincronização. Não esperaria que isso fizesse uma grande diferença na maioria dos cenários.

Geralmente, você deve usar o estilo mais simples, a menos que tenha motivos para não fazê-lo. Em seguida, use o de forma const &consistente ou adicione um comentário explicando o motivo, se você usá-lo apenas em alguns lugares.

Peterchen
fonte
3

Eu recomendaria passar um ponteiro compartilhado por referência const - uma semântica de que a função que está sendo passada com o ponteiro NÃO possui o ponteiro, que é um idioma limpo para desenvolvedores.

A única armadilha é em vários programas de thread, o objeto sendo apontado pelo ponteiro compartilhado é destruído em outro thread. Portanto, é seguro dizer que usar a referência const do ponteiro compartilhado é seguro em um programa de thread único.

Passar um ponteiro compartilhado por referência não const às vezes é perigoso - o motivo são as funções de troca e redefinição que a função pode invocar dentro de modo a destruir o objeto que ainda é considerado válido após o retorno da função.

Não se trata de otimização prematura, eu acho - trata-se de evitar desperdício desnecessário de ciclos de CPU quando você sabe o que deseja fazer e o idioma de codificação foi firmemente adotado por seus colegas desenvolvedores.

Apenas meus 2 centavos :-)

zmqiu
fonte
1
Veja o comentário acima de David Schwartz "... Eu persegui um bug muito sério que era devido à passagem de uma referência para um ponteiro compartilhado. O código estava tratando da mudança de estado de um objeto e quando percebeu que o estado do objeto havia mudado, ele o removeu da coleção de objetos no estado anterior e o moveu para a coleção de objetos no novo estado. A operação de remoção destruiu o último ponteiro compartilhado para o objeto. A função de membro foi chamada em uma referência ao ponteiro compartilhado na coleção. Boom ... "
Jason Harrison
3

Parece que todos os prós e contras aqui podem ser generalizados para QUALQUER tipo passado por referência, não apenas shared_ptr. Na minha opinião, você deve saber a semântica de passar por referência, referência const e valor e usá-la corretamente. Mas não há nada inerentemente errado em passar shared_ptr por referência, a menos que você pense que todas as referências são ruins ...

Para voltar ao exemplo:

Class::only_work_with_sp( foo &sp ) //Again, no copy here  
{    
    ...  
    sp.do_something();  
    ...  
}

Como você sabe que sp.do_something()não explodirá devido a um ponteiro pendurado?

A verdade é que, shared_ptr ou não, const ou não, isso pode acontecer se você tiver uma falha de design, como compartilhar direta ou indiretamente a propriedade spentre threads, usar incorretamente um objeto que tenha delete this, você tiver uma propriedade circular ou outros erros de propriedade.

Sandy
fonte
2

Algo que ainda não foi mencionado é que, ao passar ponteiros compartilhados por referência, você perde a conversão implícita que obtém se quiser passar um ponteiro compartilhado de classe derivada por meio de uma referência a um ponteiro compartilhado de classe base.

Por exemplo, este código produzirá um erro, mas funcionará se você alterar de test()forma que o ponteiro compartilhado não seja passado por referência.

#include <boost/shared_ptr.hpp>

class Base { };
class Derived: public Base { };

// ONLY instances of Base can be passed by reference.  If you have a shared_ptr
// to a derived type, you have to cast it manually.  If you remove the reference
// and pass the shared_ptr by value, then the cast is implicit so you don't have
// to worry about it.
void test(boost::shared_ptr<Base>& b)
{
    return;
}

int main(void)
{
    boost::shared_ptr<Derived> d(new Derived);
    test(d);

    // If you want the above call to work with references, you will have to manually cast
    // pointers like this, EVERY time you call the function.  Since you are creating a new
    // shared pointer, you lose the benefit of passing by reference.
    boost::shared_ptr<Base> b = boost::dynamic_pointer_cast<Base>(d);
    test(b);

    return 0;
}
Malvino
fonte
1

Presumirei que você esteja familiarizado com a otimização prematura e a esteja solicitando para fins acadêmicos ou porque isolou algum código pré-existente que está com baixo desempenho.

Passar por referência está certo

Passar por referência const é melhor e geralmente pode ser usado, já que não força a constância no objeto apontado.

Você não corre o risco de perder o ponteiro devido ao uso de uma referência. Essa referência é uma evidência de que você tem uma cópia do ponteiro inteligente anteriormente na pilha e apenas um thread possui uma pilha de chamadas, de modo que a cópia pré-existente não vai embora.

Usar referências geralmente é mais eficiente pelos motivos que você mencionou, mas não é garantido . Lembre-se de que cancelar a referência de um objeto também pode dar trabalho. Seu cenário de uso de referência ideal seria se seu estilo de codificação envolver muitas funções pequenas, onde o ponteiro seria passado de uma função para outra antes de ser usado.

Você deve sempre evitar armazenar seu ponteiro inteligente como referência. Seu Class::take_copy_of_sp(&sp)exemplo mostra o uso correto para isso.

Drew Dormann
fonte
1
"Você não corre o risco de perder o ponteiro devido ao uso de uma referência. Essa referência é uma evidência de que você tem uma cópia do ponteiro inteligente anteriormente na pilha" Ou um membro de dados ...?
Daniel Earwicker
Considere a magia de boost :: thread e boost :: ref: boost :: function <int> functionPointer = boost :: bind (doSomething, boost :: ref (sharedPtrInstance)); m_workerThread = novo boost :: thread (functionPointer); ... excluir sharedPtrInstance
Jason Harrison
1

Assumindo que não estamos preocupados com const exatidão (ou mais, você quer dizer permitir que as funções sejam capazes de modificar ou compartilhar a propriedade dos dados sendo transmitidos), passar um boost :: shared_ptr por valor é mais seguro do que passá-lo por referência como permitimos que o boost :: shared_ptr original controle seu próprio tempo de vida. Considere os resultados do seguinte código ...

void FooTakesReference( boost::shared_ptr< int > & ptr )
{
    ptr.reset(); // We reset, and so does sharedA, memory is deleted.
}

void FooTakesValue( boost::shared_ptr< int > ptr )
{
    ptr.reset(); // Our temporary is reset, however sharedB hasn't.
}

void main()
{
    boost::shared_ptr< int > sharedA( new int( 13 ) );
    boost::shared_ptr< int > sharedB( new int( 14 ) );

    FooTakesReference( sharedA );

    FooTakesValue( sharedB );
}

No exemplo acima, vemos que passar sharedA por referência permite que FooTakesReference redefina o ponteiro original, o que reduz sua contagem de uso para 0, destruindo seus dados. FooTakesValue , no entanto, não pode redefinir o ponteiro original, garantindo que os dados de sharedB ainda possam ser usados. Quando outro desenvolvedor inevitavelmente chega e tenta pegar carona na existência frágil de sharedA , o caos se instala . O sortudo desenvolvedor do sharedB , entretanto, vai para casa mais cedo, pois tudo está certo em seu mundo.

A segurança do código, neste caso, supera em muito qualquer melhoria de velocidade criada pela cópia. Ao mesmo tempo, o boost :: shared_ptr visa melhorar a segurança do código. Será muito mais fácil passar de uma cópia a uma referência, se algo exigir esse tipo de otimização de nicho.

Kit10
fonte
1

Sandy escreveu: "Parece que todos os prós e contras aqui podem ser generalizados para QUALQUER tipo passado por referência, não apenas shared_ptr."

Verdadeiro até certo ponto, mas o objetivo de usar shared_ptr é eliminar preocupações com relação à vida útil do objeto e deixar o compilador cuidar disso para você. Se você vai passar um ponteiro compartilhado por referência e permitir que os clientes do seu objeto contado por referência chamem métodos não const que podem liberar os dados do objeto, usar um ponteiro compartilhado é quase inútil.

Escrevi "quase" na frase anterior porque o desempenho pode ser uma preocupação e "pode" ser justificado em casos raros, mas eu também evitaria esse cenário e procuraria por mim mesmo todas as outras soluções de otimização possíveis, como olhar seriamente em adicionar outro nível de indireção, avaliação preguiçosa, etc.

Código que existe além de seu autor, ou mesmo posta sua memória de autor, que requer suposições implícitas sobre o comportamento, em particular o comportamento sobre a vida útil do objeto, requer documentação clara, concisa e legível, e muitos clientes não irão ler de qualquer maneira! A simplicidade quase sempre supera a eficiência e quase sempre há outras maneiras de ser eficiente. Se você realmente precisa passar valores por referência para evitar a cópia profunda por construtores de cópia de seus objetos de contagem de referência (e o operador igual), então talvez você deva considerar maneiras de fazer com que os dados copiados em profundidade sejam ponteiros de contagem de referência que podem ser copiado rapidamente. (Claro, esse é apenas um cenário de design que pode não se aplicar à sua situação).

Bill H
fonte
1

Eu costumava trabalhar em um projeto em que o princípio era muito forte sobre passar ponteiros inteligentes por valor. Quando me pediram para fazer uma análise de desempenho - descobri que para aumentar e diminuir os contadores de referência dos ponteiros inteligentes, o aplicativo gasta entre 4-6% do tempo do processador utilizado.

Se você deseja passar as dicas inteligentes por valor apenas para evitar problemas em casos estranhos, conforme descrito por Daniel Earwicker, certifique-se de entender o preço que está pagando por isso.

Se você decidir ir com uma referência, o principal motivo para usar a referência const é tornar possível ter um upcasting implícito quando você precisar passar um ponteiro compartilhado para o objeto da classe que herda a classe que você usa na interface.

gsf
fonte
0

Além do que litb disse, gostaria de salientar que provavelmente passará por referência const no segundo exemplo, dessa forma você tem certeza de não modificá-lo acidentalmente.

Leon Timmermans
fonte
0
struct A {
  shared_ptr<Message> msg;
  shared_ptr<Message> * ptr_msg;
}
  1. passar por valor:

    void set(shared_ptr<Message> msg) {
      this->msg = msg; /// create a new shared_ptr, reference count will be added;
    } /// out of method, new created shared_ptr will be deleted, of course, reference count also be reduced;
  2. passagem por referência:

    void set(shared_ptr<Message>& msg) {
     this->msg = msg; /// reference count will be added, because reference is just an alias.
     }
  3. passar por ponteiro:

    void set(shared_ptr<Message>* msg) {
      this->ptr_msg = msg; /// reference count will not be added;
    }
Dylan Chen
fonte
0

Cada pedaço de código deve ter algum sentido. Se você passar um ponteiro compartilhado por valor em todo o aplicativo, isso significa "Não tenho certeza sobre o que está acontecendo em outros lugares, portanto, sou a favor da segurança bruta ". Isso não é o que chamo de um bom sinal de confiança para outros programadores que podem consultar o código.

De qualquer forma, mesmo que uma função obtenha uma referência const e você não tiver certeza, ainda pode criar uma cópia do ponteiro compartilhado no cabeçalho da função, para adicionar uma referência forte ao ponteiro. Isso também pode ser visto como uma dica sobre o design ("o ponteiro pode ser modificado em outro lugar").

Então sim, IMO, o padrão deve ser " passar por referência const ".

Philippe
fonte