c++ - weak_ptr - std::make_shared




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

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 objeto shared_ptr )? 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();  
    ...  
}  

Eu não consigo ver em ambos os casos uma boa razão para passar o boost::shared_ptr<foo> por valor ao invés de por referência. Passar por valor iria apenas "temporariamente" incrementar a contagem de referência devido à cópia e, em seguida, decrementá-la ao sair do escopo da função. Eu estou negligenciando alguma coisa?

Só para esclarecer, depois de ler várias respostas: Concordo perfeitamente com as preocupações de otimização prematura, e sempre procuro primeiro-profile-then-work-on-the-hotspots. Minha pergunta era mais de um ponto de vista de código puramente técnico, se você sabe o que quero dizer.


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

Tente usar 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)


Além do que litb disse, gostaria de salientar que é provavelmente passar pela referência const no segundo exemplo, assim você tem certeza de que não o modifica acidentalmente.


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

Passando por referência está bem

Passar pela referência const é melhor, e geralmente pode ser usado, pois 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 segmento possui uma pilha de chamadas, para que a cópia pré-existente não desapareça.

O uso de referências costuma ser mais eficiente pelas razões mencionadas, mas não garantidas . Lembre-se de que a referência a um objeto pode levar o trabalho também. Seu cenário ideal de uso de referência seria se o seu estilo de codificação envolvesse muitas pequenas funções, onde o ponteiro passaria de função para função para função antes de ser usado.

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


Cada pedaço de código deve ter algum sentido. Se você passar um ponteiro compartilhado por valor em todos os lugares do aplicativo, isso significa "Não tenho certeza sobre o que está acontecendo em outro lugar, por isso eu prefiro a segurança bruta ". Isso não é o que eu chamo de um bom sinal de confiança para outros programadores que poderiam consultar o código.

De qualquer forma, mesmo que uma função obtenha uma referência const e você esteja "inseguro", ainda é possível criar uma cópia do ponteiro compartilhado no início da função, para adicionar uma referência forte ao ponteiro. Isso também pode ser visto como uma sugestão 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 ".


Eu costumava trabalhar em um projeto 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 incrementar e decrementar os contadores de referência dos ponteiros inteligentes, o aplicativo gasta entre 4-6% do tempo do processador utilizado.

Se você quiser passar os ponteiros inteligentes por valor apenas para evitar problemas em casos estranhos, conforme descrito por Daniel Earwicker, certifique-se de entender o preço pago por ele.

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


Eu defendo a passagem do ponteiro compartilhado pela referência const - uma semântica que a função que está sendo passada com o ponteiro NÃO possui o ponteiro, que é um idioma limpo para os desenvolvedores.

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

Passar ponteiro compartilhado por referência não-const é, às vezes, perigoso - o motivo são as funções de swap e reset 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 o desperdício desnecessário de ciclos de CPU quando você está claro o que você quer fazer e o idioma de codificação foi firmemente adotado por seus colegas desenvolvedores.

Apenas meus 2 centavos :-)


Eu me vi discordando da resposta mais votada, então fui procurar por 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 o shared_ptr, as cópias são caras"

Scott Meyers: "Não há nada de especial sobre shared_ptr quando se trata de passá-lo por valor, ou passá-lo 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 que shared_ptr de alguma forma resolve todos os problemas de gerenciamento, e por ser pequeno, é necessariamente barato passar por valor, tem que ser copiado, e há um custo associado a isso ... é caro passá-lo por valor, então se eu puder me safar com a semântica adequada no meu programa, eu vou passar por referência a const ou referência em vez disso "

Herb Sutter: "sempre os passe por referência a const, e muito ocasionalmente talvez porque você sabe que o que você chamou pode modificar a coisa da qual você tem uma referência, talvez então você possa passar por valor ... se copiá-los como parâmetros, oh minha benevolência você quase nunca precisa bater essa contagem de referência porque está sendo mantida viva de qualquer maneira, e você deveria estar passando isto 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_ptr's "a menos que você queira usar ou manipular o próprio ponteiro inteligente, como compartilhar ou transferir a propriedade."


No segundo caso, isso é mais simples:

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

Você pode chamá-lo como

only_work_with_sp(*sp);

Parece que todos os prós e contras aqui podem ser generalizados para QUALQUER tipo passado por referência e não apenas shared_ptr. Na minha opinião, você deve saber a semântica de passar por referência, const referência e valor e usá-lo corretamente. Mas não há absolutamente 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 vai explodir devido a um ponteiro pendente?

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


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

É verdade até certo ponto, mas o objetivo de usar shared_ptr é eliminar as preocupações relacionadas à vida útil do objeto e permitir que o compilador manipule isso para você. Se você for passar um ponteiro compartilhado por referência e permitir que clientes de sua chamada de objeto contado de referência não sejam métodos const que possam liberar os dados do objeto, então usar um ponteiro compartilhado é quase inútil.

Escrevi "quase" nessa sentença anterior porque o desempenho pode ser uma preocupação, e isso "pode" ser justificado em casos raros, mas também evitarei esse cenário e buscarei todas as outras possíveis soluções de otimização, por exemplo: em adicionar outro nível de indireção, avaliação preguiçosa, etc.

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


Uma coisa que eu não vi ainda é que quando você passa ponteiros compartilhados por referência, você perde a conversão implícita que você obtém se você quiser passar um ponteiro compartilhado de classe derivada por meio de uma referência a um ponteiro compartilhado da classe base.

Por exemplo, esse código produzirá um erro, mas funcionará se você alterar test() para 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;
}

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. passar 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 pelo ponteiro:

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




shared-ptr