📜 ⬆️ ⬇️

Droplet of reflection for C ++. Part One: Development Retrospective



IMPORTANT UPDATE. READ BEFORE READING ARTICLE
It was my fault that there were some misunderstandings about these publications. So I decided to add this warning.

In this series of articles, I wanted to put more emphasis on the development history of a certain open source library, regardless of the specific cpprt. A story from writing source codes (with an emphasis on some interesting things that people find interesting to read in general, regardless of the library itself), to the formation of a repository (with a CMake lesson) and library promotion (where part of the promotion implies publishing this series of articles). Such a training demo project for people who were thinking of posting their open source, but were either afraid or did not know how.
')
Of course, I would not mind if the library somehow came to life and there is a minimal amount of library advertising in the articles (I tried to hide it under spoilers). But still, the objectives of this cycle I considered more educational and, I hope, applicable in general, without regard to my library.

Please take this into account when reading a series of articles.


Create objects by the string names of their classes and receive information about the heirs of classes during program execution. C ++ either does not support or poorly supports similar functionality out of the box.

In this series of articles I will talk in detail about how I created my own micro-library that implements such behavior and how I prepared it for publication.





My conscience will not be clear if I do not confess one cunning thought that pushed me to write this cycle of articles. As it concerns marketing, I use hide-and-seek under cat. I warn you: there is a lot of text there.

Spacious intro with self-exposure about PR and stuffed cones
I will come from afar ...

While reading these articles, you will see a reference to a certain “main library”, of which cpprt was a part. I am working on this very “main library” (which does not even have a name yet, only conditional data_mapping) in pure time for the second month. And about the “main library” I had one very naive idea. I wanted to earn on it.

Do you understand? Earn at the library. And at the utility library. And selling it to people on their own, and not through some kind of platform ... Madness. The plan was as follows:

1. Create a kind of relatively stable core of the functional, which would show the main features of the library on a few spectacular examples.
2. Find people who will light up the idea of ​​the project. It was planned to search among friends (and through friends), at conferences, through social networks, etc.
3. Having found like-minded people, bring them up to date on the project and ensure that they begin to commit to the project repository.
4. Collective forces bring the project to a commercial type, find out about the sale mechanisms on the way (only here, pay attention, find out - holy simplicity! ..) and start selling the library. The profit from sales was planned to be divided as a percentage of some honest, predetermined conditions between the main participants.
5. After sales, as a founder, I hoped to delegate the support and development tasks to other participants (perhaps, having somewhat reduced my percentage of profits) and continue to invest less effort in the library and do my most important things myself, having this is a source of more or less stable income.
6. Well, here, as it should be, where could it be without him ... PROFIT!

The strangest thing, I seriously believed in the efficiency of this plan.

I believed in this plan, despite the fact that I had five years of experience in developing my pet projects and I knew that people have a tendency to want to live life outside working hours, and this very “desire to live life” for a very small number of acquaintances (where then for one and a half acquaintances, to be honest) means "to code all the interesting things on the pros." I knew that it was impossible to lure experienced colleagues into my repositories, and it was almost impossible to pull interested students up to the level of a worthy committer.

I believed in this plan even after I asked a question and got an answer in the apofig user blog (aka Sanyok Baglay, thanks to him). The essence of the answer boiled down, in brief, to the fact that the library, even if it starts to be sold, is the same work - and the work is sometimes more convict and nervous than a peaceful plowing for an uncle.

I believed in this plan, regularly working on the code for two months in a row. And only happened two weeks ago, already at the time of writing this series of articles, the conversation really opened my eyes to the true order of things. I present the essence of this conversation below and very much hope that he will justify such a long and extensive introduction:

Answer Yuriy Roshchenko

The man who dotted i was named Yuri Roshchenko . He worked as a department manager at a large international outsourcing company and was well versed in managing projects in general. Yuri spent forty minutes of his time talking to me, for which I am extremely grateful to him.

Having stated my plan for developing the library, I received an unequivocal answer: no, it does not work that way. We need to act completely differently. And Yuri told how. Combining Yury Roshchenko’s answer and some advice from Alexander Baghlay, I formulated the following project development steps:

1. Design the minimum repository of the project with a description and demonstration of its capabilities. At the same time, it is important that the project be launched without complicated settings, in one click, without any participation from you as the author and source of hints.

2. Show the project to the widest possible circle of acquaintances: colleagues in the companies where you worked, friends, etc. Give access to the repository, offer to test the project. This is what apofig indicated was “give him [the user] a tool in his hand, shut his mouth and see what he will do with it.”

