【问题标题】:How do I remove code duplication between similar const and non-const member functions?如何删除类似的 const 和非常量成员函数之间的代码重复?
【发布时间】:2010-09-12 12:46:14
【问题描述】:

假设我有以下class 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 情况下返回一个值,因此您无法进行以下重构。 int Z() const { 返回 z; }
  • 对于基本类型,您是绝对正确的!我的第一个例子不是很好。假设我们改为返回一些类实例。 (我更新了问题以反映这一点。)

标签: c++ class constants code-duplication c++-faq


【解决方案1】:

是的,可以避免代码重复。您需要使用 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
};

注意:重要的是您将逻辑放在非常量函数中并让常量函数调用非常量函数 - 它可能导致未定义的行为。原因是常量类实例被强制转换为非常量实例。非常量成员函数可能会意外修改类,C++ 标准规定这将导致未定义的行为。

【讨论】:

  • 哇……太可怕了。您只是增加了代码量,降低了清晰度,并添加了 两个 臭名昭著的 const_cast。也许您已经想到了一个真正有意义的例子?
  • 嘿,别这样!,它可能很难看,但根据 Scott Meyers 的说法,它(几乎)是正确的方法。请参阅 Effective C++,第 3 版,第 3 项,标题为“避免 const 和非成本成员函数中的重复。
  • 虽然我知道解决方案可能很难看,但想象一下确定返回内容的代码有 50 行长。那么重复是非常不可取的——尤其是当您必须重构代码时。在我的职业生涯中,我遇到过很多次。
  • this和Meyers的区别在于Meyers有static_cast(*this)。 const_cast 用于删除 const,而不是添加它。
  • @VioletGiraffe 我们知道该对象最初不是创建的,因为它是一个非常量对象的非常量成员,我们知道这是因为我们在一个非常量方法中表示目的。编译器不会做出这种推断,它遵循保守的规则。如果不是这种情况,你为什么认为 const_cast 存在?
【解决方案2】:

This DDJ article 展示了一种使用模板特化的方法,不需要您使用 const_cast。但是对于这么简单的功能,确实不需要。

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

最后,一些代码重复可以的,只要两个 sn-ps 直接在彼此之上。

【讨论】:

  • DDJ 文章似乎提到了迭代器——这与问题无关。常量迭代器不是常量数据——它们是指向常量数据的迭代器。
【解决方案3】:

通常,需要 const 和非 const 版本的成员函数是 getter 和 setter。大多数情况下,它们是单行代码,因此代码重复不是问题。

【讨论】:

  • 大多数时候这可能是真的。但也有例外。
  • getter 无论如何,一个 const setter 没有多大意义 ;)
  • 我的意思是非常量 getter 实际上是一个 setter。 :)
【解决方案4】:

有关详细说明,请参阅第 10 页的标题“避免const 和非const 成员函数中的重复”。 23,在第 3 项“尽可能使用const”中,Scott Meyers 的Effective C++, 3d ed,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。 (迈耶斯对此进行了彻底的讨论。)

【讨论】:

  • 没有人因为跟随 Scott Meyers 而被解雇 :-)
  • witkamp 是正确的,一般来说使用 const_cast 是不好的。正如迈耶斯解释的那样,这是一个特殊的情况。 @Adam:ROM => const 很好。 const == ROM 显然是无稽之谈,因为任何人都可以将 non-const 强制转换为 const:这相当于只是选择不修改某些内容。
  • 一般来说,我建议使用 const_cast 而不是 static_cast 来添加 const,因为它可以防止您意外更改类型。
  • @HelloGoodbye:我认为 Meyers 假设了类接口设计者的少量智能。如果 get()const 返回定义为 const 对象的内容,则根本不应该有 get() 的非 const 版本。实际上我对此的想法随着时间的推移发生了变化:模板解决方案是避免重复获得编译器检查的常量正确性的唯一方法,所以我个人将不再使用const_cast避免重复代码,我会选择将被复制的代码放入函数模板中还是让它被复制。
