有没有办法将操作应用于 N C++类成员在成员名称上循环(可能通过预处理器)?
问题:
我有一个 C++ 类,其中包含大量(>100)个成员,其行为几乎相同:
相同类型
在函数中,每个成员都有对其执行的代码与其他成员完全相同,例如从构造函数中的映射进行赋值,其中映射键与函数中的成员键相同
这种行为的相同性在许多函数(> 20)中重复,当然每个函数中的行为是不同的,因此无法分解事物
- 流动,不断添加,有时会删除,其中一些(但不是全部)是由更改数据库表中的列驱动的。
正如您可以想象的那样,这在代码创建和维护方面带来了很大的麻烦,因为要添加新成员,您必须向每个函数添加代码 使用类似成员的地方。
我想要的解决方案示例
我需要的实际 C++ 代码(例如,在构造函数中):
MyClass::MyClass(SomeMap & map) { // construct an object from a map
intMember1 = map["intMember1"];
intMember2 = map["intMember2"];
... // Up to
intMemberN = map["intMemberN"];
}
我希望能够编写的 C++ 代码 :
MyClass::MyClass(SomeMap & map) { // construct an object from a map
#FOR_EACH_WORD Label ("intMember1", "intMember2", ... "intMemberN")
$Label = map["$Label"];
#END_FOR_EACH_WORD
}
要求
解决方案必须与 GCC 兼容(如果重要的话,使用 Nmake 作为 make 系统)。 不用关心其他编译器。
解决方案可以是预处理器级别的,也可以是可编译的。我对其中任何一个都很好;但到目前为止,我所有的研究都让我得出这样的结论:后者在 C++ 中显然是不可能的(我非常想念 Perl,现在我被迫使用 C++!)
解决方案必须至少在某种程度上是“行业标准”(例如,Boost 很棒,但是 Joe-Quick-Fingers 创建一次并发布在他的博客上的自定义 Perl 脚本却不是。哎呀,我可以轻松编写该 Perl 脚本,我更像是一位 Perl 专家,而不是 C++ 专家 - 我只是无法让我的大公司软件工程领域的大佬们同意使用它:) )
该解决方案应该允许我声明 ID 列表(理想情况下,仅在一个头文件中而不是在每个“#FOR_EACH_WORD”指令中)正如我在上面的示例中所做的那样)
解决方案不能仅限于“从数据库表创建对象”构造函数。有许多函数需要此功能,其中大多数不是构造函数。
“将它们的所有值都放在一个向量中,然后在向量上运行“for”循环”的解决方案是一个显而易见的解决方案,并且不能使用 - 代码位于许多应用程序使用的库中,成员是公共的,遗憾的是,重写这些应用程序以使用向量成员而不是命名成员是不可能的。
The problem:
I have a C++ class with gajillion (>100) members that behave nearly identically:
same type
in a function, each member has the same exact code done to it as other members, e.g. assignment from a map in a constructor where map key is same as member key
This identicality of behavior is repeated across many-many functions (>20), of course the behavior in each function is different so there's no way to factor things out.
The list of members is very fluid, with constant additions and sometimes deletions, some (but not all) driven by changing columns in a DB table.
As you can imagine, this presents a big pain-in-the-behind as far as code creation and maintenance, since to add a new member you have to add code to every function
where analogous members are used.
Example of a solution I'd like
Actual C++ code I need (say, in 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 must be compatible with GCC (with Nmake as make system, if that matters).
Don't care about other compilers.The solution can be on a pre-processor level, or something compilable. I'm fine with either one; but so far, all of my research pointed me to the conclusion that the latter is just plain out impossible in C++ (I so miss Perl now that I'm forced to do C++ !)
The solution must be to at least some extent "industry standard" (e.g. Boost is great, but a custom Perl script that Joe-Quick-Fingers created once and posted on his blog is not. Heck, I can easily write that Perl script, being much more of a Perl expert than a C++ one - I just can't get bigwigs in Software Engineering at my BigCompany to buy into using it :) )
The solution should allow me to declare a list of IDs (ideally, in only one header file instead of in every "#FOR_EACH_WORD" directive as I did in the example above)
The solution must not be limited to "create an object from a DB table" constructor. There are many functions, most of them not constructors, that need this.
A solution of "Make them all values in a single vector, and then run a 'for' loop across the vector" is an obvious one, and can not be used - the code's in a library used by many apps, the members are public, and re-writing those apps to use vector members instead of named members is out of the question, sadly.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(12)
Boost 包含一个很棒的预处理器库,您可以使用它来生成此类代码:
Boost includes a great preprocessor library that you can use to generate such code:
Boost.Preprocessor 提出了许多方便的宏来执行此类操作。 Bojan Resnik 已经提供了 使用此库的解决方案,但它假设每个成员名称都以相同的方式构造。
由于您明确要求可以声明 ID 列表,因此这里有一个解决方案应该可以更好地满足您的需求。
如您所见,您只需编写一个表示要重复的模式的宏,并将其传递给
BOOST_PP_SEQ_FOR_EACH
宏。Boost.Preprocessor proposes many convenient macros to perform such operations. Bojan Resnik already provided a solution using this library, but it assumes that every member name is constructed the same way.
Since you explicitely required the possibily to declare a list of IDs, here is a solution that should better fulfill your needs.
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.您可以执行以下操作:创建一个适配器类或修改现有类以具有指向这些字段的指针向量,将所有相关成员变量的地址添加到类构造函数中的该向量中,然后在需要时运行 for-对该向量进行循环。这样,您就不会(或几乎不会)更改外部用户的类,并且具有良好的 for 循环功能。
You could do something like this: create an adapter class or modify the existing class to have a vector of pointers to those fields, add the addresses of all member variables in question to that vector in the class constructor, then when needed run the for-loop on that vector. This way you don't (or almost don't) change the class for external users and have a nice for-loop capability.
当然,最明显的问题是:为什么你的班级有 100 名成员?这看起来不太理智。
假设它是理智的——你看过 boost预处理器库?我自己从未使用过它(正如一位朋友过去常说的:这样做会带来黑暗的一面),但据我所知,它应该是完成这项工作的工具。
Of course, the obvious question is: Why do you have a class with 100 members? It doesn't really seem sane.
Assuming it is sane nevertheless -- have you looked at boost preprocessor library? I have never used it myself (as one friend used to say: doing so leads to the dark side), but from what I heard it should be the tool for the job.
偷偷地在您自己的机器上使用 perl 来创建构造函数。然后要求增加你的薪水,因为你成功地维护了这么大的代码块。
Surreptitiously use perl on your own machine to create the constructor. Then ask to increase your salary since you're succesfully maintaining such a huge chunk of code.
您可以使用预处理器来定义成员,然后使用相同的定义来访问它们:
它对我有用,但很难调试。
You could use the preprocessor to define the members, and later use the same definition to access them:
It worked for me, but is hard to debug.
您还可以实现基于成员指针的访问者模式。在采用预处理器解决方案之后,这个解决方案变得更加可调试。
You can also implement a visitor pattern based on pointer-to-members. After the preprocessor solution, this one turns out way more debuggeable.
为什么不在运行时执行呢? (我真的很讨厌宏黑客)
从某种意义上说,您真正要求的是类元数据。
所以我会尝试这样的事情:(
很确定我的语法正确,但这是一篇机械帖子而不是代码帖子)
RE:
在函数中,每个成员都具有与其他成员相同的代码,例如从构造函数中的映射进行赋值,其中映射键与成员键相同
这是上面处理的。
关于:
成员列表非常流动,不断添加,有时会删除,其中一些(但不是全部)是由更改数据库表中的列驱动的。
当您添加新的 AMember(例如 newMember)时,您所拥有的一切要做的就是使用以下内容更新元数据构造函数:
RE:
这种行为的相同性在许多函数(>20)中重复出现,当然每个函数中的行为是不同的,因此无法将事情分解出来。
你有机制来应用同样的方法构建函数的习惯用法
,例如: setAllValuesTo(const AMember& value)
如果您对函数指针或模板函数有一点创意,您可以分解出变异操作,并在集合的基础上对 YourClass' AMember 执行任何您想要的操作。包装这些通用函数(可能需要函数或函数指针)以在接口中实现当前的 20 个公共方法集。
如果您需要更多元数据,只需将元数据映射的共域扩展到指向成员的指针之外。 (当然上面的 i-> 第二个会改变)
希望这会有所帮助。
Why not do it at run time? (I really hate macro hackery)
What you really are asking for, in some sense, is class metadata.
So I would try something like:
(pretty sure I've got the syntax right but this is a machinery post not a code post)
RE:
in a function, each member has the same exact code done to it as other members, e.g. assignment from a map in a constructor where map key is same as member key
this is handled above.
RE:
The list of members is very fluid, with constant additions and sometimes deletions, some (but not all) driven by changing columns in a DB table.
When you add a new AMember, say newMember, all you have to do is update the MetaData constructor with an:
RE:
This identicality of behavior is repeated across many-many functions (>20), of course the behavior in each function is different so there's no way to factor things out.
You have the machinery to apply this same idiom to build the functions
eg: setAllValuesTo(const AMember& value)
If you are a tiny bit creative with function pointers or template functionals you can factor out the mutating operation and do just about anything you want to YourClass' AMember's on a collection basis. Wrap these general functions (that may take a functional or function pointer) to implement your current set of 20 public methods in the interface.
If you need more metadata just augment the codomain of the MetaData map beyond a pointer to member. (Of course the i->second above would change then)
Hope this helps.
您可以做类似他的事情:
这并不完全符合您的描述,但最接近它,可以节省您的打字时间。
You can do something like his:
That doesn't fully fit your description, but closest to it that saves you typing.
也许我想做的是利用运行时多态性(动态调度)。使用执行常见操作的方法为这些成员创建一个父类。成员从该父类派生出他们的类。需要不同方法实现的人会实现自己的方法。如果他们也需要完成常见的工作,那么在方法内部,他们可以向下转换为基类并调用其方法的版本。
然后,您在原始类中所要做的就是调用每个方法的成员。
Probably what I'd look to do would be to make use of runtime polymorphism (dynamic dispatch). Make a parent class for those members with a method that does the common stuff. The members derive their class from that parent class. The ones that need a different implementation of the method implement their own. If they need the common stuff done too, then inside the method they can downcast to the base class and call its version of the method.
Then all you have to do inside your original class is call the member for each method.
我会推荐一个小型命令行应用程序,用您或您的团队最精通的任何语言编写。
在源文件中添加某种模板语言。对于这样的事情,您不需要实现一个成熟的解析器或任何类似的东西。只需在行首查找易于识别的字符以及一些要替换的关键字即可。
使用命令行应用程序将模板化源文件转换为真实源文件。在大多数构建系统中,通过添加构建阶段或简单地告诉构建系统:“使用 MyParser.exe 处理 *.tmp 类型的文件”,这应该很容易自动完成,
这是我正在讨论的示例:
MyClass.tmp
我使用“▐”作为示例,但任何不会作为一行中第一个字符出现的字符都是完全可以接受的。
现在,您可以将这些 .tmp 文件视为源文件,并自动生成实际的 C++ 代码。
如果您曾经听说过“编写代码的代码”这句话,这就是它的意思:)
I would recommend a small command-line app, written in whatever language you or your team are most proficient in.
Add some kind of template language to your source files. For something like this, you don't need to implement a full-fledged parser or anything fancy like that. Just look for an easily-identified character at the beginning of a line, and some keywords to replace.
Use the command-line app to convert the templated source files into real source files. In most build systems, this should be pretty easy to do automatically by adding a build phase, or simply telling the build system: "use MyParser.exe to handle files of type *.tmp"
Here's an example of what I'm talking about:
MyClass.tmp
I've used "▐" as an example, but any character that would otherwise never appear as the first character on a line is perfectly acceptable.
Now, you would treat these .tmp files as your source files, and have the actual C++ code generated automatically.
If you've ever heard the phrase "write code that writes code", this is what it means :)
这里已经有很多好的答案和想法,但为了多样性,我将提出另一个。
在 MyClass 的代码文件中将是:
这样就可以将所需的构造函数编写为:
当然,对于对所有成员进行操作的任何其他函数,您将编写类似的循环。
现在,此技术存在一些问题:
offsetof
仅被 C++ 标准定义为适用于 POD 类型。在实践中,我从未见过它失败。不过我还没有使用过所有的 C++ 编译器。特别是,我从未使用过 GCC。因此,您需要在您的环境中对此进行测试,以确保它确实按预期工作。这些是否是问题,您必须根据自己的情况进行评估。
现在,假设这项技术可用,那么它有一个很好的优点。这些 GetMemberX 函数可以转换为类的公共静态/成员函数,从而提供对代码中更多位置的通用成员访问。
如果有用,您还可以添加
GetMemberPtrByID
函数来搜索给定的字符串 ID 并返回指向相应成员的指针。到目前为止,这种想法的一个缺点是存在可能将成员添加到类中但不能添加到 MyClassMembers 数组中的风险。然而,这种技术可以与 xtofl 的宏解决方案结合起来,以便单个列表可以填充类和数组。
标头中的更改:
以及代码文件中的更改:
注意:我在此处的示例中留下了错误检查。根据其使用方式,您可能希望确保数组边界不会因调试模式断言和/或释放模式检查而溢出,这些检查将针对错误索引返回 NULL 指针。或者在适当的情况下使用一些例外。
当然,如果您不担心检查数组边界时出错,那么 GetMemberPtr 实际上可以更改为其他可返回对该成员的引用的内容。
There are already a lot of good answers and ideas here, but for the sake of diversity I'll present another.
In the code file for MyClass would be:
Which then makes it possible to write the desired constructor as:
And of course, for any other functions operating on all the members you would write similar loops.
Now there are a few issues with this technique:
offsetof
is only defined to work on POD types by the C++ standard. In practice, I've never seen it fail. However I haven't used all the C++ compilers out there. In particular, I've never used GCC. So you would need to test this in your environment to ensure it actually works as intended.Whether or not any of these are problems is something you'll have to evaluate against your own situation.
Now, assuming this technique is usable, there is one nice advantage. Those GetMemberX functions can be turned into public static/member functions of your class, thus providing this generic member access to more places in your code.
And if useful, you could also add a
GetMemberPtrByID
function to search for a given string ID and return a pointer to the corresponding member.One disadvantage with this idea so far is that there is a risk that a member could be added to the class but not to the MyClassMembers array. However, this technique could be combined with xtofl's macro solution so that a single list could populate both the class and the array.
changes in the header:
and changes in the code file:
Note: I have left error checking out of my examples here. Depending on how this would be used, you might want to ensure the array bounds are not overrun with debug mode asserts and/or release mode checks that would return NULL pointers for bad indexes. Or some use of exceptions if appropriate.
Of course, if you aren't worried about error checking the array bounds, then
GetMemberPtr
could actually be changed into something else that would return a reference to the member.