【问题标题】:Overloading postincrement重载后增量
【发布时间】:2015-05-29 22:11:18
【问题描述】:

假设我们有一个类myClass 存储一个int。我想重载标准运算符,如+=+=++ 等。如何重载后缀增量,即operator++(int),以便在计算表达式后完成实际增量?我的意思是,在下面的代码执行完 bean 之后,

myClass object1(0), object2(1);
object1 = object2++ + object2;

我希望 object1 和 object2 都持有 2。

天真的方法

myClass& operator++(int){
  myClass tmp(x) //x - stored value
  ++x;
  return tmp;
}

不起作用,因为在 + 之前调用了 ++

我一直在寻找这个问题的答案,但没有发现任何相关信息。坦率地说,直到最近我还认为不可能做到这一点。

【问题讨论】:

  • 为什么要这样做?我怀疑任何想要这样做的现实世界的应用程序都有重大的设计问题,应该用一根非常大的棍子敲打。
  • 我不明白,object1object2 不会都持有2,因为在评估语句后已经执行了后增量?

标签: c++


【解决方案1】:

您的代码:

myClass object1(0), object2(1);
object1 = object2++ + object2;

实际上具有未指定的行为,根据 §8.3.6/9:

函数参数的求值顺序未指定。

并且由于该表达式被转换为以下内容:

operator=(objec1, operator+(operator++(.., object2), object))

避免这种不便的明显方法是在求和之后执行增量:

object1 = object + object2;
object++;

或者(假设operator*也被定义了):

object1 = object2 * 2;
object++;

根据 §1.9/15,如果它们是标量类型,那将是未定义的行为:

除非另有说明,对单个运算符的操作数和单个表达式的子表达式的求值是无序的。 [注意:在程序执行期间多次评估的表达式中,其子表达式的未排序和不确定排序的评估不需要在不同的评估中一致地执行。 — end note ] 运算符的操作数的值计算在运算符结果的值计算之前排序。如果标量对象上的副作用相对于同一标量对象的另一个副作用或使用同一标量对象的值的值计算是未排序的,并且它们不是潜在的并发 (1.10),则行为未定义。

【讨论】:

  • 如果 object1object2 是标量,那么你是正确的。但是由于这些运算符是对类对象的函数调用,因此对成员 x 的修改和检查不是无序的,而是不确定的。因此,不是未定义的行为,而是实现定义的结果。
  • @BenjaminLindley True.
  • 他在问如何避免未指明的行为
  • @MattMcNabb 他实际上是在问如何做一些不可能在不引起 C++ 中未定义或未指定行为的情况下做的事情。定义元素正确顺序的明显方法是将object1 = object2 * 2;object2++; 作为两个不同的语句。
  • @Jefffrey 我的回答解释了如何通过明确定义的行为来实现。但我的意思是你没有回答他的问题。您的回答解释了现有代码如何未指定,但没有回答“如何重载后缀增量,即 operator++(int),以便在计算表达式后完成实际增量?”的问题(正面或负面)。
【解决方案2】:

正如 cmets 中所述 - 这是一个糟糕的主意,因为您的代码语义与 operator++ 正常工作方式的语义不匹配,因此查看您的代码的其他人,甚至可能是您以后的自己,都会感到困惑。

为此,您需要使用一种称为表达式模板的技术。这并不简单,但基本思想是operator++operator+(以及任何其他运算符)将返回新类型的对象(例如Myclass_expr),并且该对象会在运行时构建一个表达式;所以x++ + x 的结果将是一棵小树,上面写着“这个表达式意味着将 x 添加到 x 然后递增 x”。尚未执行实际的算术运算。

最后,Myclass_expr 将有一个到 Myclass 的转换运算符,其中包含遍历存储的表达式并按您想要的顺序执行所有计算的逻辑。

要查看此示例,请查看 Eigen 矩阵库。

【讨论】:

    【解决方案3】:

    好的,我找到了一个丑陋但简单的方法来做到这一点,所以我将它发布给后代。我在myClass 中添加了一个myClass * ptr 字段。它通常存储nullptr,但在operator++(int) 返回的对象中除外。在这种情况下,ptr 存储一个指向应该递增的对象的指针。我还修改了析构函数,以便在销毁临时对象时执行增量:

        myClass& operator++(int){
          myClass tmp(x);
          tmp.ptr = this;
          return tmp;
        }
    
        ~myClass(){
          if(ptr != nullptr)
            ++ptr->x;
        }
    

    虽然没有标准化,tmp 通常会在到达分号时被破坏,即在执行x++ + x + x; 时,在计算总和后进行递增。

    【讨论】:

      【解决方案4】:
      myClass object1(0), object2(1);
      object1 = object2++ + object2;
      

      类似于:

      int i1 = 0; i2 = 1;
      i1 = i2++ + i2;
      

      不保证产生确定性值。 i2++i2 的评估不保证从左到右。根据首先评估哪个,该表达式的 RHS 可以评估为 32

      你应该重新考虑你的代码。

      制作:

      object1 = object2;
      object1 += (object2++);
      

      object1 = (object2++);
      object1 += object2;
      

      取决于你的意图。

      【讨论】:

      • @Jefffrey,如果首先评估i2++,那么i2++ + i2 将产生4。正如您在回答中所说,它确实是UB。
      • 如果object2++object2 之前被评估,那么object2+ 之后的第二个)将变为2,结果将是3 而不是2
      • @Jefffrey, i2++ + i2 将评估为3 o 2,具体取决于首先评估哪个术语。你是对的。
      • 这不是类似的,因为int 版本会导致未定义的行为,而类版本不会
      猜你喜欢
      • 2013-02-21
      • 2010-10-14
      • 1970-01-01
      • 1970-01-01
      • 2014-12-09
      • 2011-12-21
      • 2010-12-29
      • 1970-01-01
      • 2012-04-13
      相关资源
      最近更新 更多