如何在类似的const和非CONST成员功能之间删除代码重复?
假设我有以下类x
我想返回内部成员的访问:
class Z
{
// details
};
class X
{
std::vector<Z> vecZ;
public:
Z& Z(size_t index)
{
// massive amounts of code for validating index
Z& ret = vecZ[index];
// even more code for determining that the Z instance
// at index is *exactly* the right sort of Z (a process
// which involves calculating leap years in which
// religious holidays fall on Tuesdays for
// the next thousand years or so)
return ret;
}
const Z& Z(size_t index) const
{
// identical to non-const X::Z(), except printed in
// a lighter shade of gray since
// we're running low on toner by this point
}
};
两个成员函数 x :: z()
and x ::: Z()const
在括号内具有相同的代码。这是重复的代码,可能会导致具有复杂逻辑的长函数的维护问题。
有没有办法避免此代码重复?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(22)
通常,您需要const和非const版本的成员函数是Getters和setter。在大多数情况下,它们是单线,因此代码重复不是问题。
Typically, the member functions for which you need const and non-const versions are getters and setters. Most of the time they are one-liners so code duplication is not an issue.
为了添加到提供的解决方案JWFEARN和KEVIN提供的内容,这是当函数返回shared_ptr的相应解决方案:
To add to the solution jwfearn and kevin provided, here's the corresponding solution when the function returns shared_ptr:
找不到我想要的东西,所以我自己滚动了几个...
这是有点言行,但是有一个优势,可以同时处理许多同名的超载方法(和返回类型):
如果您每个名称只有一个
const
方法,但仍然有很多重复的方法,那么您可能会更喜欢以下方法:不幸的是,当您开始重载该名称(函数指针参数的参数列表)时,这会立即崩溃当时似乎尚未解决,因此找不到函数参数的匹配)。尽管您也可以将其模板转换为:
但是,将参数引用到
const
方法无法与显然的串联参数匹配到模板,并且会破裂。不确定为什么。这就是为什么。Didn't find what I was looking for, so I rolled a couple of my own...
This one is a little wordy, but has the advantage of handling many overloaded methods of the same name (and return type) all at once:
If you have only one
const
method per name, but still plenty of methods to duplicate, then you might prefer this:Unfortunately this breaks down as soon as you start overloading the name (the function pointer argument's argument list seems to be unresolved at that point, so it can't find a match for the function argument). Although you can template your way out of that, too:
But reference arguments to the
const
method fail to match against the apparently by-value arguments to the template and it breaks.Not sure why.Here's why.我做了一个宏
I made a macro
https://gist.github.com/esnosy/947565a4e651991dc6a25f3e9f467285
此DDJ文章显示了一种使用模板专业化的方式,该模板专业不需要您使用const_cast。对于如此简单的功能,确实不需要。
boost :: any_cast(在某一时刻,它不再)使用const _cast从const版本调用非const版本来避免重复。您不能在非const版本上强加const语义,因此您必须非常小心。
最后,只要两个摘要直接彼此顶部,那么某些代码重复是可以的。
This DDJ article shows a way using template specialization that doesn't require you to use const_cast. For such a simple function it really isn't needed though.
boost::any_cast (at one point, it doesn't any more) uses a const_cast from the const version calling the non-const version to avoid duplication. You can't impose const semantics on the non-const version though so you have to be very careful with that.
In the end some code duplication is okay as long as the two snippets are directly on top of each other.
有关详细说明,请参见p的标题“避免在
const
和non-const
成员函数中重复”。 23,在第3项“使用const
”中,在,3d Ed Scott Meyers,ISBN-13:9780321334879。 jpg“ alt =“ alt text”>
这是Meyers的解决方案(简化):
两个铸件和函数调用可能很丑
const
开始。 (迈耶斯对此有详尽的讨论。)For a detailed explanation, please see the heading "Avoid Duplication in
const
and Non-const
Member Function," on p. 23, in Item 3 "Useconst
whenever possible," in Effective C++, 3d ed by Scott Meyers, ISBN-13: 9780321334879.Here's Meyers' solution (simplified):
The two casts and function call may be ugly, but it's correct in a non-
const
method as that implies the object was notconst
to begin with. (Meyers has a thorough discussion of this.)C ++ 17已更新了此问题的最佳答案:
这具有以下优点:
波动性
是偶然的,但是volatile
是罕见的预选赛),如果您想走完整的扣除途径,那么可以通过拥有辅助功能来完成
,现在您甚至都不会搞砸
挥发性
,用法看起来像C++17 has updated the best answer for this question:
This has the advantages that it:
volatile
by accident, butvolatile
is a rare qualifier)If you want to go the full deduction route then that can be accomplished by having a helper function
Now you can't even mess up
volatile
, and the usage looks like是的,可以避免代码重复。您需要使用const成员函数具有逻辑并具有非const成员函数调用const成员函数,并将返回值重新列为非CONST引用(或如果函数返回指针,则指针):
< strong>注意:重要的是,您要做 将逻辑放在非const函数中并具有const函数呼叫非const函数 - 可能会导致未定义行为。原因是恒定类实例被施放为非恒定实例。非CONST成员函数可能会意外修改类别,C ++标准状态将导致不确定的行为。
Yes, it is possible to avoid the code duplication. You need to use the const member function to have the logic and have the non-const member function call the const member function and re-cast the return value to a non-const reference (or pointer if the functions returns a pointer):
NOTE: It is important that you do NOT put the logic in the non-const function and have the const-function call the non-const function -- it may result in undefined behavior. The reason is that a constant class instance gets cast as a non-constant instance. The non-const member function may accidentally modify the class, which the C++ standard states will result in undefined behavior.
C ++ 23已更新了此问题的最佳答案,这要归功于。
单个功能模板可作为普通成员函数可调用,并为您推论正确的参考类型。没有铸造犯错的东西,也没有为概念上一件事的事情编写多个功能。
注意:此功能由 p0847:deeduc this 。
C++23 has updated the best answer for this question thanks to explicit object parameters.
A single function template is callable as a normal member function and deduces the correct reference type for you. No casting to get wrong, no writing multiple functions for something that is conceptually one thing.
Note: this feature was added by P0847: Deducing this.
我认为可以使用Tempate Helper功能在C ++ 11中改进Scott Meyers的解决方案。这使得意图更加明显,并且可以为许多其他收获者重复使用。
可以在以下方式使用此辅助功能。
第一个参数始终是这个分点。第二个是指向成员函数的指针。之后,可以通过任意数量的其他参数,以便可以将其转发到该功能。
由于具有变异模板,因此需要C ++ 11。
I think Scott Meyers' solution can be improved in C++11 by using a tempate helper function. This makes the intent much more obvious and can be reused for many other getters.
This helper function can be used the following way.
The first argument is always the this-pointer. The second is the pointer to the member function to call. After that an arbitrary amount of additional arguments can be passed so that they can be forwarded to the function.
This needs C++11 because of the variadic templates.
好的问题和不错的答案。我还有另一种使用不使用铸件的解决方案:
但是,它具有需要静态成员的丑闻,并且需要在其中使用
实例
变量。我没有考虑该解决方案的所有可能(负)含义。请让我知道是否有。
Nice question and nice answers. I have another solution, that uses no casts:
However, it has the ugliness of requiring a static member and the need of using the
instance
variable inside it.I did not consider all the possible (negative) implications of this solution. Please let me know if any.
比Meyers多一些,但我可能会这样做:
私人方法具有不良属性,它返回了非const Z&amp;对于const实例,这就是为什么它是私人的。私有方法可能会破坏外部接口的不变性(在这种情况下,所需的不变是“无法通过通过其通过其引用到其has-a的对象获得的引用来修改const对象”)。
请注意,这些评论是模式的一部分-_getz的界面指定它永远不会有效(显然,除了访问者之外):无论如何,都没有可以想象的好处导致较小或更快的代码。调用该方法等同于用const_cast调用其中一个访问者,您也不想这样做。如果您担心错误的错误(这是一个公平的目标),请称其为const_cast_getz,而不是_getz。
顺便说一句,我感谢迈耶斯的解决方案。我对此没有哲学上的反对。不过,就个人而言,我更喜欢一点点受控的重复,而只有在某些紧密控制的情况下才能在看起来像线噪声的方法上只能调用的私人方法。选择毒药并坚持下去。
[编辑:凯文(Kevin)正确地指出,_getz可能想调用进一步的方法(例如,generatez),该方法以相同的方式被const专业化。在这种情况下,_getz会看到const z&amp;并且必须在返回之前将其cast。这仍然是安全的,因为样板登录警策的所有内容都不是很明显的。此外,如果您这样做,然后以后将Generatez始终返回const,那么您还需要将Getz更改为始终返回const,但是编译器不会告诉您您这样做。
关于编译器的后一个观点也是Meyers推荐的模式的正确性,但是关于非显而易见的const_cast的第一点不是。因此,我认为,如果_getz遇到const_cast的返回值,那么此模式就会在Meyers上失去很多价值。由于与迈耶斯(Meyers)相比,这也遭受了缺点,我想我会在这种情况下转向他。从一个到另一个的重构很容易 - 它不会影响类中的任何其他有效代码,因为只有无效的代码和样板调用_getz。]
A bit more verbose than Meyers, but I might do this:
The private method has the undesirable property that it returns a non-const Z& for a const instance, which is why it's private. Private methods may break invariants of the external interface (in this case the desired invariant is "a const object cannot be modified via references obtained through it to objects it has-a").
Note that the comments are part of the pattern - _getZ's interface specifies that it is never valid to call it (aside from the accessors, obviously): there's no conceivable benefit to doing so anyway, because it's 1 more character to type and won't result in smaller or faster code. Calling the method is equivalent to calling one of the accessors with a const_cast, and you wouldn't want to do that either. If you're worried about making errors obvious (and that's a fair goal), then call it const_cast_getZ instead of _getZ.
By the way, I appreciate Meyers's solution. I have no philosophical objection to it. Personally, though, I prefer a tiny bit of controlled repetition, and a private method that must only be called in certain tightly-controlled circumstances, over a method that looks like line noise. Pick your poison and stick with it.
[Edit: Kevin has rightly pointed out that _getZ might want to call a further method (say generateZ) which is const-specialised in the same way getZ is. In this case, _getZ would see a const Z& and have to const_cast it before return. That's still safe, since the boilerplate accessor polices everything, but it's not outstandingly obvious that it's safe. Furthermore, if you do that and then later change generateZ to always return const, then you also need to change getZ to always return const, but the compiler won't tell you that you do.
That latter point about the compiler is also true of Meyers's recommended pattern, but the first point about a non-obvious const_cast isn't. So on balance I think that if _getZ turns out to need a const_cast for its return value, then this pattern loses a lot of its value over Meyers's. Since it also suffers disadvantages compared to Meyers's, I think I would switch to his in that situation. Refactoring from one to the other is easy -- it doesn't affect any other valid code in the class, since only invalid code and the boilerplate calls _getZ.]
对于那些
这是另一个看法:
这基本上是@pait,@davidstone和 @sh1的答案的混合在一起( eding> edit :以及@cdhowie的改进)。它添加到表中的是,您只使用仅命名函数的一条额外的代码(但没有参数或返回类型重复):
注意:GCC在8.1之前未能编译此函数,clang-5及以上为以及MSVC-19很高兴(根据编译器资源管理器)。
For those (like me) who
here is another take:
It is basically a mix of the answers from @Pait, @DavidStone and @sh1 (EDIT: and an improvement from @cdhowie). What it adds to the table is that you get away with only one extra line of code which simply names the function (but no argument or return type duplication):
Note: gcc fails to compile this prior to 8.1, clang-5 and upwards as well as MSVC-19 are happy (according to the compiler explorer).
您也可以使用模板解决此问题。该解决方案有点丑陋(但是丑陋的丑陋是在.cpp文件中隐藏的),但是它确实提供了编译器对constness进行检查,并且没有代码重复。
。
其中始终需要有const和非const版本),或者您可以使此功能成为朋友。但是我不喜欢朋友。
[编辑:删除了不需要的包括在测试过程中添加的CSTDIO。]
You could also solve this with templates. This solution is slightly ugly (but the ugliness is hidden in the .cpp file) but it does provide compiler checking of constness, and no code duplication.
.h file:
.cpp file:
The main disadvantage I can see is that because all the complex implementation of the method is in a global function, you either need to get hold of the members of X using public methods like GetVector() above (of which there always need to be a const and non-const version) or you could make this function a friend. But I don't like friends.
[Edit: removed unneeded include of cstdio added during testing.]
如果您不喜欢 const 铸造,我使用此C ++ 17版本的模板静态辅助功能,另一个答案,带有可选的Sfinae测试。
完整版: https://godbolt.org/z/mmk4r3
If you don't like const casting, I use this C++17 version of the template static helper function suggested by another answer, with and optional SFINAE test.
Full version: https://godbolt.org/z/mMK4r3
虽然这里的大多数答案建议使用
const_cast
,而cppcoreguidelines则具有节While most of answers here suggest to use a
const_cast
, CppCoreGuidelines have a section about that:如何将逻辑转移到私人方法中,而只能在Getters内部执行“获取参考并返回”的内容?实际上,我会对简单的getter功能内的静态和const铸造感到困惑,而且除了极少数情况下,我会认为这很丑陋!
How about moving the logic into a private method, and only doing the "get the reference and return" stuff inside the getters? Actually, I would be fairly confused about the static and const casts inside a simple getter function, and I'd consider that ugly except for extremely rare circumstances!
我建议使用私人辅助静态功能模板,这样:
I'd suggest a private helper static function template, like this:
使用预处理器是作弊吗?
它不像模板或铸件那样花哨,但它确实使您的意图(“这两个功能是相同的”)非常明确。
Is it cheating to use the preprocessor?
It's not as fancy as templates or casts, but it does make your intent ("these two functions are to be identical") pretty explicit.
令人惊讶的是,有很多不同的答案,但几乎所有的答案都依赖于重型模板魔术。模板功能强大,但有时宏以简洁的态度击败了它们。通常通过将两者结合来实现最大多功能性。
我写了一个宏
from_const_overload()
,该可以放置在非const函数中以调用const函数。示例用法:
简单和可重复使用的实现:
说明:
如许多答案中,避免在非const成员函数中避免代码重复的典型模式是:
可以使用类型推荐来避免使用许多样式板。 First,
const_cast
can be encapsulated inWithoutConst()
, which infers the type of its argument and removes the const-qualifier.其次,可以在with const()
中使用类似的方法来const-qualifythis
指针,该指针启用了调用const-overpradaded方法。其余的是一个简单的宏,它以正确的
this-&gt;
和从结果中删除const的呼叫前缀。由于宏中使用的表达式几乎总是一个简单的函数调用,其中有1:1转发参数,因此也可以使用省略的缺点,例如多次评估。不需要,因为逗号(作为参数分离器)发生在括号内。这种方法有几个好处:
from_const_overload()
const_iterator
,std :: shared_ptr&const t&gt;
等)。为此,只需为相应类型的conconst() Overload局限性:此解决方案已针对非const Overload进行与const超载完全相同的方案进行了优化,因此可以转发参数1:1。如果您的逻辑有所不同,并且您没有通过
this-&gt;方法(args)
来调用const版本,则可以考虑其他方法。It's surprising to me that there are so many different answers, yet almost all rely on heavy template magic. Templates are powerful, but sometimes macros beat them in conciseness. Maximum versatility is often achieved by combining both.
I wrote a macro
FROM_CONST_OVERLOAD()
which can be placed in the non-const function to invoke the const function.Example usage:
Simple and reusable implementation:
Explanation:
As posted in many answers, the typical pattern to avoid code duplication in a non-const member function is this:
A lot of this boilerplate can be avoided using type inference. First,
const_cast
can be encapsulated inWithoutConst()
, which infers the type of its argument and removes the const-qualifier. Second, a similar approach can be used inWithConst()
to const-qualify thethis
pointer, which enables calling the const-overloaded method.The rest is a simple macro that prefixes the call with the correctly qualified
this->
and removes const from the result. Since the expression used in the macro is almost always a simple function call with 1:1 forwarded arguments, drawbacks of macros such as multiple evaluation do not kick in. The ellipsis and__VA_ARGS__
could also be used, but should not be needed because commas (as argument separators) occur within parentheses.This approach has several benefits:
FROM_CONST_OVERLOAD( )
const_iterator
,std::shared_ptr<const T>
, etc.). For this, simply overloadWithoutConst()
for the corresponding types.Limitations: this solution is optimized for scenarios where the non-const overload is doing exactly the same as the const overload, so that arguments can be forwarded 1:1. If your logic differs and you are not calling the const version via
this->Method(args)
, you may consider other approaches.我想出了一个宏,该宏会自动生成一对const/non-const函数。
请参阅“实施”答案的结尾。
and_const
的参数已重复。在第一个副本中,cv
无需替代;在第二份副本中,它被const
替换。在宏参数中可以出现多少次
cv
几次没有限制。不过有点不便。如果
cv
出现在括号内的内部,则必须将这对括号带有cv_in
:实现:
pre-c ++ 20实现不支持
cv_in < /代码>:
I came up with a macro that generates pairs of const/non-const functions automatically.
See the end of the answer for the implementation.
The argument of
MAYBE_CONST
is duplicated. In the first copy,CV
is replaced with nothing; and in the second copy it's replaced withconst
.There's no limit on how many times
CV
can appear in the macro argument.There's a slight inconvenience though. If
CV
appears inside of parentheses, this pair of parentheses must be prefixed withCV_IN
:Implementation:
Pre-C++20 implementation that doesn't support
CV_IN
:我为一个朋友做到了这一点,他正确地证明了使用
const_cast
...不知道我可能会做这样的事情(不是真正优雅):I did this for a friend who rightfully justified the use of
const_cast
... not knowing about it I probably would have done something like this (not really elegant) :