int 与 const int&

新手上路,请多包涵

我注意到我通常使用常量引用作为返回值或参数。我认为原因是它的工作原理与在代码中使用非引用几乎相同。但它肯定会占用更多空间,并且函数声明会变得更长。我可以接受这样的代码,但我认为有些人认为这是一种糟糕的编程风格。

你怎么看?是否值得写 const int& over int ?我认为它无论如何都被编译器优化了,所以也许我只是在浪费时间编码它,a?

原文由 Pijusn 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 544
2 个回答

在 C++ 中,我认为使用 const T& 的反模式很常见,就像在处理参数时说 T 的一种聪明方式。然而,值和引用(无论是否为 const)是两个完全不同的东西,总是盲目地使用引用而不是值可能会导致细微的错误。

原因是在处理引用时,您必须考虑值不存在的两个问题: 生命周期别名

举个例子,应用这种反模式的地方是标准库本身,其中 std::vector<T>::push_back 接受参数 a const T& 而不是一个值,这可以反击,例如代码如:

 std::vector<T> v;
...
if (v.size())
    v.push_back(v[0]); // Add first element also as last element

这段代码是一个定时炸弹,因为 std::vector::push_back 想要一个 const 引用,但执行 push_back 可能需要重新分配,如果发生这种情况意味着在重新分配之后收到的引用将不再有效( 生命周期 问题),你进入未定义行为领域¹。

从今天的逻辑角度来看,C++ 更好的是接受一个值(即 void std::vector<T>::push_back(T x) ),然后有效地将该值移动到容器内的最终位置。然后调用者最终可能会使用 std::move 如果认为这很重要(但请注意,原始 C++ 中不存在移动构造的想法)。

如果使用 const 引用而不是值,则 别名 问题反而会成为微妙问题的根源。例如,我被这种代码咬过:

 struct P2d
{
    double x, y;
    P2d(double x, double y) : x(x), y(y) {}
    P2d& operator+=(const P2d& p) { x+=p.x; y+=p.y; return *this; }
    P2d& operator-=(const P2d& p) { x-=p.x; y-=p.y; return *this; }
};

struct Rect
{
    P2d tl, br;
    Rect(const P2d& tl, const P2d& br) : tl(tl), bt(br) {}
    Rect& operator+=(const P2d& p) { tl+=p; br+=p; return *this; }
    Rect& operator-=(const P2d& p) { tl-=p; br-=p; return *this; }
};

代码乍一看似乎很安全, P2d 是一个二维点, Rect 是一个矩形,添加/减去一个点意味着平移矩形。

但是,如果要将矩形翻译回原点,则编写 myrect -= myrect.tl; 代码将不起作用,因为已定义翻译运算符接受(在这种情况下)引用同一实例的成员的引用。

This means that after updating the topleft with tl -= p; the topleft will be (0, 0) as it should but also p will become at the same time (0, 0) 因为 p 只是对左上角成员的引用,所以右下角的更新将不起作用,因为它将通过 (0, 0) 进行翻译,因此基本上什么都不做。

请不要因为 const 这个词而误以为 const 引用就像一个值。如果您尝试 使用该引用 更改被引用的对象,该词的存在只是为了给您编译错误,但这并不意味着被引用的对象是恒定的。更具体地说,由 const ref 引用的对象可以更改(例如,由于 _别名_),甚至可以在您使用它时不存在( 生命周期 问题)。

const T& 单词 const 表示 引用 的属性,而不是被 引用对象 的属性:这是无法使用它来更改对象的属性。可能 readonly 会是一个更好的名称,因为 const 具有 IMO 在使用引用时推动对象将保持不变的想法的心理效应。

您当然可以通过使用引用而不是复制值来获得令人印象深刻的加速,尤其是对于大类。但是在使用引用时,您应该始终考虑别名和生命周期问题,因为在幕后它们只是指向其他数据的指针。然而,对于“本机”数据类型(整数、双精度、指针),引用实际上会比值慢,并且使用它们代替值没有任何好处。

此外,一个 const 引用对于优化器来说总是意味着问题,因为编译器被迫偏执,并且每次执行任何未知代码时,它都必须假设所有引用的对象现在可能具有不同的值( const 参考对于优化器来说绝对没有任何意义;这个词只是为了帮助程序员——我个人不太确定它有这么大的帮助,但那是另一回事了)。


(1)显然( https://stackoverflow.com/a/18794634/320726 )标准说这种情况是有效的,但即使有这种解释(我完全不同意),问题仍然普遍存在。 push_back 不关心对象的 身份,因此应该按值获取参数。当您将 const 引用作为值传递给函数时,您有责任确保被引用的对象在函数的整个持续时间内保持活动状态。使用 v.push_back(v[0]) 如果没有进行预订,这完全是错误的,如果发生这种情况,IMO(鉴于 push_back 签名)是调用者的错。然而,真正的逻辑错误是 push_back 界面设计(故意这样做,牺牲了效率祭坛上的逻辑正确性)。不确定是否是因为该缺陷报告,但我看到一些编译器在这种特殊情况下“修复”了问题(即 push_back 检查被推送的元素是否来自向量本身)。

原文由 6502 发布,翻译遵循 CC BY-SA 4.0 许可协议

const int & 可能仍然需要传递一个指针,这与 int 的大小非常相似。它不太可能带来任何明显更好的性能。

传递 const 引用可能需要一些注意,以检查它不会是值的意外更改(例如,您调用的某些函数也可以访问它,甚至是另一个线程)。但这通常是微不足道的,除非变量具有异常长的生命周期和非常广泛的访问范围。

原文由 Audrius Meškauskas 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题