Passando shared_ptr <Derived> como shared_ptr <Base>

95

Qual é o melhor método para passar a shared_ptrde um tipo derivado para uma função que recebe a shared_ptrde um tipo base?

Eu geralmente passo shared_ptrs por referência para evitar uma cópia desnecessária:

int foo(const shared_ptr<bar>& ptr);

mas isso não funciona se eu tentar fazer algo como

int foo(const shared_ptr<Base>& ptr);

...

shared_ptr<Derived> bar = make_shared<Derived>();
foo(bar);

eu poderia usar

foo(dynamic_pointer_cast<Base, Derived>(bar));

mas isso parece abaixo do ideal por dois motivos:

  • A dynamic_castparece um pouco excessivo para um elenco simples de derivado para base.
  • Pelo que entendi, dynamic_pointer_castcria uma cópia (embora temporária) do ponteiro para passar para a função.

Existe uma solução melhor?

Atualização para a posteridade:

Era um problema de falta de um arquivo de cabeçalho. Além disso, o que eu estava tentando fazer aqui é considerado um antipadrão. Geralmente,

  • Funções que não afetam a vida útil de um objeto (ou seja, o objeto permanece válido durante a função) devem receber uma referência simples ou ponteiro, por exemplo int foo(bar& b).

  • Funções que consomem um objeto (ou seja, são os usuários finais de um determinado objeto) devem assumir um unique_ptrvalor por, por exemplo int foo(unique_ptr<bar> b). Os chamadores devem std::moveinserir o valor na função.

  • As funções que estendem a vida útil de um objeto devem assumir um shared_ptrvalor por, por exemplo int foo(shared_ptr<bar> b). O conselho usual para evitar referências circulares se aplica.

Consulte a palestra de volta ao básico de Herb Sutter para obter detalhes.

Matt Kline
fonte
8
Por que você quer passar um shared_ptr? Por que nenhuma referência constante da barra?
ipc
2
Qualquer dynamic elenco é necessário apenas para downcasting. Além disso, passar o ponteiro derivado deve funcionar perfeitamente. Ele irá criar um novo shared_ptrcom o mesmo refcount (e aumentá-lo) e um ponteiro para a base, que então se liga à referência const. Uma vez que você já está pegando uma referência, no entanto, não vejo por que você quer fazer shared_ptrisso. Faça uma Base const&ligação foo(*bar).
Xeo
@Xeo: Passar o ponteiro derivado (ou seja foo(bar)) não funciona, pelo menos no MSVC 2010.
Matt Kline
1
O que você quer dizer com "obviamente não funciona"? O código é compilado e se comporta corretamente; você está perguntando como evitar a criação de um temporário shared_ptrpara passar para a função? Tenho quase certeza de que não há como evitar isso.
Mike Seymour
1
@ Set: Eu discordo. Acho que há razão para passar um ponteiro compartilhado por valor, e há muito pouca razão para passar um ponteiro compartilhado por referência (e tudo isso sem defender cópias desnecessárias). Raciocinando aqui stackoverflow.com/questions/10826541/…
R. Martinho Fernandes

Respostas:

48

Embora Basee Derivedsejam covariantes, os ponteiros brutos para eles agirão de acordo shared_ptr<Base>e nãoshared_ptr<Derived> são covariantes. Esta é a maneira correta e mais simples de lidar com esse problema.dynamic_pointer_cast

( Editar: static_pointer_cast seria mais apropriado porque você está transmitindo do derivado para a base, o que é seguro e não requer verificações de tempo de execução. Veja os comentários abaixo.)

No entanto, se sua foo()função não deseja participar do prolongamento da vida útil (ou, melhor, participar da propriedade compartilhada do objeto), então é melhor aceitar const Base&ae desreferenciá-la shared_ptrao passá-la para foo().

void foo(const Base& base);
[...]
shared_ptr<Derived> spDerived = getDerived();
foo(*spDerived);

