我怎么删除重复代码之间类似的常量和非常量成员职能?
-
02-07-2019 - |
题
让我们说,我有以下 class 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()
和 X::Z() const
有相同的码内的括号。这是重复代码 并且可能导致维修问题对于长期的职能的复杂逻辑.
有没有一种方法来避免这种重复代码?
解决方案 2
是的,可以避免代码重复。您需要使用const成员函数来获取逻辑并让非const成员函数调用const成员函数并将返回值重新转换为非const引用(或者如果函数返回指针则返回指针): / p>
class X
{
std::vector<Z> vecZ;
public:
const Z& Z(size_t index) const
{
// same really-really-really long access
// and checking code as in OP
// ...
return vecZ[index];
}
Z& Z(size_t index)
{
// One line. One ugly, ugly line - but just one line!
return const_cast<Z&>( static_cast<const X&>(*this).Z(index) );
}
#if 0 // A slightly less-ugly version
Z& Z(size_t index)
{
// Two lines -- one cast. This is slightly less ugly but takes an extra line.
const X& constMe = *this;
return const_cast<Z&>( constMe.Z(index) );
}
#endif
};
注意:重要的是你 NOT 将逻辑放在非const函数中并让const函数调用非const函数 - 它可能会导致未定义的行为。原因是常量类实例被转换为非常量实例。非const成员函数可能会意外地修改类,C ++标准声明这将导致未定义的行为。
其他提示
有关详细说明,请参阅标题<!>“避免重复const
和非 - <=>成员函数,<!>”;在第23,在第3项<!>中;尽可能使用<=>,<!>在 Effective C ++ ,由Andrew Meyers撰写的3d编辑,ISBN-13:9780321334879。
这是迈耶斯的解决方案(简化):
struct C {
const char & get() const {
return c;
}
char & get() {
return const_cast<char &>(static_cast<const C &>(*this).get());
}
char c;
};
两个强制转换和函数调用可能很难看,但它是正确的。迈耶斯有详尽的解释原因。
我认为Scott Meyers的解决方案可以通过使用tempate helper函数在C ++ 11中得到改进。这使得意图更加明显,可以重复用于许多其他的getter。
template <typename T>
struct NonConst {typedef T type;};
template <typename T>
struct NonConst<T const> {typedef T type;}; //by value
template <typename T>
struct NonConst<T const&> {typedef T& type;}; //by reference
template <typename T>
struct NonConst<T const*> {typedef T* type;}; //by pointer
template <typename T>
struct NonConst<T const&&> {typedef T&& type;}; //by rvalue-reference
template<typename TConstReturn, class TObj, typename... TArgs>
typename NonConst<TConstReturn>::type likeConstVersion(
TObj const* obj,
TConstReturn (TObj::* memFun)(TArgs...) const,
TArgs&&... args) {
return const_cast<typename NonConst<TConstReturn>::type>(
(obj->*memFun)(std::forward<TArgs>(args)...));
}
可以通过以下方式使用此辅助函数。
struct T {
int arr[100];
int const& getElement(size_t i) const{
return arr[i];
}
int& getElement(size_t i) {
return likeConstVersion(this, &T::getElement, i);
}
};
第一个参数始终是this-pointer。第二个是指向要调用的成员函数的指针。之后,可以传递任意数量的附加参数,以便将它们转发给函数。 由于可变参数模板,这需要C ++ 11。
比迈耶斯更冗长,但我可能会这样做:
class X {
private:
// This method MUST NOT be called except from boilerplate accessors.
Z &_getZ(size_t index) const {
return something;
}
// boilerplate accessors
public:
Z &getZ(size_t index) { return _getZ(index); }
const Z &getZ(size_t index) const { return _getZ(index); }
};
私有方法具有不受欢迎的属性,它返回非const Z <!>放大器;对于const实例,这就是为什么它是私有的。私有方法可能会破坏外部接口的不变量(在这种情况下,所需的不变量是<!>“; const对象不能通过它获得的引用修改为它具有的对象 - !<!>。)。
请注意,注释是模式的一部分 - _getZ的接口指定调用它永远无效(显然除了访问器):无论如何这样做都没有可能的好处,因为它是另外一个字符来键入和不会导致更小或更快的代码。调用该方法相当于使用const_cast调用其中一个访问器,您也不希望这样做。如果你担心错误明显(这是一个公平的目标),那么称之为const_cast_getZ而不是_getZ。
顺便说一句,我很欣赏迈耶斯的解决方案。我对它没有任何哲学上的反对意见。但就个人而言,我更喜欢一点点的受控重复,以及一种只能在某些严格控制的情况下调用的私有方法,而不是一种看似线噪声的方法。选择你的毒药并坚持下去。
[编辑:Kevin正确地指出_getZ可能想要调用另一个方法(比如generateZ),这个方法与getZ一样是const专用的。在这种情况下,_getZ会看到一个const Z <!>放大器;并且必须在返回之前const_cast它。这仍然是安全的,因为样板访问器可以控制所有内容,但是它的安全性并不明显。此外,如果你这样做,然后将generateZ更改为始终返回const,那么你还需要将getZ更改为始终返回const,但编译器不会告诉您这样做。
关于编译器的后一点也适用于Meyers推荐的模式,但关于非显而易见的const_cast的第一点不是。所以总的来说,我认为如果_getZ结果需要一个const_cast作为其返回值,那么这种模式会失去很多超过Meyers的价值。由于与Meyers相比它也有缺点,我想我会在那种情况下转向他。从一个重构到另一个很容易 - 它不会影响类中的任何其他有效代码,因为只有无效代码和样板文件调用_getZ。]
C++17个已更新的最好的回答这个问题:
T const & f() const {
return something_complicated();
}
T & f() {
return const_cast<T &>(std::as_const(*this).f());
}
这具有以下优点:
- 是显而易见的是怎么回事
- 具有最小的代码顶--它适合在一个单一的线
- 是很难得到错误的(只能抛弃
volatile
通过事故,但是volatile
是一种罕见的限定词)
如果你想要去完整的扣除的路线,那么,可以通过具有辅助功能
template<typename T>
constexpr T & as_mutable(T const & value) noexcept {
return const_cast<T &>(value);
}
template<typename T>
void as_mutable(T const &&) = delete;
现在你甚至不能搞砸了 volatile
, 和使用的看起来像
T & f() {
return as_mutable(std::as_const(*this).f());
}
好问题和好答案。我有另一个解决方案,它不使用强制转换:
class X {
private:
std::vector<Z> v;
template<typename InstanceType>
static auto get(InstanceType& instance, std::size_t i) -> decltype(instance.get(i)) {
// massive amounts of code for validating index
// the instance variable has to be used to access class members
return instance.v[i];
}
public:
const Z& get(std::size_t i) const {
return get(*this, i);
}
Z& get(std::size_t i) {
return get(*this, i);
}
};
但是,它需要一个静态成员并且需要在其中使用instance
变量。
我没有考虑此解决方案的所有可能(负面)含义。如果有的话请告诉我。
您也可以使用模板解决此问题。这个解决方案稍微有些丑陋(但是丑陋隐藏在.cpp文件中)但它确实提供了constness的编译器检查,并且没有代码重复。
.h文件:
#include <vector>
class Z
{
// details
};
class X
{
std::vector<Z> vecZ;
public:
const std::vector<Z>& GetVector() const { return vecZ; }
std::vector<Z>& GetVector() { return vecZ; }
Z& GetZ( size_t index );
const Z& GetZ( size_t index ) const;
};
.cpp文件:
#include "constnonconst.h"
template< class ParentPtr, class Child >
Child& GetZImpl( ParentPtr parent, size_t index )
{
// ... massive amounts of code ...
// Note you may only use methods of X here that are
// available in both const and non-const varieties.
Child& ret = parent->GetVector()[index];
// ... even more code ...
return ret;
}
Z& X::GetZ( size_t index )
{
return GetZImpl< X*, Z >( this, index );
}
const Z& X::GetZ( size_t index ) const
{
return GetZImpl< const X*, const Z >( this, index );
}
我可以看到的主要缺点是因为该方法的所有复杂实现都在全局函数中,您需要使用上面的GetVector()等公共方法来获取X的成员(其中总是需要是一个const和非const版本)或者你可以使这个功能成为朋友。但我不喜欢朋友。
[编辑:删除了在测试期间添加的cstdio不需要的包含。]
如何将逻辑移动到私有方法中,只执行<!>“获取引用并返回<!>”;吸气剂里面的东西?实际上,我会对一个简单的getter函数中的static和const转换相当困惑,除了非常罕见的情况之外我会认为它很丑陋!
使用预处理器是否作弊?
struct A {
#define GETTER_CORE_CODE \
/* line 1 of getter code */ \
/* line 2 of getter code */ \
/* .....etc............. */ \
/* line n of getter code */
// ^ NOTE: line continuation char '\' on all lines but the last
B& get() {
GETTER_CORE_CODE
}
const B& get() const {
GETTER_CORE_CODE
}
#undef GETTER_CORE_CODE
};
它不像模板或强制转换那样华丽,但它确实使你的意图(<!>“这两个函数是相同的<!>”;)非常明确。
通常,您需要const和非const版本的成员函数是getter和setter。大部分时间它们都是单行的,因此代码重复不是问题。
我为一位合理使用const_cast
的朋友做了这件事......不知道它我可能会做这样的事情(不是很优雅):
#include <iostream>
class MyClass
{
public:
int getI()
{
std::cout << "non-const getter" << std::endl;
return privateGetI<MyClass, int>(*this);
}
const int getI() const
{
std::cout << "const getter" << std::endl;
return privateGetI<const MyClass, const int>(*this);
}
private:
template <class C, typename T>
static T privateGetI(C c)
{
//do my stuff
return c._i;
}
int _i;
};
int main()
{
const MyClass myConstClass = MyClass();
myConstClass.getI();
MyClass myNonConstClass;
myNonConstClass.getI();
return 0;
}
我建议使用私有助手静态函数模板,如下所示:
class X
{
std::vector<Z> vecZ;
// ReturnType is explicitly 'Z&' or 'const Z&'
// ThisType is deduced to be 'X' or 'const X'
template <typename ReturnType, typename ThisType>
static ReturnType Z_impl(ThisType& self, size_t index)
{
// massive amounts of code for validating index
ReturnType ret = self.vecZ[index];
// even more code for determining, blah, blah...
return ret;
}
public:
Z& Z(size_t index)
{
return Z_impl<Z&>(*this, index);
}
const Z& Z(size_t index) const
{
return Z_impl<const Z&>(*this, index);
}
};
对于那些(像我这样的人)
- 使用 c++17
- 想要加入 少量的样板/重复和
- 不介意使用 makros (同时等待元课程...),
这里是另一个考虑:
#include <utility>
#include <type_traits>
template <typename T> struct NonConst;
template <typename T> struct NonConst<T const&> {using type = T&;};
template <typename T> struct NonConst<T const*> {using type = T*;};
#define NON_CONST(func) \
template <typename... T> \
auto func(T&&... a) -> typename NonConst<decltype(func(a...))>::type { \
return const_cast<decltype(func(a...))>( \
std::as_const(*this).func(std::forward<T>(a)...)); \
}
它基本上是一个混合的答案,从@大中型,@DavidStone和@1号国道旁.什么它会增加表是你得到的唯一一个额外的代码行其简单的名称的功能(但没有参数或者返回的类型的重复):
class X
{
const Z& get(size_t index) const { ... }
NON_CONST(get)
};
注:海湾合作委员会无法编制此前8.1,铛-5及以上以及MSVC-19都乐(根据 编译器explorer).
这是令人惊奇的我,有这么多不同的答案,但几乎全部依靠重型模板的魔法。模板是强大的,但有时宏击败他们在简洁。最大的多功能性往往是通过结合两个。
我写了一个宏 FROM_CONST_OVERLOAD()
它可以放置在非常量功能援引const功能。
例的使用情况:
class MyClass
{
private:
std::vector<std::string> data = {"str", "x"};
public:
// Works for references
const std::string& GetRef(std::size_t index) const
{
return data[index];
}
std::string& GetRef(std::size_t index)
{
return FROM_CONST_OVERLOAD( GetRef(index) );
}
// Works for pointers
const std::string* GetPtr(std::size_t index) const
{
return &data[index];
}
std::string* GetPtr(std::size_t index)
{
return FROM_CONST_OVERLOAD( GetPtr(index) );
}
};
简单的和可重复使用的执行:
template <typename T>
T& WithoutConst(const T& ref)
{
return const_cast<T&>(ref);
}
template <typename T>
T* WithoutConst(const T* ptr)
{
return const_cast<T*>(ptr);
}
template <typename T>
const T* WithConst(T* ptr)
{
return ptr;
}
#define FROM_CONST_OVERLOAD(FunctionCall) \
WithoutConst(WithConst(this)->FunctionCall)
说明:
如张贴在许多答复中,典型的模式,以避免重复代码在一个非常量成员的功能是这样的:
return const_cast<Result&>( static_cast<const MyClass*>(this)->Method(args) );
很多这样可以避免使用类型推理。第一, const_cast
可以封装在 WithoutConst()
, ,其中推断的类型参数,并删除该常量限定词。第二,一个类似的方法可以使用 WithConst()
到const资格的 this
指针,这使得能够调用的常量超载方法。
其余是一个简单的宏前缀的呼吁的正确资格 this->
并删除const从结果。由于表达使用的宏几乎是始终是一个简单的功能呼叫1:1转发的论点,存在的缺陷宏如多的评价不要踢。省略号和 __VA_ARGS__
也可以用,但不应将必要的,因为逗号(作为参数分离器)内出现的括号。
这种做法有几个优点:
- 最小和自然语法--只包裹的电话在
FROM_CONST_OVERLOAD( )
- 没有额外的成员的功能需要
- 兼容C++98
- 简单的执行情况,没有模板元编程和零依赖关系
- 可扩展:其他const关系可以加入(喜欢
const_iterator
,std::shared_ptr<const T>
, 等等)。为此,简单地超载WithoutConst()
对应的类型。
限制:这种解决方案进行了优化的情况非常量超载做完全相同的格载,以便参数可以转为1:1。如果你的逻辑不同,你都不打电话const版本通过 this->Method(args)
, 你可以考虑其他办法。
此DDJ文章展示了一种使用模板专业化的方法,不需要您使用const_cast会。对于这样一个简单的功能,它确实不需要。
boost :: any_cast(在某一点上,它不再存在)使用const版本中的const_cast来调用非const版本以避免重复。你不能在非const版本上强加const语义,所以你必须非常小心。
最后,只要两个代码段直接相互叠加,一些代码重复 即可。
要添加到jwfearn和kevin提供的解决方案,这里是函数返回shared_ptr时的相应解决方案:
struct C {
shared_ptr<const char> get() const {
return c;
}
shared_ptr<char> get() {
return const_pointer_cast<char>(static_cast<const C &>(*this).get());
}
shared_ptr<char> c;
};
没找到我要找的东西,所以我自己动了几个......
这个有点罗嗦,但有一个优点就是同时处理许多同名(和返回类型)的重载方法:
struct C {
int x[10];
int const* getp() const { return x; }
int const* getp(int i) const { return &x[i]; }
int const* getp(int* p) const { return &x[*p]; }
int const& getr() const { return x[0]; }
int const& getr(int i) const { return x[i]; }
int const& getr(int* p) const { return x[*p]; }
template<typename... Ts>
auto* getp(Ts... args) {
auto const* p = this;
return const_cast<int*>(p->getp(args...));
}
template<typename... Ts>
auto& getr(Ts... args) {
auto const* p = this;
return const_cast<int&>(p->getr(args...));
}
};
如果每个名称只有一个const
方法,但仍有很多方法要复制,那么您可能更喜欢这个:
template<typename T, typename... Ts>
auto* pwrap(T const* (C::*f)(Ts...) const, Ts... args) {
return const_cast<T*>((this->*f)(args...));
}
int* getp_i(int i) { return pwrap(&C::getp_i, i); }
int* getp_p(int* p) { return pwrap(&C::getp_p, p); }
不幸的是,一旦你开始重载名称就会崩溃(函数指针参数的参数列表在那时似乎没有得到解决,所以它找不到函数参数的匹配)。虽然你也可以模板化你的方式:
template<typename... Ts>
auto* getp(Ts... args) { return pwrap<int, Ts...>(&C::getp, args...); }
但是<=>方法的引用参数无法与模板的明显的by-value参数匹配,并且它会中断。 不确定原因。 这就是原因。