Is there a way to apply an action to members of a C ++ N class in a loop over the names of participants (possibly through a preprocessor)?

Problem:

I have a C ++ class with gajillion members (> 100) that behave almost the same:

  • same type

  • in a function, each member has the same exact code as the other members, for example. assignment from the card in the constructor, where the card key matches the member key

  • This identity of behavior is repeated for many, many functions (> 20), of course, the behavior in each function is different, so there is no way to exclude it from this.

  • The list of members is very fluid, with constant additions and sometimes deletions, some ( but not all ) controlled by changing columns in the DB table.

As you can imagine, this creates a lot of pain in handling as you create and maintain the code, because to add a new member you need to add code for each function that uses similar elements.

An example of a solution that I would like

Actual C ++ code I need (say in the constructor):

MyClass::MyClass(SomeMap & map) { // construct an object from a map intMember1 = map["intMember1"]; intMember2 = map["intMember2"]; ... // Up to intMemberN = map["intMemberN"]; } 

C ++ Code I want to be able to write:

 MyClass::MyClass(SomeMap & map) { // construct an object from a map #FOR_EACH_WORD Label ("intMember1", "intMember2", ... "intMemberN") $Label = map["$Label"]; #END_FOR_EACH_WORD } 

Requirements

  • The solution should be compatible with GCC (with Nmake as a make system, if that matters). Do not care about other compilers.

  • The solution may be at the pre-processor level or something compiled. I'm fine with one; but so far all my research has indicated that the latter is simply not possible in C ++ (I skip Perl now, when I have to do C ++!)

  • The solution should be at least somewhat “industry standard” (for example, Boost works fine, but the custom Perl script created by Joe-Quick-Fingers once and posted to his blog, I can easily write that Perl script. being a lot more Perl expert than C ++ - I just can't get bigwigs in Software Engineering on my BigCompany to buy it using :))

  • The solution should allow me to declare a list of identifiers (ideally, in only one header file, and not in each #FOR_EACH_WORD directive, as in the example above)

  • The solution should not be limited to the "create object from the DB table" constructor. There are many functions, most of which are not constructors that need this.

  • The solution "Make all the values ​​in one vector and then run the" for "cycle through the vector" is obvious and cannot be used - the code in the library used by many applications, the participants are publicly available, and, unfortunately, the question of what to use these applications for the use of vector elements, rather than named members, may be unchanged.

+7
c ++ reflection c-preprocessor
source share
12 answers

Boost.Preprocessor offers many convenient macros to perform such operations. Bojan Resnik has already provided a solution using this library, but assumes that each member name is constructed in the same way.

Since you have clearly demanded the ability to declare a list of identifiers, here is a solution that should best suit your needs.

 #include <boost/preprocessor/seq/for_each.hpp> #include <boost/preprocessor/stringize.hpp> // sequence of member names (can be declared in a separate header file) #define MEMBERS (foo)(bar) // macro for the map example #define GET_FROM_MAP(r, map, member) member = map[BOOST_PP_STRINGIZE(member)]; BOOST_PP_SEQ_FOR_EACH(GET_FROM_MAP, mymap, MEMBERS) // generates // foo = mymap["foo"]; bar = mymap["bar]; ------- //Somewhere else, we need to print all the values on the standard output: #define PRINT(r, ostream, member) ostream << member << std::endl; BOOST_PP_SEQ_FOR_EACH(PRINT, std::cout, MEMBERS) 

As you can see, you just need to write a macro representing the pattern you want to repeat and pass it to the BOOST_PP_SEQ_FOR_EACH macro.

+6
source share

Boost includes an excellent preprocessor library that you can use to create this code:

 #include <boost/preprocessor/repetition.hpp> #include <boost/preprocessor/stringize.hpp> #include <boost/preprocessor/cat.hpp> typedef std::map<std::string, int> SomeMap; class MyClass { public: int intMember1, intMember2, intMember3; MyClass(SomeMap & map) { #define ASSIGN(z,n,_) BOOST_PP_CAT(intMember, n) = map[ BOOST_PP_STRINGIZE(BOOST_PP_CAT(intMember, n))]; BOOST_PP_REPEAT_FROM_TO(1, 4, ASSIGN, nil) } }; 
+9
source share

You can do something like this: create an adapter class or modify an existing class to have a vector of pointers to these fields, add the addresses of all member variables in the question to this vector in the class constructor, and then run for-loop on this if necessary vector. Thus, you do not (or almost do not change) change the class for external users and have good opportunities for the loop.

+4
source share

Of course, the obvious question is: why do you have a class with 100 members? In fact, this does not seem normal.

Assuming this is normal, did you look up the preprocessor library ? I never used it myself (as one friend said: this leads to the dark side), but from what I heard, it should be a tool for work.

+4
source share

It is difficult to use perl on your own machine to create a constructor. Then ask to increase your salary, as you successfully support such a huge piece of code.

+3
source share

You can use a preprocessor to define members, and then use the same definition to access them:

 #define MEMBERS\ MEMBER( int, value )\ SEP MEMBER( double, value2 )\ SEP MEMBER( std::string, value3 )\ struct FluctuatingMembers { #define SEP ; #define MEMBER( type, name ) type name MEMBERS #undef MEMBER #undef SEP }; .. client code: FluctuatingMembers f = { 1,2., "valuesofstringtype" }; std::cout << #define SEP << #define MEMBER( type, name ) #name << ":" << f.##name MEMBERS; #undef MEMBER #undef SEP 

It worked for me, but it's hard to debug.

+2
source share

You can also implement a visitor template based on member pointers. After solving the preprocessor, this method looks more debugged.

 struct FluctuatingMembers { int v1; double v2; std::string v3; template<typename Visitor> static void each_member( Visitor& v ); }; template<typename Visitor> void FluctuatingMembers::each_member( Visitor& v ) { v.accept( &FluctuatingMembers::v1 ); v.accept( &FluctuatingMembers::v2 ); v.accept( &FluctuatingMembers::v3 ); } struct Printer { FluctuatingMembers& f; template< typename pt_member > void accept( pt_member m ) const { std::cout << (f::*m) << "\n"; } }; // you can even use this approach for visiting // multiple objects simultaneously struct MemberComparer { FluctuatingMembers& f1, &f2; bool different; MemberComparer( FluctuatingMembers& f1, FluctuatingMembers& f2 ) : f1(f1),f2(f2) ,different(false) {} template< typename pt_member > void accept( pt_member m ) { if( (f1::*m) != (f2::*m) ) different = true; } }; ... client code: FluctuatingMembers object1 = { 1, 2.2, "value2" } , object2 = { 1, 2.2, "valuetoo" }; Comparer compare( object1, object2 ); FluctuatingMembers::each_member( compare ); Printer pr = { object1 }; FluctuatingMembers::each_member( pr ); 
+2
source share

Why not do it at runtime? (I really hate macro hacking)

What you are really asking, in a sense, is class metadata.

So, I would try something like:

 class AMember{ ...... }; class YourClass{ AMember member1; AMember member2; .... AMember memberN; typedef AMember YourClass::* pMember_t; struct MetaData : public std::vector<std::pair<std::string,pMember_t>>{ MetaData(){ push_back(std::make_pair(std::string("member1"),&YourClass::member1)); ... push_back(std::make_pair(std::string("memberN"),&YourClass::memberN)); } }; static const MetaData& myMetaData() { static const MetaData m;//initialized once return m; } YourClass(const std::map<std::string,AMember>& m){ const MetaData& md = myMetaData(); for(MetaData::const_iterator i = md.begin();i!= md.end();++i){ this->*(i->second) = m[i->first]; } } YourClass(const std::vector<std::pair<std::string,pMember_t>>& m){ const MetaData& md = myMetaData(); for(MetaData::const_iterator i = md.begin();i!= md.end();++i){ this->*(i->second) = m[i->first]; } } }; 

(pretty sure I have the syntax, but this is a message about the mechanism, not the post code)

RE: in a function, each member has the same exact code as the other members, for example. destination from the card in the constructor, where the card key matches the key of the key

it is described above.

RE: The list of members is very fluid, with constant additions and sometimes deletions, some (but not all) caused by changing columns in the database table.

When you add a new AMember, say newMember, all you have to do is update the MetaData constructor with:

  push_back(make_pair(std::string("newMember"),&YourClass::newMember)); 

RE: This behavior identity is repeated for many, many functions (> 20), of course, the behavior in each function is different, so there is no way to exclude it from this.

You have a mechanism for applying this same idiom to create functions

e.g. setAllValuesTo (const AMember & value)

  YourClass::setAllValuesTo(const AMember& value){ const MetaData& md = myMetaData(); for(MetaData::const_iterator i = md.begin();i!= md.end();++i){ this->*(i->second) = value; } } 

If you are a small bit ad with function pointers or template functionalities, you can play the mutation operation and do whatever you want to use in AMBERN from your cluster. Wrap these common functions (which a function or function pointer can take) to implement your current set of 20 public methods in an interface.

If you need more metadata, just add the codename from the MetaData map for the pointer to the element. (Of course, i-> the second above would change then)

Hope this helps.

+1
source share

You can do something like your own:

 #define DOTHAT(m) m = map[#m] DOTHAT(member1); DOTHAT(member2); #undef DOTHAT 

This is not fully consistent with your description, but closest to it, allowing you to print.

0
source share

I probably would like to do to use runtime polymorphism (dynamic dispatch). Create a parent class for these members using a method that performs common actions. Members get their class from this parent class. Those that need a different implementation of the method implement their own. If they also need common material, then inside the method they can reset the base class and call its version of the method.

Then all you need to do in your source class calls a member for each method.

0
source share

I would recommend a small command line application written in any language that you or your team speak.

Add some template language to the source files. For something like this, you do not need to implement a full-fledged parser or something like that. Just find the easily identifiable character at the beginning of the line and some keywords to replace.

Use a command-line application to convert template source files to real source files. On most build systems, this should be fairly easy to do automatically by adding the build phase or simply telling the build system: "use MyParser.exe to process files like * .tmp"

Here is an example of what I am saying:

Myclass.tmp

 MyClass::MyClass(SomeMap & map) { // construct an object from a map ▐REPLACE_EACH, LABEL, "intMember1", "intMember2, ... , "intMemberN" ▐ LABEL = map["$Label"]; } 

As an example, I used "▐", but any character that would otherwise never have appeared as the first character on the line is quite acceptable.

Now you will process these .tmp files as source files and automatically generate C ++ code.

If you've ever heard the phrase "write code that writes code", this is what it means :)

0
source share

There are already a lot of good answers and ideas, but for the sake of diversity, I will present one more.

The code file for MyClass will be:

 struct MemberData { size_t Offset; const char* ID; }; static const MemberData MyClassMembers[] = { { offsetof(MyClass, Member1), "Member1" }, { offsetof(MyClass, Member2), "Member2" }, { offsetof(MyClass, Member3), "Member3" }, }; size_t GetMemberCount(void) { return sizeof(MyClassMembers)/sizeof(MyClassMembers[0]); } const char* GetMemberID(size_t i) { return MyClassMembers[i].ID; } int* GetMemberPtr(MyClass* p, size_t i) const { return (int*)(((char*)p) + MyClassMembers[i].Offset); } 

Which then allows you to write the desired constructor as:

 MyClass::MyClass(SomeMap& Map) { for(size_t i=0; i<GetMemberCount(); ++i) { *GetMemberPtr(i) = Map[GetMemberID(i)]; } } 

And of course, for any other functions that work with all members, you should write similar loops.

There are several problems with this technique:

  • Operations with member elements use a run loop rather than other solutions that allow you to perform a detailed sequence of operations.
  • It absolutely depends on the fact that each member has the same type. Although this was allowed by the OP, it should still be assessed whether this could change in the future. Some of the other solutions do not have this limitation.
  • If I remember correctly, offsetof is defined only for working with POD types according to the C ++ standard. In practice, I have never seen this fail. However, I have not used all C ++ compilers. In particular, I have never used GCC. Therefore, you will need to test this in your environment to make sure that it really works as intended.

Regardless of whether there are any of these problems, you will need to evaluate your own situation.


Now, believing that this method can be used, there is one nice advantage. These GetMemberX functions can be turned into public static / member functions of your class, thereby giving this universal member access to more places in your code.

 class MyClass { public: MyClass(SomeMap& Map); int Member1; int Member2; int Member3; static size_t GetMemberCount(void); static const char* GetMemberID(size_t i); int* GetMemberPtr(size_t i) const; }; 

And if useful, you can also add the GetMemberPtrByID function to search for the given row identifier and return a pointer to the corresponding member.


One of the drawbacks of this idea so far is that there is a risk that the member may be added to the class, but not to the MyClassMembers array. However, this method can be combined with the xtofl macro solution so that a single list can fill both the class and the array.

changes in title:

 #define MEMBERS\ MEMBER( Member1 )\ SEP MEMBER( Member2 )\ SEP MEMBER( Member3 )\ class MyClass { public: #define SEP ; #define MEMBER( name ) int name MEMBERS; #undef MEMBER #undef SEP // other stuff, member functions, etc }; 

and changes to the code file:

 const MemberData MyClassMembers[] = { #define SEP , #define MEMBER( name ) { offsetof(MyClass, name), #name } MEMBERS #undef MEMBER #undef SEP }; 

Note. I left an error here when checking for errors. Depending on how this will be used, you might want to make sure that the bounds of the array are not overflowing with debug modes and / or release modes that will return NULL pointers for bad indexes. Or use exceptions if necessary.

Of course, if you are not worried about the error of checking the boundaries of the array, then GetMemberPtr can actually be replaced with something else that will return a link to the element.

0
source share

All Articles