user1221647 user1221647 -4 years ago 125
C++ Question

How to dynamic cast from base to child class when the child is stored on a vector of base pointers

I have shared pointers of Child objects stored in a Vector of Base shared pointers and I need to dynamically cast the elements of the Base vector to its Child type, such that I can invoke function with Child specific signature.

An example follows below. The first code block defines the class hierarchy and the "identify" functions that I would like to use. The second code block gives a specific example of how I want to invoke a TYPE-specific "identify" function, given that I can cast the original object type from the Base class to a Child class (e.g. A,B,C).

Is there any pattern or technique that I can tackle this issue?

#include <iostream>
#include <memory>
#include <vector>

class Base {};
class A : public Base{};
class B : public Base{};
class C : public Base{};

class CollectionOfBase
{
public:
void add (std::shared_ptr<Base> item){m_items.push_back(item);}
std::vector<std::shared_ptr<Base>> const& getItems() const {return m_items;}

private:
std::vector<std::shared_ptr<Base>> m_items;
};

// I want to use these 3 functions instead of identify( std::shared_ptr<Base> const& )
void identify( std::shared_ptr<A> const& )
{
std::cout << "A" << std::endl;
}
void identify( std::shared_ptr<B> const& )
{
std::cout << "B" << std::endl;
}
void identify( std::shared_ptr<C> const& )
{
std::cout << "C" << std::endl;
}

//This function works in the below for loop, but this is not what I need to use
void identify( std::shared_ptr<Base> const& )
{
std::cout << "Base" << std::endl;
}


Below, you can find the second code block:

int main()
{
using namespace std;

CollectionOfBase collection;

collection.add(make_shared<A>());
collection.add(make_shared<A>());
collection.add(make_shared<C>());
collection.add(make_shared<B>());

for (auto const& x : collection.getItems())
{
// THE QUESTION:
// How to distinguish different type of items
// to invoke "identify" with object specific signatures (e.g. A,B,C) ???
// Can I cast somehow the object types that I push_back on my Collection ???
// Note that this loop does not know the add order AACB that we pushed the Child pointers.

identify(x);
}
/*
The desired output of this loop should be:

A
A
C
B

*/

return 0;
}


The code is also available on Ideone.

Answer Source

There's three approaches you could take here: OO and dynamic dispatch, visitor, and variant. Whichever one is better is going to depend on how many types you have and how many operations you have - as well as which one you're more likely to add to.

  1. Actually use OO. If you need each derived object to perform some operation in a different way, the way to do that in OO is to add a virtual member function:

    struct Base { virtual const char* name() = 0; };
    struct A : Base { const char* name() override { return "A"; }
    // ...
    
    for (auto const& x : collection.getItems()) {
        std::cout << x->name() << std::endl;
    }
    
  2. Use the visitor pattern. This is halfway between OO and functional - we create a base object that knows how to interact with all the types:

    struct Visitor;
    struct Base { virtual void visit(Visitor& ) = 0; };
    struct A;
    struct B;
    struct C;
    
    struct Visitor {
        virtual void visit(A& ) = 0;
        virtual void visit(B& ) = 0;
        virtual void visit(C& ) = 0;
    };
    
    struct A : Base { void visit(Visitor& v) override { v.visit(*this); } };
    // ...
    
    struct IdentityVisitor : Visitor {
        void visit(A& ) { std::cout << "A" << std::endl; }
        void visit(B& ) { std::cout << "B" << std::endl; }
        void visit(C& ) { std::cout << "C" << std::endl; }
    };
    
    IdentityVisitor iv;
    for (auto const& x : collection.getItems()) {
        x->visit(iv);
    }
    
  3. Just use a variant. Instead of storing a collection of shared_ptr<Base>, store a collection of variant<A,B,C> where those types aren't even in a hierarchy. They're just three arbitrary types. And then:

    for (auto const& x : collection.getItems()) {
        visit(overload(
            [](A const& ){ std::cout << "A" << std::endl; },
            [](B const& ){ std::cout << "B" << std::endl; },
            [](C const& ){ std::cout << "C" << std::endl; }
            ), x);
    }
    
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download