我在cpPreference.com上查看了std::find_if
的各种签名,我注意到接受谓词函数的口味似乎按值接受它:
template< class InputIt, class UnaryPredicate >
InputIt find_if( InputIt first, InputIt last,
UnaryPredicate p );
如果我理解正确的话,带有捕获变量的lambda会为其数据的引用或副本分配存储空间,因此“按值传递”可能意味着为调用复制捕获数据的副本。
另一方面,对于函数指针和其他可直接寻址的东西,如果直接传递函数指针,而不是通过引用到指针(指针到指针)的方式,性能应该会更好。
第一,这样做正确吗? 上面的UnaryPredicate
是按值参数吗?
第二,我对通过lambdas的理解是否正确?
第三,在这种情况下按值传递而不是按引用传递是否有原因? 更重要的是,难道没有一些足够模糊的语法(你好,通用引用)可以让编译器做它想做的任何事情来获得最大的性能吗?
上面的UnaryPredicate是不是一个by-value参数?
是的,这就是它在函数参数列表中所说的。 它接受一个推导的值类型。
除此之外,lambda表达式是prvalues。 这意味着,使用C++17的保证复制省略,直接从lambda表达式初始化p
。 在将闭包或捕获的对象传递到函数中时,不会对其进行额外的复制(函数可能会在内部进行更多的复制,尽管这并不常见)。
如果谓词是通过引用传递的,则需要物化一个临时对象。 因此,对于lambda表达式,通过引用传递的开关不会获得任何东西。
如果您有其他类型的谓词,可以扩展到复制,那么您可以将std::reference_wrapper
传递到该谓词对象,以获得廉价的“句柄”。 包装器的操作符()
将执行正确的操作。
这个定义大多是历史性的,但是现在通过值传递来实现它已经不是什么问题了。
为了详细说明为什么引用语义学会很糟糕,让我们试着通过这些年来对它进行研究。 简单的lvalue引用是不行的,因为现在我们不支持绑定到rvalue。 常量值引用也不行,因为现在我们要求谓词不修改任何内部状态,为什么呢?
所以到了C++11,我们真的没有其他选择。 通过值传递比引用更好。 有了新标准,我们可能会修改我们的做法。 为了支持rvalues,我们可以添加一个rvalue引用重载。 但这是一个冗余的练习,因为它不需要做任何不同的事情。
通过传递一个值,调用方可以选择如何创建它,对于prvalues,在C++17中,它实际上是免费的。 如果调用方愿意,它们可以显式地提供引用语义。 因此没有任何损失,而且我认为在使用和API设计的简单性方面获得了很多。
其实有多重原因:
>
您总是可以将推导的值参数转换为使用引用语义,但不是相反:只需传递std::ref(x)
而不是x
即可。 std::reference_wrapper
并不完全等同于传递引用,但特别是对于function对象,它做的事情是正确的。 也就是说,按值传递泛型参数是更通用的方法。
按引用传递(T&
)不适用于临时对象或const
对象,T const&
不适用于非const&
对象,也就是说,唯一的选择是T&
(转发引用),它在C++11之前是不存在的,算法接口自C++98引入以来没有变化。
与任何类型的引用参数(包括转发引用)不同,值参数可以被复制省略。