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:
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.
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.