Assume the following class structure:
class Base;
class A : public Base;
class B : public Base;
class WrapperBase;
class AWrapper : public WrapperBase;
class BWrapper : public WrapperBase;
I would like to create some function like below
std::unique_ptr<WrapperBase> create(const Base* v);
That creates the appropriate "wrapper" class based upon the type of the "base" class passed in. To me, this means some sort of factory. However, I am not sure of the "best" approach to implement the factory. Here are some of the options I thought of:
Option A
Use a sequence of dynamic casts until the correct type is found:
std::unique_ptr<WrapperBase> create(const Base* v) {
A* a = dynamic_cast<const A*>(v);
if (a) return std::make_unique<AWrapper>(a);
B* b = dynamic_cast<const B*>(v);
if (b) return std::make_unique<BWrapper>(b);
return std::unique_ptr{};
}
The downside of this method is obviously performance as a) dynamic casts are slow and b) many dynamic casts have to potentially be performed and c) this requires RTTI to be enabled.
Option B
Use some for of "id" field for lookup.
Assume the Base
class has the following virtual method
enum class BaseID {
A,
B
};
virtual BaseID id() const = 0;
That could then be used as follows in the factory:
std::unique_ptr<WrapperBase> create(const Base* v) {
switch(v->id()) {
case BaseID::A:
return std::make_unique<AWrapper>(static_cast<const A*>(v));
case BaseID::B:
return std::make_unique<BWrapper>(static_cast<const B*>(v));
}
}
This is much faster, however I know that anytime a switch statement over object types appears, this smells of a bad design.
Option C
Use the visitor pattern. This would required the following modification:
class BaseVisitor;
class A;
class B;
class Base {
public:
virtual void accept(BaseVisitor& visitor) const = 0;
};
class BaseVisitor {
public:
virtual void visit(const A& v) = 0;
virtual void visit(const B& v) = 0;
};
class A : public Base {
public:
void accept(BaseVisitor& visitor) const override { visitor.visit(*this); }
};
//Same for B
class WrapperFactory : public BaseVisitor {
public:
std::unique_ptr<WrapperBase> create(const Base& v) {
v.accept(*this);
return std::move(_tmp);
}
void visit(const A& v) override {
_tmp = std::make_unique<AWrapper>(v);
}
void visit(const B& v) override {
_tmp = std::make_unique<AWrapper>(v);
}
private:
std::unique_ptr<WrapperBase> _tmp;
};
This seems a bit "clunky" to me, but perhaps it is a good solution.
My question is, is there a different solution I am not considering, which would be:
- Easy to maintain
- Easy to read
- Safe
- More "OOP friendly"