为啥 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&lt;T&gt;) 按值接受它的参数。

如果您按照其名称所暗示的方式使用std::initializer_list,即作为某个对象的初始化程序,我知道我们不关心复制其元素,因为无论如何它们都会被复制以初始化对象。但是,在这种情况下,我们很可能不需要任何副本,因此如果可能的话,将参数设为 std::initializer_list&lt;const T&amp;&gt; 似乎更合理。

这种情况的最佳做法是什么?如果您不想制作不必要的副本,您是否应该不调用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&lt;const T&amp;&gt; 这样的东西。初始化列表只能按值保存对象。

参见[dcl.init.list]/5:

std::initializer_list&lt;E&gt; 类型的对象是从初始化列表构造的,就好像实现分配了一个包含 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 - 使用 minmmin,制作两个额外的副本(用于 ab)。

【讨论】:

【参考方案2】:

总结一下cmets:

std::initializer_list 应该是cppreference.com 中描述的轻量级代理。 因此,初始化列表的副本应该非常快,因为不会复制底层元素:

C++11 §18.9/2

initializer_list&lt;E&gt; 类型的对象提供对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>) 按值接受参数?的主要内容,如果未能解决你的问题,请参考以下文章

MinGW 错误:“min”不是“std”的成员

使用 std::initializer_list 创建指向 std::min 的函数指针

#define NOMINMAX 使用 std::min/max

用 std::min 将迭代器值钳位到 end()

列表上的 std::min_element 不返回最小值

是否在空的初始化程序列表(并明确指定类型)上调用 std::min 未定义的行为?