【问题标题】:Create C++ integer class to act absolutely identical to integral integer type创建 C++ 整数类以与整数类型完全相同
【发布时间】:2011-12-16 10:19:25
【问题描述】:

我几天前看到的一个小而非常讨厌的问题,在面试时问我的朋友。

最初的面试问题是:“以下代码的输出是什么?”

int i = 2;
i = i++ + i++;

正确答案是 ((2 + 2) + 1) + 1 = 6,即在赋值之前应用两次后增量,但在加法之后。

然后我想创建一个带有一个整数的简单类,并重载 operator+() 和 operator++(int) 以在日志中查看执行运算符的确切顺序。

这是我得到的:

class A
{
public:
A(int _data) : data(_data) { }

A &operator=(const A& _rhs)
{
    data = _rhs.data;
    cout<<" -- assign: "<<data<<endl;
}

A operator++(int _unused)
{
    A _tmp = data;
    data++;

    cout<<" -- post-increment: "<<data<<endl;
    return _tmp;
}

A operator+(const A &_rhs)
{
    A _tmp = data + _rhs.data;

    cout<<" -- addition: "<<data<<"+"<<_rhs.data<<endl;
    return _tmp;
}

inline operator int() const { return data; }

private:
    int data;
};

结果令人沮丧:

-- post-increment: 3
-- post-increment: 4
-- addition: 3+2
-- assign: 5

对于不太复杂的构造,例如 (A _dt2 = a++; ),它会按照应有的方式运行,但运算符的执行顺序与整数类型不同。

我猜这可能是编译器特定的问题:

$ gcc --version
gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3
Copyright (C) 2009 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

所以,我有点迷路了:)

【问题讨论】:

  • 我很确定第一个截图的输出是未定义的
  • 是的,第一个 sn-p 有未定义的行为。
  • 隐藏的采访 Q 是:Can you detect that this is Undefined Behavior?
  • @Als:感谢您提供更好的链接来提供我的答案,但它不是直接重复的。该问题的主要主题是模拟类 wrt 之类的整数。增量。

标签: c++ operator-overloading


【解决方案1】:

最初的面试问题是:“以下代码的输出是什么?”

int i = 2;
i = i++ + i++;

正确答案是 undefined behaviour,因为您要多次修改同一个变量,中间没有序列点。

C++03 标准§5 [expr] p4:

除非另有说明,单个运算符的操作数和单个表达式的子表达式的求值顺序以及副作用发生的顺序是未指定的。

这可能无法回答您真正的问题,但即使您创建类似整数的类并重载operator++(int)operator+(A const&amp;),它也会相似。函数参数的求值顺序是未指定的,它可以按照编译器喜欢的任何顺序完成,因此结果是未指定的。

【讨论】:

  • 如果两个运算符都被重载,那么行为就不再是未定义的——每个运算符都变成了一个函数调用,它引入了一个序列点。正如您所说,结果仍然未指定。
  • @Mike:不要只是删除您的评论并创建一个新评论。 :P 但是,是的,你是对的,运算符成为函数调用的乐趣。
  • 您不妨添加来自标准的引文,C++03 第 5 节:表达式,第 4 段: 除非另有说明 [例如&& 和 ||] 的特殊规则、单个运算符的操作数和单个表达式的子表达式的求值顺序以及副作用发生的顺序是 Unspecified
  • @MikeSeymour,@Xeo:在重载的情况下,“+”操作数的唯一求值顺序是未指定的;但它不是 UB 并且 i = i++ + i++ 的 最终结果 可以通过 + 和 ++ 的正确实现来很好地指定(如果实现尊重数字 + 运算的幺半群属性并将 ++ 视为+e 返回前一个值)
  • ...抱歉,“+”的可交换性是必需的并且几乎足够了:)
【解决方案2】:

除了其他人已经指出的:单独考虑你的问题的标题 - “创建 C++ 整数类以与整数类型完全相同” - 我应该指出一个完全不同的原因,为什么它是 不可能的

(据我所知)不可能用类模拟 ||&& 运算符的快捷行为,即两者无论如何都会评估操作数的两侧。

编辑: 查看 cmets。 “据我所知”似乎还不够。然而,史蒂夫·杰索普(Steve Jessop)有一个不同的例子,证明了整体观点是有效的。

这与您的增量问题完全无关,但与您的问题标题有关,所以我认为应该提及。

【讨论】:

  • 但是如果你想模拟一个整数类型,我认为你不需要重新实现这些运算符。运算符&amp;&amp;|| 可以自动调用你的类'operator bool(),在这种情况下它们的短路行为是守恒的。
  • 这是真的,虽然这是不可能的。一个更强有力的原因是将 A 类的实例隐式转换为(例如)long,“用完”您在隐式转换中允许的 1 个用户定义转换,而将 int 转换为 long 不会不。因此,如果您有另一个类B 的构造函数采用longint i = 0; A a = i;,您会发现B b = i; 是允许的,但B b = a; 不是因为转换链A -> int -> long -> B 涉及两个用户定义的转换。
  • @Fabio A.:这就是你从(遥远而模糊的)记忆中做出陈述的结果。我确定我在某处读过这篇文章,并且 98% 的人肯定它是在 Scott Meyer 出色的“Effective C++”中的某个地方,但我无法再准确回忆它,也无法在测试代码中重现该效果。雪上加霜的是,我再也无法访问那本书,现在也无法查找。 /me unhappy...但史蒂夫在这里提出了另一个强项,所以总体信息成立。
  • 使用一些表达式模板,我怀疑可以正确地将它们短路,但它会给您的实现增加相当多的额外工作。
  • @SteveJessop 但是如果AB 属于同一个类FakeInt,那么你只是在那里调用复制构造函数。
【解决方案3】:

正确答案是 ((2 + 2) + 1) + 1 = 6,即在赋值之前应用两次后增量,但在加法之后。

这不是正确答案:

除非另有说明,单个运算符的操作数和单个表达式的子表达式的求值顺序以及副作用发生的顺序是未指定的。在前一个和下一个序列点之间,一个标量对象的存储值最多只能通过表达式的评估修改一次。此外,只能访问先验值以确定要存储的值。对于完整的子表达式的每个允许排序,都应满足本段的要求。 表达;否则行为未定义。
- ISO-IEC-14882

【讨论】:

    【解决方案4】:

    实际上,你在早期犯了一个非常严重的错误。

    最初的面试问题是:“以下代码的输出是什么?”

    int i = 2;
    i = i++ + i++;
    

    这个问题的正确答案是“输出未定义”。

    通过修改和读取没有中间序列点的变量,您正在调用未定义的行为。


    更具体地说,在这种情况下,最让你头疼的是+ 运算符的参数的求值顺序是未定义的;这在运算符和函数的一般情况下都是正确的,除了一些值得注意的例外,即短路逻辑运算符。

    【讨论】:

      猜你喜欢
      • 2016-10-06
      • 1970-01-01
      • 2011-09-15
      • 2012-08-19
      • 2016-02-20
      • 2014-04-19
      • 1970-01-01
      • 2012-11-18
      • 2015-06-27
      相关资源
      最近更新 更多