【解决方案5】:

比 Meyers 更冗长,但我可能会这样做:

class X {

    private:

    // This method MUST NOT be called except from boilerplate accessors.
    Z &_getZ(size_t index) const {
        return something;
    }

    // boilerplate accessors
    public:
    Z &getZ(size_t index)             { return _getZ(index); }
    const Z &getZ(size_t index) const { return _getZ(index); }
};

私有方法有一个不受欢迎的属性,它为一个 const 实例返回一个非 const Z&,这就是它是私有的原因。私有方法可能会破坏外部接口的不变量(在这种情况下,所需的不变量是“一个 const 对象不能通过通过它获得的对其拥有的对象的引用来修改”)。

请注意,cmets 是模式的一部分 - _getZ 的接口指定调用它永远无效(显然,除了访问器):无论如何,这样做没有任何可以想象的好处,因为它需要多输入 1 个字符并且不会导致更小或更快的代码。调用该方法等同于使用 const_cast 调用其中一个访问器,您也不想这样做。如果您担心使错误变得明显(这是一个公平的目标),那么将其命名为 const_cast_getZ 而不是 _getZ。

顺便说一句,我很欣赏迈耶斯的解决方案。我对此没有哲学上的反对意见。不过,就个人而言,我更喜欢一点点受控的重复,以及只能在某些严格控制的情况下调用的私有方法,而不是看起来像线路噪音的方法。选择你的毒药并坚持下去。

[编辑:Kevin 正确地指出 _getZ 可能想要调用另一个方法(比如 generateZ),它与 getZ 一样是 const 专用的。在这种情况下,_getZ 会看到一个 const Z& 并且必须在返回之前对其进行 const_cast。这仍然是安全的,因为样板访问器会监控所有内容,但它的安全性并不是很明显。此外,如果您这样做,然后将 generateZ 更改为始终返回 const,那么您还需要将 getZ 更改为始终返回 const,但编译器不会告诉您这样做。

关于编译器的后一点也适用于 Meyers 推荐的模式,但关于非显而易见的 const_cast 的第一点不是。所以总的来说,我认为如果 _getZ 需要一个 const_cast 作为它的返回值,那么这种模式比 Meyers 的模式失去了很多价值。由于与迈耶斯相比,它也有劣势,我想在那种情况下我会改用他的。从一个重构到另一个很容易——它不会影响类中的任何其他有效代码,因为只有无效代码和样板调用_getZ。]

【讨论】:

  • 这仍然存在问题,即对于 X 的常量实例,您返回的东西可能是常量。在这种情况下,您仍然需要 _getZ(...) 中的 const_cast。如果被后来的开发者误用,仍然会导致 UB。如果返回的东西是“可变的”,那么这是一个很好的解决方案。
  • 任何私有函数(见鬼,公共的)都可能被后来的开发者误用,如果他们选择忽略关于其有效使用、头文件和 Doxygen 等的 BLOCK CAPITAL 指令. 我无法阻止,我不认为这是我的问题,因为说明很容易理解。
  • -1:这在很多情况下都不起作用。如果_getZ() 函数中的something 是一个实例变量怎么办?编译器(或至少一些编译器)会抱怨因为_getZ() 是 const,所以其中引用的任何实例变量也是 const。所以something 将是 const(类型为const Z&amp;)并且不能转换为Z&amp;。在我(诚然有些有限)的经验中,大多数时候something 在这种情况下是一个实例变量。
  • @GravityBringer:那么“某事”需要涉及const_cast。它的目的是作为从 const 对象获得非常量返回所需的代码的占位符,而不是作为 复制的 getter 中的内容的占位符。所以“某物”不仅仅是一个实例变量。
  • 我明白了。不过,这确实降低了该技术的实用性。我会删除反对票,但 SO 不会让我这样做。
【解决方案6】:

