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?
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
).