为啥 std::min(std::initializer_list<T>) 按值接受参数?
Posted
技术标签:
【中文标题】为啥 std::min(std::initializer_list<T>) 按值接受参数?【英文标题】:Why does std::min(std::initializer_list<T>) take arguments by value?为什么 std::min(std::initializer_list<T>) 按值接受参数? 【发布时间】:2014-11-19 04:49:34 【问题描述】:阅读this question 的答案后,我惊讶地发现std::min(std::initializer_list<T>)
按值接受它的参数。
如果您按照其名称所暗示的方式使用std::initializer_list
,即作为某个对象的初始化程序,我知道我们不关心复制其元素,因为无论如何它们都会被复制以初始化对象。但是,在这种情况下,我们很可能不需要任何副本,因此如果可能的话,将参数设为 std::initializer_list<const T&>
似乎更合理。
这种情况的最佳做法是什么?如果您不想制作不必要的副本,您是否应该不调用initializer_list
版本的std::min
,或者是否有其他技巧可以避免复制?
【问题讨论】:
AFAIK,使用引用比传递int
的副本需要更多的 CPU 周期,并且比传递更小的变量需要更多的内存(至少在 x86 上)。
我认为原因在N2722 中有所描述,尽管我不愿意相信性能比较和结论。可能会有更多的讨论记录在某个地方。
相关:nd.home.xs4all.nl/dekkerware/issues/n2772_fix/…
@dyp 顺便说一句,在 n2772_fix 中它说可变参数模板优先于 initializer_list。为什么标准委员会决定用 initializer_list 而不是可变参数模板定义 std::min?
@dyp:链接已损坏,论文现在在这里:open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2722.pdf。
【参考方案1】:
没有std::initializer_list<const T&>
这样的东西。初始化列表只能按值保存对象。
参见[dcl.init.list]/5:
std::initializer_list<E>
类型的对象是从初始化列表构造的,就好像实现分配了一个包含 N 个const E
类型元素的临时数组,其中 N 是初始化列表中的元素数。
没有引用数组,因此也不能有 initializer_list
的引用。
在这种情况下,我的建议是为 std::min
编写一个替代品,它采用转发引用的可变参数模板。这行得通(尽管我确信它可以改进):
template<typename T, typename... Args>
T vmin( T arg1, Args&&... args )
T *p[] = &arg1, &args... ;
return **std::min_element( begin(p), end(p),
[](T *a, T *b) return *a < *b; );
See it working - 使用 min
或 mmin
,制作两个额外的副本(用于 a
和 b
)。
【讨论】:
【参考方案2】:总结一下cmets:
std::initializer_list
应该是cppreference.com 中描述的轻量级代理。
因此,初始化列表的副本应该非常快,因为不会复制底层元素:
C++11 §18.9/2
initializer_list<E>
类型的对象提供对const E.
类型对象数组的访问 [ 注意: 一对指针或一个指针加上一个长度将是initializer_list
的明显表示。initializer_list
用于实现 8.5.4 中指定的初始化列表。复制初始化列表不会复制底层元素。 — 尾注 ]
虽然使用引用会归结为使用指针,因此需要额外的间接。
因此,std::min
的实际问题不是它按值获取 initializer_list
,而是如果 initializer_list
的参数在运行时计算,则必须复制它们。 [1]
不幸的是,发现 [1] 的基准被打破了 later。
auto min_var = std::min(1, 2, 3, 4, 5); // fast
auto vec = std::vector<int>1, 2, 3, 4, 5;
min_var = std::min(vec[0], vec[1], vec[2], vec[3], vec[4]); // slow
[1]:N2722 p。 2
【讨论】:
我认为您完全误解了问题和 cmets。他们抱怨是initializer_list
中的元素是在运行时复制的,而不是有initializer_list
的引用。
好点!我忽略了“显示另外 8 个 cmets”链接,一旦我看到它就会导致编辑混乱。虽然我不会说我误解了这个问题。因为初始化器列表在创建时不需要创建副本。这取决于情况。
您的“基准”似乎声称复制 int
很慢。它不应该是大型复制对象的向量吗? (好吧,我看到链接的文章确实有这个,每个都是百万元素的std::list
)
这不是一个基准,而是一个示例来说明问题并保持代码小。以上是关于为啥 std::min(std::initializer_list<T>) 按值接受参数?的主要内容,如果未能解决你的问题,请参考以下文章
使用 std::initializer_list 创建指向 std::min 的函数指针