Is a large blocking block inevitable in C ++ due to lack of reflection

Suppose I have a class hierarchy:

class Shape { }; class Circle : public Shape { } class Square : public Shape { } ... hundreds of other shapes continue on... 

When the form class name is given as a string, I need to create objects of this 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: (pseudocode!)

 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 a better way in C ++ to handle this situation?

+6
source share
6 answers

This can be avoided with the factory template, but you still need a bunch of template code to get off the ground. For instance:

 // 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 string comparisons if/else... is unclear, as it depends on what exactly you are doing with it.

+6
source

I Adam Rosenfield's second solution using map s. However, a lower-level interface for getting higher-level functionality is a dlsym() lookup.

Suppose your common Shape interface is in the Shape.hpp file 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 want to dynamically add a new shape by creating a new shared object and then dynamically linking it to an existing executable executable. Then you can create an abstract factory sort, which uses the dynamic loading of shared objects to get specific 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 may 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 form:

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

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

+1
source

There is no support for what you speak this language. However, you can use the following template to optimize your design:

 class Shape { Shape *CreateShape(const char *name) { // Iterate single linked list of known derived classes. Node *item = ListOfDerivedClasses; while (item != NULL) { if (strcmp(item->name, name) == 0) return item->factory(); item = item->next; } } typedef Shape *CreateShapeInstance(); struct Node { char *name; CreateShapeInstance *factory; Node *next; Node(char *n, CreateShapeInstance *f) { name = n; factory = f; next = Shape::ListOfDerivedClasses; Shape::ListOfDerivedClasses = this; } }; static Node *ListOfDerivedClasses; }; class Circle : public Shape { static Shape *CreateInstance() { return new Circle(); } } static Shape::Node circle_info("Circle", Circle::CreateInstance); 

The idea is that when you initialize static objects, a single linked list is created containing only static elements, and after that it does not change. This design allows you to add derived classes without changing the base class, and CreateShape in the base class can create any derived class registered in the list.

0
source

The main difference is that, unlike Java, C ++ does not have a built-in function like forName(String) that performs this task for you. In C ++ you have to implement it.

Now it’s important how you do it. The proposed switch/case method is one way that is straightforward but long. You can automate things:

(1) First, enter an intermediate template class that creates the object, so you do not need to implement a method for each class.

 template<class Derived> class ShapeCreator : public Shape { // This class automates the creations public: static Shape* Create () { new Derived(); // Assuming that no-argument default constructor is avaialable } }; class Circle : public ShapeCreator<Circle> { }; class Square : public ShapeCreator<Square> { }; //... and so on 

(2) Now inside the class Shape enter one static std::map , which contains a handle for each derived class.

 class Shape { public: typedef std::map<std::sting, Shape* (*)()> ShapeMap; static ShapeMap s_ShapeMap; static Shape* Create (const std::string name) { ShapeMap::iterator it = s_ShapeMap.find(name); if(it == s_ShapeMap.end()) return 0; it->second(); } }; 

(3) The s_ShapeMap filling should be done statically, you can do this before main() is called (be careful with this) or as the first function inside main() . Use a preprocessing trick to automate things:

 #define INIT(SHAPE) Shape::s_ShapeMap[#SHAPE] = &SHAPE::Create Shape* InitializeShapeMap () { INIT(Circle); INIT(Square); INIT(Triangle); // ... } #undef INIT 

Whenever a new form is introduced, just add it as INIT inside the function.

0
source

It's impossible to do what you want, like in Java, but there are ways to make this a little less painful than the giant switch statement. You will need a factory. Personally, I like to use something in this direction:

 class ShapeBase { }; template<class TShape> class Shape: public ShapeBase { public: typedef TShape shape_type; template< class TFactory > static void registerClass(TFactory* factory) { factory->registerShape(shape_type::name(), [](){ return new shape_type(); }); } }; class Circle: public Shape<Circle> { public: static const char* name() { return "Circle"; } }; class Square: public Shape<Square> { public: static const char* name() { return "Square"; } }; class ShapeFactory { private: typedef std::function<ShapeBase*()> shape_creator; std::map<std::string,shape_creator> _creators; public: ShapeFactory() { registerShapes(); } void registerShapes() { Square::registerClass(this); Circle::registerClass(this); } void registerShape( const std::string& name, shape_creator creator ) { _creators[name] = creator; } ShapeBase* create(const std::string& name) { return _creators[name](); } }; int main( int argc, char** argv ) { ShapeFactory factory; ShapeBase* circle = factory.create("Circle"); ShapeBase* square = factory.create("Square"); return 0; } 

If you manage to define all Shape objects in an executable or dynamic library, and not in a static library, then there are tricks that you can use to automatically register your classes with a singleton factory, but I think it's the best idea to do it this way and avoid the singleton.

0
source

C ++ is a "class-based" language, which means that the structure of a class is known only at compile time. Therefore, you cannot generate a type at runtime.

It is better to avoid this kind of class initialization unless you know the class name at runtime.

If you need to do this on a large scale, look at third-party code generators such as jinja. This will help you create a factory pattern and the specified string-to-class mapping.

0
source

Source: https://habr.com/ru/post/950324/


All Articles