假设我有下面的类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在大括号内具有相同的代码。这是重复的代码,可能会导致具有复杂逻辑的长函数的维护问题。
有办法避免这种代码重复吗?
对于那些(像我一样)
使用c++ 17
想要添加最少的样板文件/重复和
不介意使用宏(在等待元类时…),
下面是另一种说法:
#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(std::forward<T>(a)...))>::type \
{ \
return const_cast<decltype(func(std::forward<T>(a)...))>( \
std::as_const(*this).func(std::forward<T>(a)...)); \
}
它基本上是@Pait, @DavidStone和@sh1的答案的混合(编辑:和@cdhowie的改进)。它向表中添加的是,你只需要额外的一行代码,它只是简单地命名函数(但没有参数或返回类型重复):
class X
{
const Z& get(size_t index) const { ... }
NON_CONST(get)
};
注意:gcc在8.1之前编译失败,clang-5及以上版本以及MSVC-19都很高兴(根据编译器资源管理器)。
是的,可以避免代码重复。你需要使用const成员函数来拥有逻辑,并让非const成员函数调用const成员函数,并将返回值重新转换为非const引用(或指针,如果函数返回指针):
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
};
注意:重要的是,不要将逻辑放在非const函数中,并让const函数调用非const函数——这可能会导致未定义的行为。原因是常量类实例被转换为非常量实例。非const成员函数可能会意外地修改类,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...); }
但是const方法的引用参数与模板的明显的按值参数不匹配,它就崩溃了。不知道为什么。这是为什么。