3. The next item was a revelation to me. According to Yuri, if the project has a positive reaction from friends, you need - attention! - roll it to open source . How can you!! .. I always shook over my achievements like aki Koschey Immortal over gold. The idea of ​​so easily giving back the fruits of their labors sounded seditious to me. “Snooze!” I thought. “They will steal, make a splash!” - I thought.
No, Yuri answered me. Everything will be fine ... Why everything will be fine, he explained further.

4. After the publication of the project in open source, you need to maximize it. More publicity - more users, more caught bugs, more really useful features requested directly from people who have tried the project. The quantity according to Hegel tends to grow into quality, and users - into contributors. More contributors - even higher quality project, even more stars . More stars, a higher position of the project on the repository, which means more publicity and ... the circle is closed. And in this case, the fact that the circle closes is very good.
Note: If it is not clear who the contributors are, it does not matter. Here is a link to a short article about the roles of GitHub users. Contributors are such good people who make a pull request to your project.

5. At some point, commercial companies may start using the project. You need to find out who uses, you need to write the names of companies as advertising - because if a product is used by projects that bring profit to its authors, this means that the product itself is also quite valuable.
And only at this moment it makes sense to create a closed fork from your open repository and you can try to sell it under a commercial license, spicing up the free version with some additional useful and necessary features.
As the owner, you will have access to information that is very useful for commercializing the library: download statistics, frequently asked questions and code use patterns, different offers that can come to you as the project owner from users directly.
In the context of the conversation, Yury answered the question here why the library was not stolen and not plagiarized. The fact is that, as a founder, you will always understand the project deeper than most contributors. In order for potential intruders to understand the project and write similar code, they will have to spend a lot of time and energy or - speaking differently - money. And this, in turn, means that no one will be engaged in such activities until the purchase of a library becomes much more expensive than investing in the development of its clone.

6. If the project becomes astronomically famous, and you feel that even some Google wants the same thing as yours - you should try to sell it. To sell is success, it is good. Properly bargaining, you can gain from the sale of as much money as you yourself can hardly earn from this library in a lifetime.
Moreover, if you bother, there is a real risk that some Google will write the same library as yours during the week, and you will be left with nothing.

On points, it seems, everything. I will add a few comments from Yuri:
- It is necessary to remember about two turning points in the development of the project: (1) the moment when it makes sense to make a commercial fork, and (2) the moment when it will be more expensive for someone to buy a project than to write it yourself.
- Habra is a really good platform for popularizing the project in the Russian-speaking segment of the Internet. My interlocutor mentioned here the guys from PVS-studio, who are promoting their product in Habré, at the same time telling people useful things.
- It is important to remember about the English segment - and focus largely on him too.

In general, I have stated here all the main thoughts that I learned from the conversation. I hope someone will help the presented algorithm. And, I hope also, someone will share in the comments their experiences and their thoughts on this issue. I am sure it will be useful for the whole community.

... yes, I almost forgot. I wanted to admit one trick.

The fact is that this cycle of articles concerns a very small, but, in my opinion, self-sufficient part of the “main library”, pinched from it and turned into a library. Using her example, I decided to try what it is to promote and publish open source software. To follow in this way the way to be followed later for the “main library” ... At the same time, of course, I am honestly ready to support the cpprt library itself, to which this series of articles is dedicated, if it interests anyone.

Perhaps someone can consider my idea of ​​manipulation. It is possible ... But I justify myself by trying to tell in as much detail as possible about my experience, which can be valuable for people who want to solve similar problems someday.
I hope this pays for the time that you, dear readers, spend on reading this series of articles.


This series of articles consists of three parts:

The first part, retrospective . It details the history of the development of the micro-library.
Why read: This part is intended for people who are interested in what kind of bumps can be stuffed in the process of writing code to register information about classes before the start of the main () function. Serious C ++ programmers may find this part naive and not interesting.

The second part, the publication . Describes the preparation of the library repository for publication: the choice of license, the organization of the repository structure, the study of CMake, etc.
Why read: This part can be useful for people who have their own library gathering dust and want to know how to present this library to people.

The third part, the documentation . Here is a look at the library from the user's point of view: the main use cases of use, the mechanism for registering classes, the API for creating objects using string class names, the API for accessing information about inherited classes through specifying the parent class during program execution. This part contains sample code as well as future plans.
Why read: This part can be useful for people who want to get the functionality provided by cpprt in their project, and also, again, for those who want to contribute to the development of the library and, perhaps, even to the development of the “main library”.

