【问题标题】:why is `std::initializer_list` often passed by value?为什么`std::initializer_list`经常按值传递?
【发布时间】:2013-07-23 07:03:43
【问题描述】:

在我在 SO 上看到的几乎每一篇涉及std::initializer_list 的帖子中,人们倾向于按值传递std::initializer_list。根据这篇文章:

https://web.archive.org/web/20120707045924/cpp-next.com/archive/2009/08/want-speed-pass-by-value/

如果要复制传递的对象,则应按值传递。但是复制std::initializer_list 不是一个好主意,因为

复制std::initializer_list 不会复制底层对象。 底层数组在生命周期后不保证存在 原来的初始化列表对象已经结束。

那么为什么它的一个实例通常是按值传递而不是通过 const& 来保证不会产生不必要的副本?

【问题讨论】:

  • 据我了解,编译器识别出常见的“按值返回”习语并将其优化为“移动”,这实际上在其实现中不涉及任何内存复制。
  • @MatthewD 不过我们不是说要回到这里。

标签: c++ c++11


【解决方案1】:

它是按值传递的,因为它很便宜。 std::initializer_list,作为一个瘦包装器,最有可能实现为一对指针,因此复制(几乎)与通过引用传递一样便宜。此外,我们实际上并没有执行复制,我们(通常)执行 move,因为在大多数情况下,参数无论如何都是由临时构造的。但是,这不会对性能产生影响 - 移动两个指针与复制它们一样昂贵。

另一方面,访问副本的元素可能更快,因为我们避免了额外的取消引用(引用的取消引用)。

【讨论】:

  • const& 不一定作为指针实现(如果我没记错标准的话),它也可能是某种别名。
  • @user1095108 是的,标准没有指定。然而,在现实世界中并没有神奇的“别名”类型。引用要么被完全忽略——在本地范围内,我们可以只引用原始实体——或者被指针替换。如果您通过引用将值传递给函数(并且该函数未内联),则实际上保证会被指针替换。
  • @user1095108 也是如此。然而,仅仅通过引用访问它们一次的成本可能很好地抵消了在传递参数时复制一个额外指针的成本(实际上,结果可能相同)。
  • @user1095108 它实际上是有保证的:标准的 also 通过值传递std::initializer_list。这是一个非常强烈的暗示。如果标准库使用按值传递,那么你可以安全地做同样的事情,因为即使效率低下,你也会受到这种影响。此外,该标准给出了如何实现它的明确建议(第 18.9/2 节:“一对指针或一个指针加上一个长度将是 initializer_list 的明显表示。”)。
  • @user1095108 从概念上讲,initializer_list 是两个迭代器。整个 STL 是围绕复制迭代器成本低廉的假设而设计的。支持按值传递的论据比 Konrad 提出的论据要多得多——最重要的可能是传递的值将是一个带有微不足道的析构函数的临时值,因此可以直接构造,因为参数可能是最能说明问题——但不能针对按值传递来调用性能考虑。
【解决方案2】:

可能出于同样的原因,迭代器几乎总是按值传递:复制迭代器被认为是“便宜的”。在initializer_list 的情况下,还有一个事实是大多数实例将是带有微不足道的析构函数的临时实例,因此编译器可以直接在放置函数参数的地方构造它们,而无需复制。最后,像迭代器一样,被调用的函数很可能想要修改值,这意味着如果它是通过对 const 的引用传递的,它必须在本地复制它。

编辑:

只是为了概括:标准库通常假设最好按值传递迭代器、initializer_lists 和函数对象。因此,您应该确保您设计的任何迭代器、iterator_lists 或功能对象复制起来都很便宜,并且您应该在自己的代码中假设它们复制起来很便宜。关于何时使用对 const 的引用以及何时使用 value 的传统规则可能应该修改以反映这一点:

对除迭代器、initializer_lists 或函数对象之外的类类型使用对 const 的引用传递;否则使用按值传递。

【讨论】:

  • 但是为什么要复制一个std::function<> 实例呢?复制肯定不便宜。
  • @user1095108 标准库总是通过副本传递它。因此,实现应该尽一切努力使其复制成本低。 (并且您应该避免做会导致复制成本高昂的事情:std::function 内部带有大的 std::vector不是一个好主意。)
  • 说到std::functionstd::bind 的结果可以相互移动,移动会移动绑定的参数。我进行了调试以验证情况是否如此。因此,从包含例如绑定向量的std::function 移动应该是相对有效的——因为可以有效地从中移动向量。
  • @haelix 移动语义引入了另一组需要考虑的问题,但我认为它们对一般规则的改变不大。初始化列表可能总是在与移动相关的上下文中使用;迭代器通常不是。功能对象因对象类型而异。我仍然会尝试让这三个都便宜复制;支持移动取决于我复制成功的成本。
  • @user1095108 即使我们承认某些std::functions 复制起来并不便宜,但其他类型的仿函数通常几乎是免费的。
猜你喜欢
  • 2014-11-19
  • 2020-08-03
  • 1970-01-01
  • 1970-01-01
  • 2022-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-09
相关资源
最近更新 更多