假设我有下面的类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在大括号内具有相同的代码。这是重复的代码,可能会导致具有复杂逻辑的长函数的维护问题。

有办法避免这种代码重复吗?


当前回答

很好的问题和答案。我有另一个解决方案,不使用类型转换:

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);
    }
};

没有找到我要找的东西,所以我自己卷了一些…

这个方法有点啰嗦,但优点是可以一次性处理多个同名(和返回类型)重载方法:

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方法的引用参数与模板的明显的按值参数不匹配,它就崩溃了。不知道为什么。这是为什么。

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());
}

令我惊讶的是,有这么多不同的答案,但几乎所有的答案都依赖于沉重的模板魔法。模板功能强大,但有时宏在简洁方面胜过模板。最大的通用性通常通过两者结合来实现。

我写了一个宏FROM_CONST_OVERLOAD(),它可以放在非const函数中调用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)

解释:

在许多回答中,避免在非const成员函数中代码重复的典型模式是:

return const_cast<Result&>( static_cast<const MyClass*>(this)->Method(args) );

使用类型推断可以避免很多这种样板文件。首先,const_cast可以封装在WithoutConst()中,它推断其参数的类型并删除const限定符。其次,可以在WithConst()中使用类似的方法对this指针进行const限定,从而可以调用const重载方法。

剩下的是一个简单的宏,它在调用前加上正确限定的this->,并从结果中删除const。由于宏中使用的表达式几乎总是一个简单的带有1:1转发参数的函数调用,因此宏的缺点(如多重求值)并没有发挥作用。省略号和__VA_ARGS__也可以使用,但不应该被需要,因为逗号(作为参数分隔符)出现在括号内。

这种方法有几个好处:

最小和自然的语法——只需将调用包装在FROM_CONST_OVERLOAD()中。 不需要额外的成员函数 兼容c++ 98 简单的实现,没有模板元编程和零依赖 可扩展:可以添加其他const关系(如const_iterator、std::shared_ptr<const T>等)。为此,只需重载对应类型的WithoutConst()。

限制:此解决方案针对非const重载与const重载完全相同的场景进行了优化,因此参数可以1:1转发。如果你的逻辑不同,并且你没有通过this->方法(args)调用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都很高兴(根据编译器资源管理器)。