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

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


当前回答

要添加到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...); }

但是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_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;
}

要添加到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;
};

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

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

};

但是,它需要一个静态成员,并且需要使用其中的实例变量。

我没有考虑到这个解决方案的所有可能(负面)影响。如果有,请告诉我。