Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is a big switch block unavoidable in C++ due to lack of reflection [duplicate]

Tags:

c++

oop

factory

Assume I have a hierarchy of classes:

class Shape {
};

class Circle : public Shape {
}

class Square : public Shape {
}

... hundreds of other shapes continue on...

When given the name of a shape class as a string, I need to instantiate objects of that class.

In java, I can do something like this (pseudo code!)

Shape createShape(String name) {
    return new Class.forName(name);
}

But in C++, I have to do this: (pseudo code!)

Shape * createShape(const string &name) {
    if (name.compare("Circle") == 0) {  
        return new Circle();
    }
    else if (name.compare("Square") == 0) {
        return new Square();
    }
    else if ... //hundreds of else if continues, one for each shape
}

Is there any better way in C++ to handle situation like this?

like image 598
ROTOGG Avatar asked Jul 26 '13 04:07

ROTOGG


2 Answers

It's avoidable using the factory pattern, but you still need a bunch of boilerplate code to get off the ground. For example:

// Class factory functions -- these could also be inlined into their respective
// class definitions using a macro
Shape *createCircle() { return new Circle(); }
Shape *createSquare() { return new Square(); }
// etc.

// Create a map from type name to factory
typedef std::map<std::string, Shape *(*)()> ShapeFactoryMap;
ShapeFactoryMap factoryMap;
factoryMap["Circle"] = &createCircle;
factoryMap["Square"] = &createSquare;
// etc.

Then, when you want to instantiate an object, you can do this:

ShapeFactoryMap::iterator factory = factoryMap.find("Circle");
if (factory != factoryMap.end())
{
    Shape *circle = factory->second();  // Creates a Circle instance
    ...
}
else
{
    // Handle error
}

Whether this is better than just doing a series of if/else... string comparisons is not clear, since it depends on what exactly you're doing to be doing with this.

like image 52
Adam Rosenfield Avatar answered Oct 09 '22 20:10

Adam Rosenfield


I second Adam Rosenfield's solution using maps. However, a lower level interface to get your higher level functionality is to use a dlsym() lookup.

Assume that your generic Shape interface lies in the file Shape.hpp and has the following form:

class Shape {
public:
    virtual ~Shape () {}
    //...virtual methods
    virtual void draw () const = 0;
};

template <typename DERIVED>
class ShapeBridge : public Shape {
public:
    static Shape * create () { return new DERIVED; }
};

struct ShapeFactory {
    Shape * (*create) ();
};

Suppose you wanted to add a new shape dynamically by creating a new shared object, and then linking it dynamically into your existing running executable. Then, you can now create an abstract factory of sorts, which uses dynamic loading of shared objects to obtain the concrete factory functions:

#include <string>
#include <map>
#include <dlfcn.h>

struct ShapeCreator {
    void *dlhandle_;
    void *factory_;
    ShapeCreator () : dlhandle_(0), factory_(0) {}
    void open (std::string libname) {
        dlhandle_ = dlopen(libname.c_str(), RTLD_LAZY);
        factory_ = dlsym(dlhandle_, "factory");
    }
    void close () { if (dlhandle_) dlclose(dlhandle_); }
    ShapeFactory * factory () const {
        return static_cast<ShapeFactory *>(factory_);
    }
    static Shape * create (std::string name) {
        static std::map<std::string, ShapeCreator> lookup;
        static std::string dir = "./";
        if (lookup[name].factory() == 0) {
            lookup[name].open(dir + name + ".so");
    }
        return lookup[name].factory()->create();
    }
};

Your shared object could have the following implementation:

// gcc -fPIC  -shared -Wl,-export-dynamic -o Circle.so Circle.cpp -lc
#include "Shape.hpp"
#include <iostream>

class Circle : public ShapeBridge<Circle> {
public:
    //..
    void draw () const { std::cout << "I am a circle.\n"; }
};

extern "C" {
    ShapeFactory factory = { Circle::create };
}

Then to dynamically create the shape:

    Shape *s = ShapeCreator::create("Circle");
    s->draw();

Of course, the example is a little more interesting if it actually obtained its name dynamically (like from a configuration file, or from a user input).

like image 44
jxh Avatar answered Oct 09 '22 22:10

jxh