Template factory objects (again, and in fifteen lines)
- Tutorial
Hello!
I am very new to C ++, I program in general solely for my pleasure (sometimes for somewhat exotic platforms), I have not read theoretical books, I actively use Google, Stack Overflow and intuition in the writing process, and I also maintain that C ++ is impossible to know.
I hope this removes some of the questions and prevents surprised looks. :)
Nevertheless, I once wrote (on the pluses and with Qt 'ohm) in my free time my next bike, and then I thought that it would be nice to screw some mechanism into this place, which, as it turned out, some call the factory objects. Moreover, as I understand it, there is a more general design pattern called an abstract factory of objects, the essence of which I did not catch, as well as a simpler abstraction, the essence of which boils down to the fact 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 can be convenient to use, for example, an enumeration). When I more or less understood what I want (that is, the last one), I climbed to look for ready-made, beautiful solutions that would suit me, but having raped almost all of Google, which is surprising, I did not find one.
As a result, having spent an unacceptable amount of time on such nonsense, I gathered from a heap of places (reaching the rest of Google) my unique and, it seems to me, still elegant small (really small ... I even doubt the need for a whole post ...) bike, having to deal with a lot of things, including template classes, template functions, specialization, and even variadic templates and functions from C ++ 11 .
Let's make a reservation what exactly I want. Firstly, of course, I read the article by the ruman character rtorsten Put objects on a stream, pattern factory objects, but the meaning of everything that’s happening does not reach me so far, the implementation seems a bit overloaded, and I don’t see a way to pass arguments to the constructor when creating the next object. You can say this post is an attempt to do about the same thing, but better. Well, and secondly, what I wanted and what I achieved (it must have coincided :)): suppose we have a base class
Base
and two immediate descendants from it: Derived1
and Derived2
, copies of which we want to create by the factory. The above is covered by that article, but I also want
- create class instances with various constructor arguments;
- have one single factory implementation, for any base class and any number and types of constructor arguments;
- No need to modify classes so that they can be added to the factory.
That is, something like this:
#include
#include
#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 animalFactory;
animalFactory.add("man's friend");
animalFactory.add("^_^");
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, first a factory is created animalFactory
from a template class GenericObjectFactory
, 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 container of the type map
where the values are classes , which then will need to be added to the factory so that it can create them; as the second required parameter - the base class of these classes (and they should be inherited from one class); the remaining two parameters, which can be any number (including zero), are the types of arguments of the constructor of the classes created by the factory, in turn.Then you can add classes to the factory by calling a template member function
add
, specifying as the first and only parameter, in fact, the class that the factory needs to register, and as the first and only argument - the identifier of this class, in our case, the string (defined first template class parameter). Then the fun begins. The member function
get
takes the identifier string as the first and only argument, and returns the so-called instantiatorfound by this class identifier. Instantiator is such a function that takes a set of arguments (in the number and types specified during factory specialization) and creates an instance of the desired class, passing it to the constructor. A kind of proxy, which is needed then I will explain why. It is very similar to how the constructor itself was returned. :) At the same time, an instantiator will return a pointer to the created object, but this pointer is not of an inherited type, but of a base class. With a great desire, it can then be dynamic_cast
nosed. By the way, nothing prevents you from saving this instantiator somewhere:
auto catInstantiator=animalFactory.get("^_^");
, and already create objects later, when it will be necessary:Animal *cat=catInstantiator(true,"delayed cat");
Animal *cat=catInstantiator(false,"dead delayed cat");
:) But alright, in the end it was obvious. It is also obvious that I am writing this note not to seasoned pluses, but to beginners who for some reason wanted to make a factory. :)
Let's look at the actual implementation of the factory. I intentionally cut out everything without which it would not work, including all sorts of checks for the presence or absence of a class in the factory when creating or registering, and similar logic and all sorts of other conveniences (or rather, I was too lazy to port this from Qt to STL), since no It’s not difficult to add. But in general, I still think that the example code should be as simple and obvious as possible, without these, of course, inevitable devilish details: it’s easier to understand, and this is exactly what is needed now.
And that’s it! Isn’t it an elephant? It seems to me that I have surpassed the rtorsten solution proposed in his article at least three times.
- #include
- template
class GenericObjectFactory { - private:
- typedef Base * (* fInstantiator) (Args ...);
- template
static Base * instantiator (Args ... args) { - return new Derived (args ...);
- }
- std :: map
classes; - public:
- GenericObjectFactory () {}
- template
void add (ID id) { - classes [id] = & instantiator
; - }
- fInstantiator get (ID id) {
- return classes [id];
- }
- };
So what is going on here:
- First line: all you need is some kind of
map
container. In this case, we take STL-evsky. But the suiteQMap
also fits without problems. - Third line: template definition. You can read about the magic three points (variadic templates) on Google , on Wikipedia , or even on the Habr (Respect to the FlexFerrum Habraiser ).
- Fifth : type definition
fInstantiator
. It is a pointer to a function that returns a pointer to an object of typeBase
(template parameter) and accepts type argumentsArgs ...
(also a template parameter, but there can be any number of them, see variadic templates ↑) the member function returnsget
(line 16). - Sixth : just such a function, the very one
instantiator
we talked about, at the same time, a template one. This is necessary so that this function can be specialized in the class added to the factoryDerived
, which makes the template member function , and then take it, for the specialized function address (which creates objects of only this class), and put it in the container as the value, where the key - an argument to a type function (the first parameter of the factory template). declared for simplicity, but if you really need it, you can use the so-called member function pointers .void add
std::map classes
void add
ID
instantiator
static
- On the seventh line, in fact, the creation of the object takes place, and the constructor of this object is passed arguments in the number and types specified during the specialization of the factory, and ours
instantiator
, in fact, takes their values as arguments. Of course, in the same number and types, and of course, all this is known at compile time, and if none of the constructors of any of the classes added to the factory accepts arguments of such types, the compiler will throw an error. - Ninth line: the container in which the factory stores the classes added to it at runtime. The core of the factory. The key, as we have already said, has the type specified by the first parameter of the template - everything is simple. But the meaning is just that magic pointer to a template function. After specialization (by any class inherited from
Base
), the function has the same signature, that is, it always (with any template parameter) takes a known and the same number and type of arguments, and always returns the value of one known type - a pointer to an object of typeBase
. That is, the original problem is solved! :). - Fourteenth line: the function
add
adds to the typestd::map classes
pair (the first parameter of the class template) the address of the static just-specialized template member function . Already said.id
ID
instantiator
- Seventeenth (well, I wanted to practice writing numerals) line: the function
get
returns a pointer to theinstantiator
desired class. Please note that at the moment the object is not created, this is done by the user himself later using the issuedinstantiator
-a. This is exactly what I wanted to achieve.
Did I mention that I'm new to C ++ ? :) So, it’s quite possible that I lied in some places (or maybe I just didn’t in some places ...), but I can definitely say that it all works.
Just take the last listing, stick it in the first instead
#include "factory.h"
and check the result in your favorite compiler, not forgetting to enable support for the C ++ 11 standard . PS In general, initially I wanted to leave my version of the factory a comment on the first article, but for some reason I wrote a post ... in any case, I will be extremely glad to discuss the errors in the LAN and ... do not hit hard. :)
PPS Oh! I just wanted to send it, but I remembered that there was Ideone ! Filled an example there: you can admire . :)