For this, I finish the cycle, and turn to the essence of this particular article.

0. Introduction



The structure of this article:

Section number 0 . This section. It tells about the cpprt library analogues and gives some thoughts on why cpprt has a right to exist if there are serious analogues.
Section number 1 . Why do I need reflection in C ++.
Section number 2 . About finding a solution for the arisen task.
Section number 3 . About the first implementation of the class registration mechanism.
Section number 4 . About what problems there were in the first decision about how I corrected them.
Section number 5 . Adding the ability to register information about inheritance.
Section number 6 . Conclusion

And now, finally, we start the conversation.

At the time of publication of this series of articles, my library allows very little:
1. Create objects by the string name of their classes.
2. Get information about the successors of classes at runtime.

Already when working on an article a friend threw a link to this review of existing libraries. It refers to really powerful projects. They allow you to equip C ++ classes with a thick layer of meta information and related functionality: create objects using string class names, get a list of fields and methods of classes during program execution, call these methods ... In short, they allow you to bathe in whole oceans of meta information. My achievements seemed insignificant in the shadow of competitors. However, having calmed down a bit, I thought: perhaps there is a certain meaning to tell about the work I have done.

First, in these articles a lot of attention is paid to retrospective: what difficulties I encountered in the process of work, as I solved them. This can be useful as an educational material.

On the other hand, this cycle talks about how I prepared the library for publication: I researched licenses, dealt with CMake, with the world of open source software, and how it is common to design my projects in this world. This can also be useful to someone.

Well and, finally, yes, let my micro-library really be very limited in its possibilities in comparison with the existing bison of the C ++ reflection. But perhaps this is its advantage. The smaller the library, the easier it is for the user to become aware of the features of her work and the easier it is for her to integrate into the project. Need a heaped reflection - use a powerful library. We need a set of several simple features - you can try cpprt.

1. Why did I need this functionality



C ++ is known for its effectiveness. The code written on it comes out well optimized primarily due to the possibility of fine-tuning the use of computer resources. C ++ - really allows you to write very well optimized code. But you have to pay for it.

On the altar of efficiency, among other things, put the possibility of using some metadata during the execution of the program. Yes, there is RTTI with its typeid and dynamic_cast. There are Boost.TypeTraits . But RTTI is often turned off to save resources ( link about ), and Boost.TypeTraits, being a template-based library, is not particularly friendly with the logic of program execution time and generates a lot of service specializations of its templates.

However, sometimes it is impossible to do without the possibility of creating a class by its string name. For example, I needed such an opportunity within the framework of the serialization mechanism: for saving and loading objects stored by pointers to the parent class. Sounds maybe not very clear. I will explain in more detail.

Suppose we have a system for maintaining the state of class objects. Let its use look something like this:

//      ISerializable  //   ,    class ISerializable { virtual void save(Serializer *inSerializer) = 0; virtual void load(Deserializer *inDeserializer) = 0; } class Base : public ISerializable { virtual void save(Serializer *inSerializer) { /*     inSerializer */ } virtual void load(Deserializer *inDeserializer) { /*     inDeserializer */ } }; class Derived : public Base { virtual void save(Serializer *inSerializer) { /*     inSerializer */ } virtual void load(Deserializer *inDeserializer) { /*     inDeserializer */ } }; 


Test code that saves / loads data:

 Base *theBase = new Derived(); . . . Serializer theSerializer("save.txt"); theSerializer.save("name", theBase); theSerializer.flush(); . . . Deserializer theDeserializer("save.txt"); theBase = theDeserializer.load("name"); 


When saving an object stored by pointer to the parent class, the serializer will call the heir's save (...) method due to polymorphism. But when loading, the deserializer must somehow find out exactly what class the object had at the time of saving in order to be able to create it.

It follows that at the stage of saving you need to have a certain object class identifier, by which at the stage of loading we will be able to find out which class object you need to create and, using this identifier, through a certain API you can create an object of the required class.

