Retorna ponteiros inteligentes por valor.
Como você disse, se você devolvê-lo por referência, não incrementará adequadamente a contagem de referência, o que abre o risco de deletar algo no momento impróprio. Só isso já deve ser motivo suficiente para não voltar por referência. As interfaces devem ser robustas.
A preocupação com os custos é discutível hoje em dia graças à otimização do valor de retorno (RVO), então você não irá incorrer em uma seqüência incremento-incremento-decremento ou algo parecido em compiladores modernos. Portanto, a melhor maneira de retornar a shared_ptr
é simplesmente retornar por valor:
shared_ptr<T> Foo()
{
return shared_ptr<T>(/* acquire something */);
};
Esta é uma oportunidade RVO óbvia para os compiladores C ++ modernos. Eu sei que os compiladores Visual C ++ implementam RVO mesmo quando todas as otimizações estão desligadas. E com a semântica de movimentação do C ++ 11, essa preocupação é ainda menos relevante. (Mas a única maneira de ter certeza é traçar um perfil e experimentar.)
Se você ainda não está convencido, Dave Abrahams tem um artigo que defende o retorno por valor. Eu reproduzo um trecho aqui; Eu recomendo fortemente que você leia o artigo inteiro:
Seja honesto: como você se sente com o código a seguir?
std::vector<std::string> get_names();
...
std::vector<std::string> const names = get_names();
Francamente, embora eu devesse saber melhor, isso me deixa nervoso. Em princípio, quando get_names()
retorna, temos que copiar a vector
de string
s. Então, precisamos copiá-lo novamente quando inicializamos
names
e precisamos destruir a primeira cópia. Se houver N string
s no vetor, cada cópia pode exigir até N + 1 alocações de memória e uma grande quantidade de acessos de dados hostis ao cache> conforme o conteúdo da string é copiado.
Em vez de enfrentar esse tipo de ansiedade, muitas vezes recorri à passagem por referência para evitar cópias desnecessárias:
get_names(std::vector<std::string>& out_param );
...
std::vector<std::string> names;
get_names( names );
Infelizmente, essa abordagem está longe de ser ideal.
- O código cresceu 150%
- Tivemos que abandonar o
const
-ness porque estamos mudando nomes.
- Como os programadores funcionais gostam de nos lembrar, a mutação torna o código mais complexo para raciocinar ao minar a transparência referencial e o raciocínio equacional.
- Não temos mais semânticas de valor estrito para nomes.
Mas é realmente necessário bagunçar nosso código dessa forma para ganhar eficiência? Felizmente, a resposta acabou sendo não (e especialmente se você estiver usando C ++ 0x).
cout << "Hello World!";
instrução em um construtor padrão e de cópia, você não verá doisHello World!
s quando RVO estiver em vigor. No entanto, isso não deve ser um problema para ponteiros inteligentes projetados corretamente, mesmo para sincronização errada.Com relação a qualquer ponteiro inteligente (não apenas shared_ptr), não acho que seja aceitável retornar uma referência a um, e ficaria muito hesitante em passá-los por referência ou ponteiro bruto. Por quê? Porque você não pode ter certeza de que não será copiado superficialmente por meio de uma referência posterior. Seu primeiro ponto define o motivo pelo qual isso deve ser uma preocupação. Isso pode acontecer até mesmo em um ambiente de thread único. Você não precisa de acesso simultâneo a dados para colocar semântica de cópia incorreta em seus programas. Você não controla realmente o que seus usuários fazem com o ponteiro depois de passá-lo, então não incentive o uso indevido, dando aos usuários da API corda suficiente para se enforcarem.
Em segundo lugar, observe a implementação do seu ponteiro inteligente, se possível. A construção e a destruição devem ser quase insignificantes. Se essa sobrecarga não for aceitável, não use um ponteiro inteligente! Mas, além disso, você também precisará examinar a arquitetura de simultaneidade que possui, porque o acesso mutuamente exclusivo ao mecanismo que rastreia os usos do ponteiro vai deixá-lo mais lento do que a mera construção do objeto shared_ptr.
Editar, 3 anos depois: com o advento dos recursos mais modernos em C ++, eu ajustaria minha resposta para aceitar mais os casos em que você simplesmente escreveu um lambda que nunca vive fora do escopo da função de chamada, e não é copiado em outro lugar. Aqui, se você quiser economizar o mínimo de sobrecarga de copiar um ponteiro compartilhado, seria justo e seguro. Por quê? Porque você pode garantir que a referência nunca será mal utilizada.
fonte