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

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


当前回答

虽然这里的大多数答案都建议使用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的全部或部分可以是非依赖的,并分解成一个公共的非模板函数,则可以进一步改进,从而可能大大减少代码大小。

其他回答

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

这篇DDJ文章展示了一种不需要使用const_cast就可以使用模板专门化的方法。对于这样一个简单的函数,它确实是不需要的。

Boost::any_cast(在某一时刻,它不再使用)使用const版本的const_cast调用非const版本以避免重复。你不能把const语义强加在非const版本上,所以你必须非常小心。

最后,一些代码复制是可以的,只要这两个代码段直接在彼此之上。

我认为Scott Meyers的解决方案可以在c++ 11中通过使用tempate helper函数进行改进。这使得意图更加明显,并且可以被许多其他getter重用。

template <typename T>
struct NonConst {typedef T type;};
template <typename T>
struct NonConst<T const> {typedef T type;}; //by value
template <typename T>
struct NonConst<T const&> {typedef T& type;}; //by reference
template <typename T>
struct NonConst<T const*> {typedef T* type;}; //by pointer
template <typename T>
struct NonConst<T const&&> {typedef T&& type;}; //by rvalue-reference

template<typename TConstReturn, class TObj, typename... TArgs>
typename NonConst<TConstReturn>::type likeConstVersion(
   TObj const* obj,
   TConstReturn (TObj::* memFun)(TArgs...) const,
   TArgs&&... args) {
      return const_cast<typename NonConst<TConstReturn>::type>(
         (obj->*memFun)(std::forward<TArgs>(args)...));
}

可以通过以下方式使用此helper函数。

struct T {
   int arr[100];

   int const& getElement(size_t i) const{
      return arr[i];
   }

   int& getElement(size_t i) {
      return likeConstVersion(this, &T::getElement, i);
   }
};

第一个参数总是this指针。第二个是指向要调用的成员函数的指针。在此之后,可以传递任意数量的附加参数,以便将它们转发给函数。 这需要c++ 11,因为有可变模板。

c++ 23更新了这个问题的最佳答案,因为推导出了这个:

struct s {
    auto && f(this auto && self) {
        // all the common code goes here
    }
};

单个函数模板可作为普通成员函数调用,并为您推导正确的引用类型。没有错误的类型转换,没有为一个概念上的东西编写多个函数。

我这样做是为了一个朋友,他合理地证明了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;
}