为什么`std :: initializer_list`通常按值传递?


71

我在SO上看到的几乎所有帖子中都包含a std::initializer_list,人们倾向于std::initializer_list按值传递a 。根据这篇文章:

http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

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

复制astd::initializer_list不会复制基础对象。在原始初始化程序列表对象的生存期结束后,不能保证基础数组存在。

那么,为什么它的一个实例经常按值而不是按值传递const&,即保证不会产生不必要的复制?


1
据我了解,编译器会识别常见的“按值返回”习惯用法,并将其优化为“移动”,实际上在其实现中不涉及任何内存副本。
MatthewD

1
@MatthewD我们不是在谈论返回这里。
Konrad Rudolph

Answers:


61

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

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


1
const&尽管不一定实现为指针(如果我正确地记住了标准),它也可能是某种别名。
user1095108 2013年

11
@ user1095108是的,标准未指定。但是,在现实世界中,没有神奇的“别名”类型。引用要么被完全删除(在本地范围内,我们只能引用原始实体),要么被指针替换。如果按引用传递的值的函数(这则不内联函数),它实际上担保由一个指针来代替。
Konrad Rudolph

1
@ user1095108也是如此。但是,仅通过引用访问它们一次的开销就可以很好地抵消传递参数时复制一个附加指针的开销(实际上,结果可能是相同的)。
Konrad Rudolph

9
@ user1095108实际上是有保证的:标准std::initializer_list按值传递。这是一个非常强烈的暗示。如果标准库使用按值传递,那么您可以放心地做同样的事情,因为即使效率低下,您仍然会遭受这种影响。此外,该标准对如何实现它给出了明确的建议(第18.9 / 2节:“一对指针或一个指针加上一个长度将明显表示initializer_list。”)。
Konrad Rudolph

2
@ user1095108从概念上讲,aninitializer_list是两个迭代器。整个STL的设计基于复制迭代器便宜的假设。支持传递值的参数比Konrad提出的参数要多得多--最重要的可能是传递的值将是带有琐碎析构函数的临时值,因此可以直接构造,因为该参数可能是最有说服力的-但是不能将性能考虑与按价值传递有关。
James Kanze

14

可能出于相同的原因,迭代器几乎总是按值传递:复制迭代器被视为“便宜”。在的情况下initializer_list,还存在一个事实,即大多数实例将是带有琐碎析构函数的临时变量,因此编译器可以在放置函数参数的位置直接构建它们,而没有副本。最后,存在一个事实,就是像迭代器一样,被调用的函数可能想修改该值,这意味着如果通过引用将其传递给const,则必须在本地复制该值。

编辑:

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

对于迭代器,initializer_lists或函数对象以外的类类型,对const使用按引用传递。否则使用按值传递。


1
但是为什么要复制一个std::function<>实例呢?当然,复制并不便宜。
user1095108

1
@ user1095108标准库始终通过副本传递它。因此,实现应尽一切可能使其廉价地进行复制。(而且您应该避免做会增加复制成本的事情:里面std::function有一个很大的std::vector东西不是一个好主意。)
James Kanze

说到std::function:的结果std::bind可以相互移动,移动将移动绑定的参数。我调试以验证是这种情况。因此,从std::function包含例如绑定矢量的移出应该相对有效-因为矢量可以有效地移出。
haelix

@haelix Move语义引入了另一组要考虑的问题,但我认为它们不会改变一般规则。初始化列表可能总是在与移动相关的上下文中使用。迭代器通常不会。功能对象根据对象的类型而有所不同。我仍然会尝试将所有三个便宜的东西复制下来。支持的举动取决于我成功制作副本的便宜程度。
James Kanze 2015年

@ user1095108即使我们接受某些std::function副本的复制并不便宜,其他种类的函子通常也是免费的。
Lightness Races in Orbit,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.