📜 ⬆️ ⬇️

Template factory of objects (once again, and in fifteen lines)

image

Hello!

I am very new to C ++, I generally program solely for my own pleasure (and sometimes for several exotic platforms), I haven’t read theoretical books, I actively use Google, Stack Overflow and intuition in the process of writing, and also adhere to the opinion that it’s impossible to know C ++.
')
I hope this removes some questions and prevents surprised looks. :)

Nevertheless, I once wrote (on the pros and with Qt 'ohm) in my spare time in my free time, and then I thought that it would be nice to screw in some mechanism, which some people have turned out to be called a factory of objects. And, as I understand it, there is a more general design pattern , called an abstract object factory , the essence of which I did not catch, as well as a simpler abstraction, the essence of which is that the object knows about a number of class-identifier pairs, and then it can create instances of classes by their identifier (in the simplest case, this identifier is a string, but it may be convenient to use, for example, an enumeration). When I more or less understood what I wanted (that is, the last one), I climbed to look for ready-made, beautiful solutions that would suit me, but, having raped almost all Google, surprisingly, I did not find one.

As a result, having spent an inadmissible amount of time on such nonsense, I gathered from a heap of places (reaching the rest of Google) my unique and, I think, yet elegant small (really small ... I even doubt the need for a whole post ...) Bicycle, having managed to figure out a lot with what, including with template classes, template functions, specialization and even variadic templates and functions from C ++ 11 .

Make a reservation what exactly I want. Firstly, of course, I read the article of the hatorsuber man rtorsten. We put objects on the flow, the pattern of the factory of objects , but the meaning of everything and everything doesn’t reach me so far, the implementation seems a bit overloaded, and I’m not I see. You can say this post is an attempt to do roughly the same thing, but better. Well, and secondly, what I wanted and what I achieved (it must have coincided :)): suppose we have the base class Base and two immediate heirs from it: Derived1 and Derived2 , the instances of which we want to create by the factory.

The above is covered in that article, but I also want
That is something like this:
 #include <iostream> #include <string> #include "factory.h" using namespace std; class Animal{ public: Animal(bool isAlive,string name) : isAlive(isAlive),name(name){}; bool isAlive; string name; virtual string voice() const=0; }; class Dog : public Animal{ public: using Animal::Animal; string voice() const{ return this->isAlive? "Woof! I'm "+this->name+"\n": ""; } }; class Cat : public Animal{ public: using Animal::Animal; string voice() const{ return this->isAlive? "Meow, I'm "+this->name+"\n": ""; } }; int main(void){ GenericObjectFactory<string,Animal,bool,string> animalFactory; animalFactory.add<Dog>("man's friend"); animalFactory.add<Cat>("^_^"); Animal *dog1=animalFactory.get("man's friend")(true,"charlie"); Animal *dog2=animalFactory.get("man's friend")(false,"fido"); Animal *cat =animalFactory.get("^_^")(true,"begemoth"); cout << dog1->voice() << dog2->voice() << cat ->voice(); return 0; } 
Thus, the animalFactory factory is first created from the animalFactory template class, the first required parameter of which is the key type (in this example, a string, but it can also be handy to use something else, such as an integer value or an enumeration) of a map type, where the values ​​are the classes that will then need to be added to the factory so that it can create them; as the second mandatory parameter - the base class of these classes (and they must be inherited from one class); the remaining two parameters, which can be any number (including zero), are the types of the arguments of the class constructor created by the factory, in turn.

Then you can add classes to the factory by calling the template member function add , specifying, as the first and only parameter, the class that the factory needs to register, and the first and only argument is the identifier of this class, in our case the string (defined the first parameter of the template class).

Then the fun begins. The member function get takes the id string as the first and only argument, and returns the so-called instantiator of the class found by this id. Instantiator is such a function that will take a set of arguments (in the number and types specified in the factory specialization) and create an instance of the desired class, passing them to the constructor. A kind of proxy, which is needed later I will explain why. It is very similar to the fact that the constructor itself was returned. :) It will return an instantiator with a pointer to the created object, but this pointer is of a type not of the inherited, but of the base class. With a great desire, then you can dynamic_cast 'thread.

By the way, nothing prevents to save the given instantiator somewhere:
 auto catInstantiator=animalFactory.get("^_^"); 
, and already to create objects later, when it will be necessary:
 Animal *cat=catInstantiator(true,"delayed cat"); Animal *cat=catInstantiator(false,"dead delayed cat"); 
:)
But ok, in the end it was obvious. It’s also obvious that I’m writing this note not to the young plusists, but to the newcomers, who for some reason wanted a factory for themselves. :)



Let's look at the implementation of the factory. I deliberately cut out everything without which it would not work, including any checks for the presence or absence of a class in a factory when creating or registering and similar logic and all other amenities (or rather, I was too lazy to transfer it from Qt to STL), no good it is not difficult to finish it. But in general, I still think that the example code should be as simple and obvious as possible, without these undoubtedly inevitable diabolical details: it's easier to understand, and this is exactly what is needed now.
  1. #include <map>
  2. template < class ID, class Base, class ... Args > class GenericObjectFactory {
  3. private :
  4. typedef Base * ( * fInstantiator ) ( Args ... ) ;
  5. template < class Derived > static Base * instantiator ( Args ... args ) {
  6. return new Derived ( args ... ) ;
  7. }
  8. std :: map < ID, fInstantiator > classes ;
  9. public :
  10. GenericObjectFactory ( ) { }
  11. template < class Derived > void add ( ID id ) {
  12. classes [ id ] = & instantiator < Derived > ;
  13. }
  14. fInstantiator get ( id id ) {
  15. return classes [ id ] ;
  16. }
  17. } ;
And that's it! Isn't it elengant? It seems to me that I exceeded the rtorsten solution proposed in his article at least three times.

So what happens here:
Did I already say that I am new to C ++ ? :) So, it is quite possible that I lied in some places (or maybe only in some places I did not lie ...), but I can definitely say that it all works.

Just take the last listing, insert it into the first one instead of #include "factory.h" and check the result in your favorite compiler, without forgetting to include support for the C ++ 11 standard.

PS In general, initially I wanted to leave my version of the factory with a comment on the first article, but for some reason I wrote a post ... in any case, I will be extremely happy to discuss, errors in drugs and ... do not hit hard. :)

PPS Oh! I just wanted to send it, but I remembered that there is still Ideone ! Filled example there: you can admire . :)

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


All Articles