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

};

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

其他回答

有关详细说明,请参阅《Effective c++》第23页第3项“尽可能使用const”中的标题“避免const和非const成员函数中的重复”,由Scott Meyers 3d编辑,ISBN-13: 9780321334879。

以下是Meyers的解决方案(简化版):

struct C {
  const char & get() const {
    return c;
  }
  char & get() {
    return const_cast<char &>(static_cast<const C &>(*this).get());
  }
  char c;
};

这两个类型转换和函数调用可能很难看,但在非const方法中是正确的,因为这意味着对象一开始就不是const对象。(Meyers对此进行了深入的讨论。)

我认为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
    }
};

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

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

是的,可以避免代码重复。你需要使用const成员函数来拥有逻辑,并让非const成员函数调用const成员函数,并将返回值重新转换为非const引用(或指针,如果函数返回指针):

class X
{
   std::vector<Z> vecZ;

public:
   const Z& z(size_t index) const
   {
      // same really-really-really long access 
      // and checking code as in OP
      // ...
      return vecZ[index];
   }

   Z& z(size_t index)
   {
      // One line. One ugly, ugly line - but just one line!
      return const_cast<Z&>( static_cast<const X&>(*this).z(index) );
   }

 #if 0 // A slightly less-ugly version
   Z& Z(size_t index)
   {
      // Two lines -- one cast. This is slightly less ugly but takes an extra line.
      const X& constMe = *this;
      return const_cast<Z&>( constMe.z(index) );
   }
 #endif
};

注意:重要的是,不要将逻辑放在非const函数中,并让const函数调用非const函数——这可能会导致未定义的行为。原因是常量类实例被转换为非常量实例。非const成员函数可能会意外地修改类,c++标准状态将导致未定义的行为。