假设我有下面的类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 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

};

它不像模板或类型转换那么花哨,但它确实使您的意图(“这两个函数是相同的”)非常明确。

其他回答

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

我写了一个宏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版本,你可以考虑其他方法。

通常,需要使用const版本和非const版本的成员函数是getter和setter。大多数时候它们都是一行程序,所以代码复制不是问题。

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

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

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的使用…如果我不知道,我可能会这样做(不太优雅):

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

我建议使用私有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);
    }
};