Como um aparte, porque shared_ptr tipos não podem ser covariantes, as regras de conversões implícitas entre os tipos de retorno covariante não se aplicam ao retornar tipos de shared_ptr<T>.

Bret Kuhns
fonte
39
Eles não são covariantes, mas shared_ptr<Derived>são implicitamente conversíveis em shared_ptr<Base>, então o código deve funcionar sem travessuras de casting.
Mike Seymour
9
Hum, shared_ptr<Ty>tem um construtor que leva ae shared_ptr<Other>faz a conversão apropriada se Ty*for implicitamente conversível para Other*. E se um gesso for necessário, static_pointer_casté o apropriado aqui, não dynamic_pointer_cast.
Pete Becker
Verdade, mas não com seu parâmetro de referência, como na pergunta. Ele precisaria fazer uma cópia, independentemente. Mas, se ele está usando refs to shared_ptrde para evitar a contagem de referência, então não há realmente nenhuma boa razão para usar um shared_ptrem primeiro lugar. É melhor usar em seu const Base&lugar.
Bret Kuhns
@PeteBecker Veja meu comentário para Mike sobre o construtor de conversão. Eu honestamente não sabia static_pointer_cast, obrigado.
Bret Kuhns de
1
@TanveerBadar Não tenho certeza. Talvez isso não tenha sido compilado em 2012? (especificamente usando Visual Studio 2010 ou 2012). Mas você está absolutamente certo, o código do OP deve ser absolutamente compilado se a definição completa de uma classe / derivada publicamente estiver visível para o compilador.
Bret Kuhns de
34

Isso também acontecerá se você se esquecer de especificar a herança pública na classe derivada, ou seja, se, como eu, você escrever isto:

class Derived : Base
{
};
pastor
fonte
classé para parâmetros de modelo; structé para definir classes. (Isso é no máximo 45% uma piada.)
Davis Herring
Definitivamente, esta deve ser considerada a solução, não há necessidade de gesso, pois só falta público.
Alexis Paques,
12

Parece que você está se esforçando demais. shared_ptré barato para copiar; esse é um de seus objetivos. Repassá-los por referência não faz muito. Se você não quiser compartilhar, passe o ponteiro bruto.

Dito isso, há duas maneiras de fazer isso que posso imaginar de início:

foo(shared_ptr<Base>(bar));
foo(static_pointer_cast<Base>(bar));
Pete Becker
fonte
9
Não, eles não são baratos para copiar, eles devem ser passados ​​por referência sempre que possível.
Seth Carnegie
6
@SethCarnegie - o Herb perfilou seu código para ver se a passagem de valor era um gargalo?
Pete Becker de
25
@SethCarnegie - isso não responde à pergunta que fiz. E, pelo que vale a pena, escrevi a shared_ptrimplementação que a Microsoft envia.
Pete Becker de
6
@SethCarnegie - você tem a heurística reversa. Otimizações de mão geralmente não devem ser feitas a menos que você possa mostrar que são necessárias.
Pete Becker de
21
É apenas uma otimização "prematura" se você precisar trabalhar nisso. Não vejo problema em adotar expressões eficientes em vez de ineficientes, independentemente de fazer diferença em um determinado contexto ou não.
Mark Ransom
11

Verifique também se #includeo arquivo de cabeçalho que contém a declaração completa da classe derivada está em seu arquivo de origem.

Eu tive esse problema. O std::shared<derived>não seria lançado std::shared<base>. Eu havia declarado as duas classes para que pudesse conter ponteiros para elas, mas, como não tinha o, #includeo compilador não conseguia ver que uma classe era derivada da outra.

Phil Rosenberg
fonte
1
Uau, eu não esperava, mas isso resolveu para mim. Eu estava sendo extremamente cuidadoso e incluindo apenas arquivos de cabeçalho onde eu precisava, então alguns deles estavam apenas em arquivos de origem e encaminhei declarando-os em cabeçalhos como você disse.
jigglypuff
Compilador estúpido é estúpido. Esse era o meu problema. Obrigado!
Tanveer Badar