当参数被传递给推断参数类型的函数模板时,应该如何传递参数是一个有趣的问题。值得注意的是,使用参数的确切性质无关紧要:参数是否用作函数对象、迭代器、值等与答案无关。有四个选项:
template <typename T> void f(T&& a)
template <typename T> void f(T& a)
template <typename T> void f(T const& a)
template <typename T> void f(T a)
函数模板的实际作用有点重要:如果f()
它的参数所做的a
只是将其转发给另一个函数,那么您希望通过通用引用传递。被调用的函数将整理细节并拒绝不合适的选项。当然,虽然通常有用,但转发功能有些乏味。
如果f()
真的对对象做了什么,我们通常可以立即丢弃两个选项:
- 通过通用引用传递会导致我们不知道它是引用还是值的类型。除了转发类型之外,这对于其他任何事情都毫无用处,因为它的行为要么像值,要么像引用。仅指定函数实际执行的操作将是一个有问题的舞蹈。
- 绕过对
T const&
我们也没有多大好处:我们得到了一个对象的引用,该对象的生命周期我们无法控制,我们既不能从中移动也不能复制/删除它。
如果对象本身被修改,则通过非const
引用传递对象可能很有用。这显然是功能契约的一个重要部分,也是一个强加的设计选择,在采用时不能被功能的客户覆盖。
首选的方法是按值取参数。通常,这使得函数行为的定义变得更加容易,并且实际上为用户保留了类型应该遵循值还是引用语义的选择!仅仅因为某些东西是按值传递的,并不意味着感兴趣的对象也按值传递。特别是对于函数对象的情况,标准 C++ 库甚至提供了一个通用适配器,给出了值类型引用语义:std::ref()
.
当然,界面设计是微妙的,在某些情况下,每个选项都是必要的。但是,作为一般经验法则,我认为这很简单:
- 转发函数使用通用引用。
- 使用参数做某事的函数使用值。
...当然,这些规则仅适用于推导出类型的函数模板的参数。