如何将逻辑移到私有方法中,并且只在 getter 中执行“获取引用并返回”内容?实际上,我会对简单 getter 函数中的静态和 const 强制转换感到相当困惑,除了极少数情况外,我认为这很难看!

【讨论】:

  • 为了避免未定义的行为,您仍然需要一个 const_cast。请参阅 Martin York 的答案和我的评论。
  • 凯文,马丁·约克的回答是什么
【解决方案7】:

您也可以使用模板解决此问题。这个解决方案略显丑陋(但丑陋隐藏在 .cpp 文件中),但它确实提供了对 constness 的编译器检查,并且没有代码重复。

.h 文件:

#include <vector>

class Z
{
    // details
};

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

public:
    const std::vector<Z>& GetVector() const { return vecZ; }
    std::vector<Z>& GetVector() { return vecZ; }

    Z& GetZ( size_t index );
    const Z& GetZ( size_t index ) const;
};

.cpp 文件:

#include "constnonconst.h"

template< class ParentPtr, class Child >
Child& GetZImpl( ParentPtr parent, size_t index )
{
    // ... massive amounts of code ...

    // Note you may only use methods of X here that are
    // available in both const and non-const varieties.

    Child& ret = parent->GetVector()[index];

    // ... even more code ...

    return ret;
}

Z& X::GetZ( size_t index )
{
    return GetZImpl< X*, Z >( this, index );
}

const Z& X::GetZ( size_t index ) const
{
    return GetZImpl< const X*, const Z >( this, index );
}

我可以看到的主要缺点是,因为该方法的所有复杂实现都在一个全局函数中,所以您要么需要使用上面的 GetVector() 等公共方法来获取 X 的成员(其中总是需要成为 const 和非 const 版本)或者您可以将此函数设为朋友。但我不喜欢朋友。

[编辑:删除了测试期间添加的 cstdio 的不需要的包含。]

【讨论】:

  • 您总是可以将复杂的实现函数设为静态成员以获取对私有成员的访问权限。该函数只需要在类头文件中声明,定义可以驻留在类实现文件中。毕竟,它是类实现的一部分。
  • 啊是的好主意!我不喜欢出现在标题中的模板内容,但如果从这里开始可能会使实现变得非常简单,那么它可能是值得的。
  • + 1 到这个解决方案,它不会复制任何代码,也不会使用任何丑陋的const_cast(可能会不小心被用来制作实际上应该是的东西const 到不是的东西)。
  • 现在这可以通过模板的推导返回类型来简化(特别有用,因为它减少了成员案例中类中必须重复的内容)。
【解决方案8】:

我认为 Scott Meyers 的解决方案可以通过使用 tempate 辅助函数在 C++11 中得到改进。这使得意图更加明显,并且可以被许多其他 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)...));
}

这个辅助函数可以通过以下方式使用。

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。

【讨论】:

  • 很遗憾我们没有std::remove_bottom_conststd::remove_const 一起使用。
  • 我不喜欢这个解决方案,因为它仍然嵌入了const_cast。您可以将 getElement 本身设为模板,并将类型的特征用于您需要的 mpl::conditional 类型,如 iterators 或 constiterators 如果需要。真正的问题是,当这部分签名无法模板化时,如何生成方法的 const 版本?
  • @v.oddou: std::remove_const&lt;int const&amp;&gt;int const &amp; (删除顶级const 资格),因此NonConst&lt;T&gt; 在这个答案中的体操。推定的std::remove_bottom_const 可以删除底层const 资格,并精确地执行NonConst&lt;T&gt; 在这里所做的事情:std::remove_bottom_const&lt;int const&amp;&gt;::type => int&amp;
  • 如果getElement 过载,此解决方案效果不佳。如果不显式给出模板参数,就无法解析函数指针。为什么?
  • 您需要修复您的答案才能使用 C++11 完美转发:likeConstVersion(TObj const* obj, TConstReturn (TObj::*memFun)(TArgs...) const, TArgs&amp;&amp;... args) { return const_cast&lt;typename NonConst&lt;TConstReturn&gt;::type&gt;((obj-&gt;*memFun)(std::forward&lt;TArgs&gt;(args)...)); } 完成:gist.github.com/BlueSolei/bca26a8590265492e2f2760d3cefcf83
