Fazendo downcast de shared_ptr <Base> para shared_ptr <Derived>?

102

Update: o shared_ptr neste exemplo é como o do Boost, mas não suporta shared_polymorphic_downcast (ou dynamic_pointer_cast ou static_pointer_cast para esse assunto)!

Estou tentando inicializar um ponteiro compartilhado para uma classe derivada sem perder a contagem de referência:

struct Base { };
struct Derived : public Base { };
shared_ptr<Base> base(new Base());
shared_ptr<Derived> derived;

// error: invalid conversion from 'Base* const' to 'Derived*'
derived = base;  

Por enquanto, tudo bem. Eu não esperava que o C ++ convertesse implicitamente Base * em Derivado *. No entanto, eu quero a funcionalidade expressa pelo código (ou seja, manter a contagem de referência enquanto reduz o ponteiro de base). Meu primeiro pensamento foi fornecer um operador de elenco na Base para que uma conversão implícita para Derivado pudesse ocorrer (para pedantes: eu verificaria se o cast para baixo é válido, não se preocupe):

struct Base {
  operator Derived* ();
}
// ...
Base::operator Derived* () {
  return down_cast<Derived*>(this);
}

Bem, não ajudou. Parece que o compilador ignorou completamente meu operador typecast. Alguma ideia de como eu poderia fazer a atribuição shared_ptr funcionar? Para pontos extras: que tipo de tipo Base* consté? const Base*Eu entendo, mas Base* const? A que se constrefere neste caso?

Lajos Nagy
fonte
Por que você precisa de um shared_ptr <Derived>, em vez de shared_ptr <Base>?
Bill
3
Porque eu quero acessar funcionalidade em Derived que não está em Base, sem clonar o objeto (eu quero um único objeto, referenciado por dois ponteiros compartilhados). A propósito, por que os operadores de elenco não funcionam?
Lajos Nagy

Respostas:

109

Você pode usar dynamic_pointer_cast. É apoiado por std::shared_ptr.

std::shared_ptr<Base> base (new Derived());
std::shared_ptr<Derived> derived =
               std::dynamic_pointer_cast<Derived> (base);

Documentação: https://en.cppreference.com/w/cpp/memory/shared_ptr/pointer_cast

Além disso, não recomendo usar o operador de elenco na classe base. Casting implícito como esse pode se tornar a fonte de bugs e erros.

-Atualizar: Se o tipo não for polimórfico, std::static_pointer_castpode ser usado.

Massood Khaari
fonte
4
Não entendi desde a primeira linha que ele não está usando std::shared_ptr. Mas pelos comentários da primeira resposta eu inferi que ele não está usando boost, então pode estar usando std::shared_ptr.
Massood Khaari
ESTÁ BEM. Desculpe. Ele deve esclarecer melhor que está usando uma implementação customizada.
Massood Khaari
47

Suponho que você esteja usando boost::shared_ptr... Acho que você quer dynamic_pointer_castou shared_polymorphic_downcast.

No entanto, eles requerem tipos polimórficos.

que tipo de tipo Base* consté? const Base*Eu entendo, mas Base* const? A que se constrefere neste caso?

  • const Base *é um ponteiro mutável para uma constante Base.
  • Base const *é um ponteiro mutável para uma constante Base.
  • Base * consté um ponteiro constante para um mutável Base.
  • Base const * consté um ponteiro constante para uma constante Base.

Aqui está um exemplo mínimo:

struct Base { virtual ~Base() { } };   // dynamic casts require polymorphic types
struct Derived : public Base { };

boost::shared_ptr<Base> base(new Base());
boost::shared_ptr<Derived> derived;
derived = boost::static_pointer_cast<Derived>(base);
derived = boost::dynamic_pointer_cast<Derived>(base);
derived = boost::shared_polymorphic_downcast<Derived>(base);

Não tenho certeza se foi intencional que seu exemplo crie uma instância do tipo base e a projete, mas serve para ilustrar bem a diferença.

A static_pointer_castvontade "apenas faça". Isso resultará em um comportamento indefinido (um Derived*apontamento para a memória alocada e inicializada por Base) e provavelmente causará um travamento ou pior. A contagem de referência baseserá incrementada.

O dynamic_pointer_castresultará em um ponteiro nulo. A contagem de referência em basepermanecerá inalterada.

O shared_polymorphic_downcastterá o mesmo resultado que um elenco estático, mas acionará uma afirmação, em vez de parecer bem-sucedido e levar a um comportamento indefinido. A contagem de referência baseserá incrementada.

Veja (link morto) :

Às vezes é um pouco difícil decidir se usará static_castou dynamic_cast, e você gostaria de ter um pouco dos dois mundos. É bem sabido que dynamic_cast tem uma sobrecarga de tempo de execução, mas é mais seguro, enquanto o static_cast não tem sobrecarga, mas pode falhar silenciosamente. Como seria bom se você pudesse usar shared_dynamic_castem compilações de depuração e shared_static_castem compilações de lançamento. Bem, tal coisa já está disponível e é chamada shared_polymorphic_downcast.

Tim Sylvester
fonte
Infelizmente, sua solução depende da funcionalidade Boost que foi deliberadamente excluída da implementação de shared_ptr particular que estamos usando (não pergunte por quê). Quanto à explicação const, faz muito mais sentido agora.
Lajos Nagy
3
Além de implementar os outros shared_ptrconstrutores (taking static_cast_tage dynamic_cast_tag), não há muito que você possa fazer. Qualquer coisa que você fizer fora shared_ptrnão será capaz de gerenciar o refcount. - Em um design OO "perfeito", você sempre pode usar o tipo base, e nunca precisa saber nem se importar com o que é o tipo derivado, porque toda a sua funcionalidade é exposta por meio de interfaces de classe base. Talvez você só precise repensar por que precisa fazer o down-cast em primeiro lugar.
Tim Sylvester
1
@Tim Sylvester: mas C ++ não é uma linguagem OO "perfeita"! :-) down-casts têm seu lugar em uma linguagem OO não perfeita
Steve Folly
4

Se alguém chegar aqui com boost :: shared_ptr ...

É assim que você pode fazer o downcast para o Boost derivado shared_ptr. Assumindo que Derived herda da Base.

boost::shared_ptr<Base> bS;
bS.reset(new Derived());

boost::shared_ptr<Derived> dS = boost::dynamic_pointer_cast<Derived,Base>(bS);
std::cout << "DerivedSPtr  is: " << std::boolalpha << (dS.get() != 0) << std::endl;

Certifique-se de que a classe / estrutura 'Base' tenha pelo menos uma função virtual. Um destruidor virtual também funciona.

Mitendra
fonte