假设我有下面的类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在大括号内具有相同的代码。这是重复的代码,可能会导致具有复杂逻辑的长函数的维护问题。
有办法避免这种代码重复吗?
没有找到我要找的东西,所以我自己卷了一些…
这个方法有点啰嗦,但优点是可以一次性处理多个同名(和返回类型)重载方法:
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方法的引用参数与模板的明显的按值参数不匹配,它就崩溃了。不知道为什么。这是为什么。
虽然这里的大多数答案都建议使用const_cast, CppCoreGuidelines有一个章节是关于这个的:
相反,更喜欢共享实现。通常,你可以让非const函数调用const函数。然而,当存在复杂的逻辑时,这可能导致以下模式,仍然诉诸于const_cast:
class Foo {
public:
// not great, non-const calls const version but resorts to const_cast
Bar& get_bar()
{
return const_cast<Bar&>(static_cast<const Foo&>(*this).get_bar());
}
const Bar& get_bar() const
{
/* the complex logic around getting a const reference to my_bar */
}
private:
Bar my_bar;
};
尽管这种模式在正确应用时是安全的,因为
调用者必须一开始就有一个非const对象,这并不理想
因为安全很难作为检查规则自动执行。
相反,更倾向于将公共代码放在公共helper函数中——
让它成为一个模板,这样它就可以推导出const。这个不需要任何东西
Const_cast:
class Foo {
public: // good
Bar& get_bar() { return get_bar_impl(*this); }
const Bar& get_bar() const { return get_bar_impl(*this); }
private:
Bar my_bar;
template<class T> // good, deduces whether T is const or non-const
static auto& get_bar_impl(T& t)
{ /* the complex logic around getting a possibly-const reference to my_bar */ }
};
注意:不要在模板中做大量不依赖的工作,这会导致代码膨胀。例如,如果get_bar_impl的全部或部分可以是非依赖的,并分解成一个公共的非模板函数,则可以进一步改进,从而可能大大减少代码大小。
c++ 17更新了这个问题的最佳答案:
T const & f() const {
return something_complicated();
}
T & f() {
return const_cast<T &>(std::as_const(*this).f());
}
这样做的好处是:
很明显发生了什么
有最小的代码开销——它适合单行
很难出错(只能抛弃不稳定的偶然,但不稳定是一个罕见的限定词)
如果你想要走完整的演绎路线,那么可以通过一个辅助函数来完成
template<typename T>
constexpr T & as_mutable(T const & value) noexcept {
return const_cast<T &>(value);
}
template<typename T>
constexpr T * as_mutable(T const * value) noexcept {
return const_cast<T *>(value);
}
template<typename T>
constexpr T * as_mutable(T * value) noexcept {
return value;
}
template<typename T>
void as_mutable(T const &&) = delete;
现在你甚至不能搞混volatile,它的用法看起来就像
decltype(auto) f() const {
return something_complicated();
}
decltype(auto) 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);
}
};
但是,它需要一个静态成员,并且需要使用其中的实例变量。
我没有考虑到这个解决方案的所有可能(负面)影响。如果有,请告诉我。
我建议使用私有helper静态函数模板,如下所示:
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);
}
};