【解决方案9】:

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

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

};

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

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

【讨论】:

  • 好吧,让我们看看您添加了更多样板的简单事实。如果有的话,这应该用作为什么语言需要一种方法来修改函数限定符以及返回类型auto get(std::size_t i) -&gt; auto(const), auto(&amp;&amp;) 的示例。为什么 '&&'?啊,所以我可以说:auto foo() -&gt; auto(const), auto(&amp;&amp;) = delete;
  • gd1 :正是我的想法。 @kfsone,我也得出了结论。
  • @kfsone 语法应该包含 this 关键字。我建议template&lt; typename T &gt; auto myfunction(T this, t args) -&gt; decltype(ident) this 关键字将被识别为隐式对象实例参数,并让编译器识别 myfunction 是成员或TT 将在调用站点上自动推导出来,它始终是类的类型,但具有免费的 cv 资格。
  • 该解决方案还有一个优势(与const_cast 相比)允许返回iteratorconst_iterator
  • 如果实现在 cpp 文件中移动(并且由于不重复的方法不应该是微不足道的,可能就是这种情况),static 可以在文件范围而不是类范围内完成. :-)
【解决方案10】:

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

【讨论】:

    【解决方案11】:

    我这样做是为了一个有正当理由使用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;
    }
    

    【讨论】:

      【解决方案12】:

      我建议使用私有助手静态函数模板,如下所示:

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

      【讨论】:

        【解决方案13】:

        没有找到我要找的东西,所以我自己滚动了几个......

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

        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 方法的引用参数无法与模板的明显按值参数匹配并且它会中断。 不知道为什么。Here's why

        【讨论】:

          【解决方案14】:

          使用预处理器是作弊吗?

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

          它不像模板或演员表那样花哨,但它确实使您的意图(“这两个函数要相同”)非常明确。

          【讨论】:

          • 但是你必须小心使用反斜杠(与多行宏一样),此外,你会在大多数(如果不是全部)编辑器中失去语法高亮。
          【解决方案15】:

          C++17 更新了这个问题的最佳答案:

          T const & f() const {
              return something_complicated();
          }
          T & f() {
              return const_cast<T &>(std::as_const(*this).f());
          }
          

          这样做的好处是:

          • 很明显发生了什么
          • 具有最小的代码开销——它适合一行
          • 很难出错(只能偶然抛弃volatile,但volatile是一个罕见的限定词)

          如果你想走完整的演绎路线,那么可以通过一个辅助函数来完成

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

          【讨论】:

          • 请注意,如果 f() 返回 T 而不是 T&amp;,则删除 const rvalue 重载的“as_mutable”(通常更可取)会阻止最后一个示例工作。
          • @MaxTruxa:是的,这是一件好事。如果它刚刚编译,我们将有一个悬空引用。在f()返回T的情况下,我们不希望有两个重载,单独const版本就足够了。
          • 非常真实,我为我昨天的全脑放屁道歉,不知道我在写这篇评论时在想什么。我正在查看一个返回 shared_ptr 的 const/mutable getter 对。所以我真正需要的是类似于as_mutable_ptr 的东西,它看起来与上面的as_mutable 几乎相同,除了它接受并返回shared_ptr 并使用std::const_pointer_cast 而不是const_cast
          • 如果一个方法返回T const*,那么这将绑定到T const* const&amp;&amp;,而不是绑定到T const* const&amp;(至少在我的测试中是这样)。我必须为 T const* 添加一个重载作为返回指针的方法的参数类型。
          • @monkey0506:我已经更新了我的答案以支持指针和参考
          【解决方案16】:

          对于那些(像我一样)的人

          • 使用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 的答案的混合(EDIT:和@cdhowie 的改进)。它添加到表格中的是,您只需多写一行代码即可轻松命名函数(但没有参数或返回类型重复):

          class X
          {
              const Z& get(size_t index) const { ... }
              NON_CONST(get)
          };
          

          注意:gcc 在 8.1 之前编译失败,clang-5 及更高版本以及 MSVC-19 很高兴(根据the compiler explorer)。

          【讨论】:

          • 这对我来说直接有效。这是一个很好的答案,谢谢!
          • 不应该 decltype()s 在参数上也使用 std::forward 以确保在我们有 get() 重载的情况下使用正确的返回类型参考类型?
          • @cdhowie 你能举个例子吗?
          • @axxel 这简直是天方夜谭,但here you go。由于decltype(func(a...)) 类型中缺少转发,NON_CONST 宏将错误地推导出返回类型并将const_casts 推导出为错误的类型。将它们替换为 decltype(func(std::forward&lt;T&gt;(a)...)) solves this。 (这只是一个链接器错误,因为我从未定义任何声明的 X::get 重载。)
          • 谢谢@cdhowie,我把你的例子拉到了实际使用非常量重载:coliru.stacked-crooked.com/a/0cedc7f4e789479e
          【解决方案17】:

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

          我写了一个宏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)
          

          解释:

          正如许多答案中所发布的,在非常量成员函数中避免代码重复的典型模式是:

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

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

          剩下的是一个简单的宏,它用正确限定的 this-&gt; 作为调用的前缀,并从结果中删除 const。由于宏中使用的表达式几乎总是具有 1:1 转发参数的简单函数调用,因此不会出现诸如多重评估等宏的缺点。省略号和 __VA_ARGS__ 也可以使用,但不应使用,因为逗号(作为参数分隔符)出现在括号内。

          这种方法有几个好处:

          • 最小且自然的语法——只需将调用包装在FROM_CONST_OVERLOAD( )
          • 不需要额外的成员函数
          • 与 C++98 兼容
          • 实现简单,无模板元编程和零依赖
          • 可扩展:可以添加其他 const 关系(如const_iteratorstd::shared_ptr&lt;const T&gt; 等)。为此,只需为相应的类型重载 WithoutConst()

          限制:此解决方案针对非常量重载与 const 重载完全相同的场景进行了优化,因此可以 1:1 转发参数。如果您的逻辑不同,并且您没有通过this-&gt;Method(args) 调用 const 版本,您可以考虑其他方法。

          【讨论】:

            【解决方案18】:

            我想出了一个宏,它可以自动生成成对的 const/non-const 函数。

            class A
            {
                int x;    
              public:
                MAYBE_CONST(
                    CV int &GetX() CV {return x;}
                    CV int &GetY() CV {return y;}
                )
            
                //   Equivalent to:
                // int &GetX() {return x;}
                // int &GetY() {return y;}
                // const int &GetX() const {return x;}
                // const int &GetY() const {return y;}
            };
            

            请参阅答案的末尾以了解实施情况。

            MAYBE_CONST 的参数重复。在第一个副本中,CV 被替换为空;在第二个副本中,它被替换为const

            CV 在宏参数中出现的次数没有限制。

            虽然有一点不便。如果CV出现在括号内,这对括号必须以CV_IN为前缀:

            // Doesn't work
            MAYBE_CONST( CV int &foo(CV int &); )
            
            // Works, expands to
            //         int &foo(      int &);
            //   const int &foo(const int &);
            MAYBE_CONST( CV int &foo CV_IN(CV int &); )
            

            实施:

            #define MAYBE_CONST(...) IMPL_CV_maybe_const( (IMPL_CV_null,__VA_ARGS__)() )
            #define CV )(IMPL_CV_identity,
            #define CV_IN(...) )(IMPL_CV_p_open,)(IMPL_CV_null,__VA_ARGS__)(IMPL_CV_p_close,)(IMPL_CV_null,
            
            #define IMPL_CV_null(...)
            #define IMPL_CV_identity(...) __VA_ARGS__
            #define IMPL_CV_p_open(...) (
            #define IMPL_CV_p_close(...) )
            
            #define IMPL_CV_maybe_const(seq) IMPL_CV_a seq IMPL_CV_const_a seq
            
            #define IMPL_CV_body(cv, m, ...) m(cv) __VA_ARGS__
            
            #define IMPL_CV_a(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_b)
            #define IMPL_CV_b(...) __VA_OPT__(IMPL_CV_body(,__VA_ARGS__) IMPL_CV_a)
            
            #define IMPL_CV_const_a(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_b)
            #define IMPL_CV_const_b(...) __VA_OPT__(IMPL_CV_body(const,__VA_ARGS__) IMPL_CV_const_a)
            

            不支持 CV_IN 的 C++20 之前的实现:

            #define MAYBE_CONST(...) IMPL_MC( ((__VA_ARGS__)) )
            #define CV ))((
            
            #define IMPL_MC(seq) \
                IMPL_MC_end(IMPL_MC_a seq) \
                IMPL_MC_end(IMPL_MC_const_0 seq)
            
            #define IMPL_MC_identity(...) __VA_ARGS__
            #define IMPL_MC_end(...) IMPL_MC_end_(__VA_ARGS__)
            #define IMPL_MC_end_(...) __VA_ARGS__##_end
            
            #define IMPL_MC_a(elem) IMPL_MC_identity elem IMPL_MC_b
            #define IMPL_MC_b(elem) IMPL_MC_identity elem IMPL_MC_a
            #define IMPL_MC_a_end
            #define IMPL_MC_b_end
            
            #define IMPL_MC_const_0(elem)       IMPL_MC_identity elem IMPL_MC_const_a
            #define IMPL_MC_const_a(elem) const IMPL_MC_identity elem IMPL_MC_const_b
            #define IMPL_MC_const_b(elem) const IMPL_MC_identity elem IMPL_MC_const_a
            #define IMPL_MC_const_a_end
            #define IMPL_MC_const_b_end
            

            【讨论】:

              【解决方案19】:

              如果您不喜欢 const 强制转换,我使用 another answer 建议的模板静态辅助函数的 C++17 版本,以及可选的 SFINAE 测试。

              #include <type_traits>
              
              #define REQUIRES(...)         class = std::enable_if_t<(__VA_ARGS__)>
              #define REQUIRES_CV_OF(A,B)   REQUIRES( std::is_same_v< std::remove_cv_t< A >, B > )
              
              class Foobar {
              private:
                  int something;
              
                  template<class FOOBAR, REQUIRES_CV_OF(FOOBAR, Foobar)>
                  static auto& _getSomething(FOOBAR& self, int index) {
                      // big, non-trivial chunk of code...
                      return self.something;
                  }
              
              public:
                  auto& getSomething(int index)       { return _getSomething(*this, index); }
                  auto& getSomething(int index) const { return _getSomething(*this, index); }
              };
              

              完整版:https://godbolt.org/z/mMK4r3

              【讨论】:

                【解决方案20】:

                虽然这里的大多数答案都建议使用 const_cast,但 CppCoreGuidelines 对此有一个 section

                相反,更喜欢共享实现。通常,您可以让非常量函数调用 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。这不使用任何 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 的全部或部分可以是非依赖的并分解为一个通用的非模板函数,则进一步的改进可能会大大减少代码大小。

                【讨论】:

                  【解决方案21】:

                  感谢deducing this,C++23 更新了这个问题的最佳答案:

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

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

                  【讨论】:

                    猜你喜欢
                    • 2013-11-03
                    • 2017-11-22
                    • 2012-01-28
                    • 2017-06-19
                    • 2014-11-19
                    • 2020-02-02
                    • 2013-01-04
                    • 1970-01-01
                    相关资源
                    最近更新 更多