Search code examples
c++shared-ptr

How to switch off correctly?


This might be a case for the switch-off rule explained in C++ coding standards and I am wondering if I am doing it correctly. I am wondering because I still have if-clauses in the switching function.

Class A never gets instantiated directly, it's always either B or C that get dynamically created and uniformly handled through a (shared) pointer to A. foo switches and selects the operation depending on whether it's an B or C.

class A {
public:
  virtual ~A(){}
};

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

typedef std::shared_ptr<A> Aptr;
typedef std::shared_ptr<B> Bptr;
typedef std::shared_ptr<C> Cptr;


template<class T>
std::shared_ptr<T> get(const Aptr& pA) {
  return std::dynamic_pointer_cast< T >( pA );
}

void foo( const Bptr& pB ) {
  std::cout << "operate on B\n";
}

void foo( const Cptr& pC ) {
  std::cout << "operate on C\n";
}

void foo( const Aptr& pA ) {
  if ( auto x = get<B>(pA) ) {
    foo(x);
    return;
  }
  if ( auto x = get<C>(pA) ) {
    foo(x);
    return;
  }
  assert(!"oops");
}


int main()
{
  Aptr pA( new C );

  foo( pA );
}

My question is whether void foo( const Aptr& pA ) can be implemented more elegantly. That could mean without if. Is throwing in get and catching in foo recommended in this situation?


Solution

  • Unless you have good reasons for doing otherwise (and if you have them, your code does not show them), this seems to me like the typical use case for dynamic polymorphism achieved through a virtual function:

    class A 
    {
    public:
        virtual ~A() {}
        virtual void foo() = 0;
    };
    
    class B : public A 
    {
        virtual void foo() 
        {
            std::cout << "operate on B\n";
        }
    };
    
    class C : public A 
    {
        virtual void foo() 
        {
            std::cout << "operate on B\n";
        }
    };
    

    Besides, in C++11 it is preferable to use std::make_shared<>() over the construction of a shared_ptr with a naked new allocation (again, unless you have good reasons to do otherwise):

    int main()
    {
        Aptr pA = std::make_shared<C>();
        pA->foo();
    }
    

    If you have reasons not to use virtual functions and prefer a different, non-intrusive kind of polymorphism, you may use Boost.Variant in combination with boost::static_visitor. This does not even require B and C to be related.

    #include <boost/variant.hpp>
    #include <memory>
    #include <iostream>
    
    class B /* ... */ {};
    class C /* ... */ {};
    
    // ...
    typedef std::shared_ptr<B> Bptr;
    typedef std::shared_ptr<C> Cptr;
    
    struct foo_visitor : boost::static_visitor<void>
    {
        void operator () (Bptr p)
        {
            std::cout << "operate on B\n";
        }
    
        void operator () (Cptr p)
        {
            std::cout << "operate on C\n";
        }
    };
    
    int main()
    {
        boost::variant<Bptr, Cptr> ptr;
        ptr = std::make_shared<C>();
    
        foo_visitor v;
        ptr.apply_visitor(v);
    }
    

    This approach is pretty similar to the one you chose, except that Boost.Variant also makes sure you are not forgetting to include a handling case for each of the values the variant could possibly assume (in this case, Bptr and Cptr).