The same thought in the form of code:

 class Serializer { void saveInt(const char *inName, int inValue) { ... } void saveString(const char *inName, const std::string &inValue) { ... } // . . . //      –    //  save( )       save(...) void saveObject(ISerializable *inObject) { inObject->save(this); } }; class Deserializer { int loadInt(const char *inName) { ... } void loadString(const char *inName, std::string &outValue) { ... } // . . . ISerializable *loadObject() { //     -  ,  //     –   ,  //    ISerializable *theObject = new < ??? >( ) theObject->load(this); return theObject; } }; 


Output: the code of methods for saving objects should be extended using some kind of API. Time to prototype!



 void saveObject(ISerializable *inObject) { //  ,     this->saveObjectType(inObject->classID()); // <<<--– classID() inObject->save(this); }; ISerializable *loadObject() { //         ISerializable *theObject = objectFabric().create( this->loadObjectType());// <<<--– objectFabric().create(...) theObject->load(this); return theObject; }; 


Throwing in this way the general view of the desired API, I took up the search for a solution that would provide similar functionality.

2. Search for a solution



For a start, I googled to find out what people think about the right behavior. I wanted something lightweight, without a thousand dependencies, without using auxiliary systems like Qt MOC (another article on the topic) and, among other things, easy to use. It was not possible to find a ready-made solution in the form of a library that meets the specified criteria.

Note
As I wrote above, already at the time of writing, a link appeared on the review of high-quality existing solutions ( here it is again). It is very strange that google on the request of “c ++ reflection” gave the first link to the strange article “My bike to reflection in c ++” , and not to this relevant publication.


I started to think how to implement similar functionality on my own.

Solutions, by and large, revolved around the description of factory functions and / or classes that were registered and used further through a mechanism that selected the right factory depending on the class name transferred. In this article, I decided to highlight this answeroverflow answer (by Johannes Schaub , thanks to him for the idea), since it embodies the main approaches found in the network.

The first idea proposed by Johannes Schaub implied the use of a template factory function and the preservation of its specializations in the dictionary. I didn’t really like the idea by itself - the mechanism looked somehow incomplete, without a beautiful object wrapper. However, some echoes of such an implementation can be found in the resulting final cpprt implementation (using factory classes).

The second idea from Johannes Schaub: the inclusion of the mechanism for registering factories in class constructors that [constructors] were invoked when creating static objects of these classes. I liked this idea, especially considering the proposed macros: the macros allowed to hide the details of the registration mechanism, due to which this mechanism could be changed from version to version of the library without side effects for code users.

I quote here the solution of Johannes Schaub with minimal changes and with my comments:

Johannes Schaub Solution
 // in base.hpp: //   .    . //     Base,  Johannes,   , //        . //  ,         //  void, ,  , . //        Base,   //       || (    ). template< typename T > Base * createT() { return new T; } //    ""   struct BaseFactory { // ,        //        // .       ,   //   createT    // (      ). typedef std::map< std::string, Base*(*)() > map_type; //         // ... ,     . static Base *createInstance(std::string const& s) { map_type::iterator it = getMap()->find(s); return (it == getMap()->end()) ? NULL : it->second(); } protected: //  ,    - //  map    .   //    ,  //     . static map_type * getMap() { //  Johannes Schaub: // never delete'ed. (exist until program termination) // because we can't guarantee correct destruction order if(!map) { map = new map_type; } return map; } private: static map_type * map; }; // ,      //    . ,   //   –  ,  ,    T   //       . template< typename T > struct DerivedRegister : BaseFactory { DerivedRegister(std::string const& s) { getMap()->insert(std::make_pair(s, &createT< T >)); } }; // in derivedb.hpp //  ,    , //  Johannes Schaub.   , Johannes Schaub //     Base.   : class DerivedB : public Base { ...; private: //       reg. static DerivedRegister< DerivedB > reg; }; // in derivedb.cpp: //  ( ,     DerivedB) //    ,    //       . //        //  DerivedRegister< DerivedB >    //     DerivedB DerivedRegister< DerivedB > DerivedB::reg("DerivedB"); 



Johannes Schaub also proposed macros to simplify type registration. They allow you to close the features of the implementation of the registration system and make the code more concise:

Macros from Johannes Schaub
 #define REGISTER_DEC_TYPE(NAME) \ static DerivedRegister<NAME> reg #define REGISTER_DEF_TYPE(NAME) \ DerivedRegister<NAME> NAME::reg(#NAME) 



I took the decision of Johannes Schaub as a basis, slightly changing it to your liking.

3. First implementation



The Johannes Schaub solution had a DerivedRegister template class, whose objects of specializations were created as static fields within the registered classes (static DerivedRegister reg). First of all, I decided to transfer factory functions to the DerivedRegister class as factory methods. Due to this, in addition to simplifying the code, it was possible to expand the meta information about the registered classes by simply adding fields to the DerivedRegister class.

I also transferred information about the string name of classes to my DerivedRegister counterpart, thus starting to use it as a repository of meta-information (so far only the string name of the class was used as meta-information).

There was, in fact, the implementation of the design pattern of an abstract factory with some frills for the possibility of executing the metadata registration code before the start of the main () function:

ClassManager.h
 //    ( DerivedRegister   // Johannes Schaub).        // .      //  createAbstractObject() // class IClassManager { private: const char *_name; public: IClassManager(const char *inClassName) : _name(inClassName) { } //-– Workflow const char *name() const { return _name; } virtual IManagedClass *createAbstractObject() = 0; }; //----------------------------------------------------------------- //   .   //          //      . //     . template<typename T_Type> class ClassManager : public IClassManager { public: //   ,     // Johannes Schaub,  ,  : ClassManager(const char *inClassName) : IClassManager(inClassName) { globalRuntime.registerClass(this); } T_Type *createObject() { return new T_Type(); } virtual IManagedClass *createAbstractObject() { return createObject(); } }; 



The variable globalRuntime is a global object that is dedicated to managing all meta-information machinery. In fact, just an object wrapper around a vector storing ClassManager specialization objects.

Consider the class code whose object is globalRuntime. I think the essence of his work will be clear without comment:

CPPRuntime.h
 class CPPRuntime { private: std::vector< IClassManager * > _registries; IClassManager *managerByName(const char *inName); public: CPPRuntime() : _registries() { } void registerClass(IClassManager *inClass); IManagedClass *createObject(const char *inClassName); }; //----------------------------------------------------------------------------- extern CPPRuntime globalRuntime; 



CPPRuntime.cpp
 IClassManager *CPPRuntime::managerByName(const char *inName) { for (size_t theIndex = 0, theSize = _registries.size(); theIndex < theSize; ++theIndex) { if (0 == strcmp(_registries[theIndex]->name(), inName)) { return _registries[theIndex]; } } return NULL; } //-– Registering void CPPRuntime::registerClass(IClassManager *inClass) { _registries.push_back(inClass); } //-– Public API IManagedClass *CPPRuntime::createObject(const char *inClassName) { IClassManager *theRegistry = managerByName(inClassName); //TODO: Through an exception if no class found return theRegistry->createAbstractObject(); } //----------------------------------------------------------------------------- CPPRuntime globalRuntime; 



It remained to describe the base class IManagedClass (analogue of the Base class from the Johannes Schaub solution) and create macros to simplify the mechanism for registering classes.

In connection with the description of the base class IManagedClass, you should recall the API for which everything was started:

1. API for loading data . Ability to create an object by ID:
objectFabric (). create (inObjectID)
This is done by the globalRuntime.createObject ("ClassName") method.

2. API for saving data . Ability to get object class identifier:
object-> classID ()
The solution proposed by Johannes Schaub did not include a similar API. You have to implement it yourself .

I sketched the use case for inheriting IManagedClass using the example of a single class. I think that here, too, everything will be more or less clear without unnecessary comments:

TestClass.h
 //--------------------------------------------------------------------------------------------------- //  ,     . //        //  . class IClassManager { public: virtual IClassManager *getRegistry() = 0; }; //--------------------------------------------------------------------------------------------------- //  ,   .   //          //          //      . class TestClass : public IClassManager { public: //     .     . static ClassManager< TestClass > gClassManager; virtual IClassManager *getRegistry() { return &gClassManager; } public: }; 



TestClass.cpp
 //    gClassManager   //      TestClass   //   .        . ClassManager< TestClass > TestClass::gClassManager("TestClass"); 



I ran the code using this class, namely, the call to globalRuntime.createObject ("TestClass"). The object is safely created.

It remained to describe the macros that would remove from the user the need to manually make copy-paste code for registering classes:

Macros.h
 #define CPPRT_DECLARATION(M_ClassName)\ public:\ static ClassManager< M_ClassName > gClassManager;\ virtual IClassManager *getClassManager() { return &gClassManager; }\ protected:\ //  ,         ,   namespace. #define CPPRT_IMPLEMENTATION(M_ClassName) ClassManager< M_ClassName > M_ClassName::gClassManager(#M_ClassName); 


Note: The CPPRT prefix is ​​first mentioned here. This is short for C Plus Plus Run Time .



Macro was ready. The principle of its use did not differ from the principles of using the macro proposed by Johannes Schaub:

The principle of using macro
For example, we want to register a class TestClass. We declare a class:

 //--– TestClass.h --- class TestClass : public IClassManager { //         //  –      // ,       protected CPPRT_DECLARATION(TestClass) . . . }; 


Next, create the implementation file. In the implementation file, we use a macro to describe a static object that stores information about the CPPRT_IMPLEMENTATION class (we recall that we only declared this object through the CPPRT_DECLARATION macro).

 //--– TestClass.cpp --- CPPRT_IMPLEMENTATION(TestClass) 



Done! Trembling with impatience, I wrapped the described macros with a hierarchy of my classes, implemented serialization / deserialization methods using the newly created API, launched the code ...

Hooray! Objects are correctly saved and loaded, keeping their type! I tested everything thoroughly, saving objects of different classes. It worked! I didn’t expect that everything would start so easily at once ...

4. The first problems and the fight against them



... and I was absolutely right. Actually, in the written code there was one dangerous error. At some point - namely, when I added the next registered class - everything broke. Some classes stopped registering, and I couldn’t catch the sign on which the registration of classes fell off. Random of some kind. I spent half an hour of time moving from debug to logging and back. The ClassManager constructor for each class was called. Registration, respectively, took place ... But when it came to creating an object of a certain class globalRuntime.createObject (“SomeHellClass”), it turned out that there was no ClassManager for the class SomeHellClass in the array of registered class managers.

It took me a minute to think that one of the two of us was crazy: either I or C ++. And, as always, it turned out that I had lost my mind. Everything fell into place when I tried to add / delete sources for transfer to compilation. Each time the set of classes changed, the registration of which “fell off”. That is, it was in the order of compilation of the associates.

People who have carefully read the code, I think, have already understood the reason for the error.

Notice how the globalRuntime was defined:

 //--– CPPRuntime.h --- extern CPPRuntime globalRuntime; //--– CPPRuntime.cpp --- CPPRuntime globalRuntime; 


This is a global object. Not nested in a function and thus created at the time of the first function call, but simply a global object.

In this case, we recall the implementation of the ClassManager template class constructor, in which ClassManager objects of specialization register themselves within the globalRuntime object:

 //--– ClassManager.h --- ClassManager(const char *inClassName) : IClassManager(inClassName) { globalRuntime.registerClass(this); } 


And we also recall how objects of the ClassManager specializations are created (they are described via macros):

 #define CPPRT_DECLARATION(M_ClassName)\ public:\ static ClassManager< M_ClassName > gClassManager;\ virtual IClassManager *getClassManager() { return &gClassManager; }\ protected:\ #define CPPRT_IMPLEMENTATION(M_ClassName)\ ClassManager< M_ClassName > M_ClassName::gClassManager(#M_ClassName); 


ClassManager specialization objects (for example, SomeHellClass :: gClassManager object) are also global variables! .. They must be global variables: it’s important that a constructor be executed for each such object before running main (), otherwise the registration of such objects will not be performed.

And now let's remember: in what order are the constructors of global and static variables in C ++ called? .. Yes, that's right. In random order ( stackoverflow , quotation from the Standard is also available there). What follows from this?

And this implies the possibility that the constructor of the globalRuntime object can be called after the constructors of any ClassManager specializations have been called. The situation is very bad: in the object constructors of the ClassManager specializations (objects with the names gClassManager), the methods of an object that has not yet been created (globalRuntime) can be accessed. This behavior could lead to some kind of stall, but did not lead - which is even worse in this case. Typical undefined behavior.

Do not do this. Never.

Optional note
The funny thing is that access to the globalRuntime object from somewhere in the “normal” code, which goes to main () with the rootstock’s roots, would not be a problem: the standard ensures that the constructor of the global object must be called before the main () call.


Fixing the problem was obvious: for correct access to the object, it was necessary to implement one of the variations of the singleton Meyers (a more detailed article on singletons can be found there and about this singleton):

extern CPPRuntime globalRuntime;
 //--– CPPRuntime.h --- CPPRuntime &globalRuntime(); 


CPPRuntime globalRuntime;
 //--– CPPRuntime.cpp --- CPPRuntime &globalRuntime() { // , ,      ,  //        –     // header only . ,   thread safe... static CPPRuntime sCPPRuntime; return sCPPRuntime; } 


Access through the function provides guaranteed creation of an object of the CPPRuntime class at any time, from anywhere in the code.

After fixation, all that remained was to make a change to the ClassManager specialization constructor, which accessed the CPPRuntime class object:

 ClassManager(const char *inClassName) : IClassManager(inClassName) { //     CPPRuntime  , //   globalRuntime() globalRuntime().registerClass(this); } 


I drove a good code through different hierarchies of objects, changing the list of sources sent for compilation - in order to verify exactly that now everything is fine.

Everything was really good.

As time went on, the main library lived its own life, developed. And at some point a new functional was needed ...

5. Genealogy of classes



At some point it became necessary to collect information about the heirs of classes during the execution of the program. I will not go into details, why exactly this was needed - I’ll tell you about it sometime when the time comes to publish the main library. In order to have a more substantive conversation, suppose that this was necessary for debugging purposes: for the convenience of viewing information about registered classes for especially large projects.

boost (is_base_of) compile time ( is_base_of API boost).

, … !



API - :

 std::vector< IClassManager * > theChildManagers; //      ClassManager // (       IClassManager) //  -  BaseClass. globalRuntime(IClassManager).getChildren(BaseClass::gClassManager, theChildManagers); //     –   // -  BaseClass: std::cout << "Children of class " << BaseClass::gClassManager.name() << std::endl; for (size_t theIndex = 0, theSize = theChildManagers.size(); theIndex < theSize; ++theIndex) { std::cout << theChildManagers[theIndex]->name() << std::endl; } 


, API, , .

API , , API .

: ClassManager ClassManager . :

ClassManager.h
 class IClassManager { private: . . . //      //    std::vector< IClassManager *> _parents; std::vector< IClassManager *> _children; protected: // ,    ClassManager  //   void setParent(IClassManager *inParent) { _parents.push_back(inParent); inParent->_children.push_back(this); } . . . }; //----------------------------------------------------------------- template<typename T_Type> class ClassManager : public IClassManager { . . . public: //   , , //    ClassManager(const char *inClassName) : IClassManager(inClassName) { globalRuntime().registerClass(this); } ClassManager(const char *inClassName, IClassManager *inParent0) : IClassManager(inClassName) { globalRuntime().registerClass(this); setParent(inParent0); } ClassManager(const char *inClassName, IClassManager *inParent0, IClassManager *inParent1) : IClassManager(inClassName) { globalRuntime().registerClass(this); setParent(inParent0); setParent(inParent1); } //  ..,      . . . }; 



:

Macros.h
 // 0 parents #define CPPRT_IMPLEMENTATION_0(M_Class)\ ClassManager< M_Class > M_Class::gClassManager(#M_Class); // 1 parent #define CPPRT_IMPLEMENTATION_1(M_Class, M_BaseClass0)\ ClassManager< M_Class > M_Class::gClassManager(#M_Class,\ &M_BaseClass0::gClassManager); // 2 parents #define CPPRT_IMPLEMENTATION_2(M_Class, M_BaseClass0, M_BaseClass1)\ ClassManager< M_Class > M_Class::gClassManager(#M_Class,\ &M_BaseClass0::gClassManager,\ &M_BaseClass1::gClassManager); //  ..,      



CPPRuntime , ClassManager. , :

CPPRuntime.h
 class CPPRuntime { . . . private: // ,    void CPPRuntime:: getClassRegistries_internal( IClassManager *inClassManager, std::vector<IClassManager *> &outRegistries) public: // ,     CPPRuntime. //     getClassRegistries_internal(...) //  ,     API  //    . void getChildren(IClassManager *inBaseRegistry, std::vector< IClassManager *> inChildRegistries); . . . }; . . . 


CPPRuntime.cpp
 . . . void CPPRuntime::getClassRegistries_internal( IClassManager *inRegistry, std::vector<IClassManager *> &outRegistries) { //   ClassManager    outRegistries.push_back(inRegistry); std::vector< IClassManager * > &theChilds = inRegistry->_childs; for (size_t theIndex = 0, theSize = theChilds.size(); theIndex < theSize; ++theIndex) { //        //  ClassManager getClassRegistries_internal(theChilds[theIndex], outRegistries); } } void CPPRuntime::getChildren(IClassManager *inBaseRegistry, std::vector< IClassManager *> &outRegistries) { getClassHeirarhieNames_internal(inBaseRegistry, outRegistries); } . . . 



- ( inRegistry->_childs CPPRuntime:: getClassRegistries_internal). ClassManager _children, friend . , , :

ClassManager.h
 template< typename T_Type > class ClassManager : public IClassManager { . . . //  CPPRuntime    _children. friend class CPPRuntime; . . . }; 



friend . , — .

. , / . : … _parents , _children – . ?

: . ClassManager:

Macros.h
 #define CPPRT_IMPLEMENTATION_NO_PREFIX_1(M_Class, M_BaseClass0)\ ClassManager< M_Class > M_Class::gClassManager(\ #M_Class,\ &M_BaseClass0::gClassManager); 



ClassManager ( &M_BaseClass0::gClassManager ) ClassManager ( M_Class::gClassManager ), setParent(...):

ClassManager.h
 class IClassManager { . . . void setParent(IClassManager *inParent) { _parents.push_back(inParent); inParent->_children.push_back(this); // <<<--–   ! } . . . }; 



ClassManager … ClassManager , !

.

. . , globalRuntime(), ClassManager , . : ClassManager . gClassManager SomeHellClass, , SomeHellClass, .

Read more
, , « » ClassManager, , CPPRuntime. , «» ClassManager ( virtual IClassManager *getRegistry() ):

 //----– Declaration.h ----- class Base { public: static ClassManager<Base> *getClassManager( ); }; class Child { public: static ClassManager<Child> *getClassManager( ); }; class ChildOfChild { public: static ClassManager<ChildOfChild> *getClassManager( ); }; 


 //----– Declaration.cpp ----- ClassManager<Base> *Base::getClassManager() { static ClassManager<Base> gClassManager("Base"); return &gClassManager; } ClassManager<Child> *Child::getClassManager() { static ClassManager<Child> gClassManager("Child", Base::getClassManager()); return &gClassManager; } ClassManager<ChildOfChild> *ChildOfChild::getClassManager() { static ClassManager<ChildOfChild> gClassManager("ChildOfChild", Child::getClassManager()); return &gClassManager; } 


, , , , Child. , , ClassManager . Like this:

 ClassManager<Child> *theManager = Child::getClassManager(); 


getClassManager() :

 . . . static ClassManager<Child> gClassManager("Child", Base::getClassManager()); // <<---!!! return &gClassManager; . . . 


, Base::getClassManager() … ! ChildOfChild::getClassManager(). , , :

 //   ClassManager . . . globalRuntime().registerClass(this); IClassManager::setParent(inParent0); // <<<--- !!! . . . 


Here it is. , - , . ClassManager - . getClassManager() , _child ClassManager .

, - – , ++, , .


, , ClassManager , , ClassManager , CPPRuntime.

, , , ClassManager. main() , dynamic initialization . , - .

, : , , , , . , , ClassManager – :

ClassManager.h
 class IClassManager { . . . std::vector< int > _parentsIndexes; std::vector< int > _childIndexes; . . . void setParent(IClassManager *inParent) { _parentIndexes.push_back(globalRuntime().indexOf(inParent)); inParent->_childIndexes.push_back(globalRuntime().indexOf(this)); } . . . }; 



CPPRuntime.h
 class CPPRuntime { . . . std::vector< IClassManager * > _registries; . . . int indexOf(IClassManager *inRegistry) { for(int theIndex = 0, theSize = _registries.size(); theIndex < theSize; ++theIndex) if (_registries[theIndex] == inRegistry) return theIndex; _registries.push_back(inRegistry); return _registries.size() – 1; } . . . }; 



, , , , (, ).

– – - gClassManager. , , :

Macros.h
 . . . // 1 parent #define CPPRT_IMPLEMENTATION_1(M_Class, M_BaseClass0)\ ClassManager< M_Class > *M_Class::getRegistry() { static ClassManager< M_Class > gClassManager(#M_Class, &M_BaseClass0:: getRegistry() ); return &gClassManager; } //   ,       //    ClassManager< M_Class >,  ,  //  .    ,    //        cpp-. // ,           //  .      –  , //  ! char __dummy__##M_Class = (char)M_Class::getRegistry(); . . . 



. 3.6.2 . : stackoverflow , (, , PDF, 3.6.2 ).

Variables with static storage duration (3.7.1) or thread storage duration (3.7.2) shall be zero-initialized (8.5) before any other initialization takes place


, ():

 getPointer() { //  ,    ,  //  zero-value ,  //    ,   if (!Class::gPointer) { Class::gPointer = initializeValue(); } return Class::gPointer; } Class::gPointer = getPointer(); 


, . ClassManager CPPRuntime, CPPRuntime .

450 .

6. Conclusion



:
1. «» bitbucket. GitHub. .
2. GitHub — , CMake ( ).

, . . Thanks for attention!

,

:

a1ien_n3t . jpg.
Sirikid (const const) ClassManager.h.

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


All Articles