浅墨浓香

想要天亮进城,就得天黑赶路。

导航

7.6 Recommended Template Parameter Declarations

7.6 推荐的模板参数声明方法

 

As we learned in the previous sections, we have very different ways to declare parameters that depend on template parameters:

正如前几节介绍的那样,函数模板有多种不同的传参方式:

  • Declare to pass the arguments by value: 将参数声明为按值传递:

  This approach is simple, it decays string literals and raw arrays, but it doesn’t provide the best performance for large objects. Still the caller can decide to pass by reference using std::cref() and std::ref(), but the caller must be careful that doing so is valid.

  这一方法很简单,它会将字符串字面量和原生数组类型进行退化。但传递内存占用大的对象时性能不佳。在这种情况下,调用者仍可以通过std::cref/std::ref来按引用传递参数。但必须小心确保该对象是有效的。

  • Declare to pass the arguments by-reference:  将参数声明为按引用传递:

  This approach often provides better performance for somewhat large objects, especially when passing

  这一方法对大的对象能够提供较好的性能,特别是当

    – existing objects (lvalues) to lvalue references,

       将己经存在的对象(lvalue)传递给左值引用,

    – temporary objects (prvalues) or objects marked as movable (xvalue) to rvalue references,

       将临时对象(prvalue),或者可移动对象(xvalue)传递给右值引用,

    – or both to forwarding references.

       或将以上两种类型的对象传递给转发引用。

Because in all these cases the arguments don’t decay, you may need special care when passing string literals and other raw arrays. For forwarding references, you also have to beware that with this approach template parameters implicitly can deduce to reference types.

由于这几种情况下参数类型都不会退化(decay),因此在传递字符串字面量和其他原生数组要格外小心。对于转发引用,需要意识到模板参数可能会隐式推导为引用类型。

 

General Recommendations

一般性建议

With these options in mind, for function templates we recommend the following:

考虑到这些传参方式,对于函数模板我们建议如下:

  1. By default, declare parameters to be passed by value. This is simple and usually works even with string literals. The performance is fine for small arguments and for temporary or movable objects. The caller can sometimes use std::ref() and std::cref() when passing existing large objects (lvalues) to avoid expensive copying.

  默认情况下,采用按值传递。这种方法比较简单,就算对于字符串字面量,通常也凑效。对于比较小的对象、临时对象以及可移动对象,这种方法的性能也不错。为了避免成本高昂的拷贝,对于较大的对象可以使用std::ref/std::cref。

  2. If there are good reasons, do otherwise:  如果有充分的理由传引用就另当别论,例如:

    – If you need an out or inout parameter, which returns a new object or allows to modify an argument to/for the caller, pass the argument as a nonconstant reference (unless you prefer to pass it via a pointer). However, you might consider disabling accidentally accepting const objects as discussed in Section 7.2.2 on page 110.

    如果需要传递out或inout参数时,该参数返回一个新对象或允许调用者修改的对象,那么就使用非const引用来传参(除非你更喜欢使用指针)。但是,需要按照第110页7.2.2节介绍的方法来考虑禁用接受const对象。

  – If a template is provided to forward an argument, use perfect forwarding. That is, declare parameters to be forwarding references and use std::forward<>() where appropriate. Consider using std::decay<> or std::common_type<> to “harmonize” the different types of string literals and raw arrays.

    如果模板是用于转发参数的,请选择完美转发。也就是将参数声明为转发引用并在适当时使用std::forward<>。在处理字符串字面量和原生数组的类型时,可以考虑使用std::decay<>或者std::common_type来使得对它们的处理更一致。

  – If performance is key and it is expected that copying arguments is expensive, use constant references. This, of course, does not apply if you need a local copy anyway.

    如果很在意运行效率,并且预期到拷贝参数的代价会很昂贵,请使用const引用。当然,如果你需要创建一个副本(局部对象),这条建议就不适用了。

  3. If you know better, don’t follow these recommendations. However, do not make intuitive assumptions about performance. Even experts fail if they try. Instead:  Measure!

      如果你更了解程序的情况,可以不遵循这些建议。但是请不要仅凭直觉对性能进行评估,即便是专家也会失误。相反,你应该去做实际的测试!

 

Don’t Be Over-Generic

不要过度地泛型化

 

Note that, in practice, function templates often are not for arbitrary types of arguments. Instead, some constraints apply. For example, you may know that only vectors of some type are passed. In this case, it is better not to declare such a function too generically, because, as discussed, surprising side effects may occur.

值得注意的是,在实际应用中,函数模板通常不需要适用所有的类型,而是有一定的限制。例如,你可能己经知道函数模板的参数只会是某些vector类型。在这种情况下,最好不要将函数模板定义得过于泛化。因为,正如之前讨论的,这可能出现一些令人意外的副作用。

Instead, use the following declaration:

针对这种情况,可以按如下方式声明:

template<typename T>
void printVector (std::vector<T> const& v)
{
    …
}

With this declaration of parameter v in printVector(), we can be sure that the passed T can’t become a reference because vectors can’t use references as element types. Also, it is pretty clear that passing a vector by value almost always can become expensive because the copy constructor of std::vector<> creates a copy of the elements. For this reason, it is probably never useful to declare such a vector parameter to be passed by value. If we declare parameter v just as having type T deciding, between call-by-value and call-by-reference becomes less obvious.

在printVector()函数中通过这样声明参数v,可以保证传入的T不会是引用类型。因为vector的元素类型不能为引用类型。同样,很显然vector的拷贝构造函数会创建所有元素的副本,按值传递vector代价几乎总是很昂贵。因此,当传递vector时将参数声明为按值传递不会有什么好处。如果我们直接将参数v声明为T类型,那么按值传递还是按引用传递之间的关系就不那么明显了。

 

The std::make_pair() Example

以std::make_pair()为例

 

std::make_pair<>() is a good example to demonstrate the pitfalls of deciding a parameter passing mechanism. It is a convenience function template in the C++standard library to create std::pair<> objects using type deduction. Its declaration changed through different versions of the C++ standard:

std::make_pair<>是一个很好例子,它演示了参数传递机制的陷阱。使用它可以很方便地通过类型推导来创建一个std::pair<>对象。它的定义在C++的各个版本中都不一样:

  • In the first C++ standard, C++98, make_pair<>() was declared inside namespace std to use call-by-reference to avoid unnecessary copying:

     在第1版的C++标准(C++98)中,std::make_pair<>被定义在std命名空间中,并且使用按引用传递来避免不必要的拷贝:

template<typename T1, typename T2>
pair<T1,T2> make_pair (T1 const& a, T2 const& b)
{
    return pair<T1,T2>(a,b);
}

   This, however, almost immediately caused significant problems when using pairs of string literals or raw arrays of different size.

   但是,当使用std::pair<>来存储长度不同的字符串字面量或原生数组时,会马上会出现一个严重的问题。

  • As a consequence, with C++03 the function definition was changed to use call-by-value:

     因此在C++03中,该函数模板的定义被改为按值传递参数:

template<typename T1, typename T2>
pair<T1,T2> make_pair (T1 a, T2 b)
{
    return pair<T1,T2>(a,b);
}

  As you can read in the rationale for the issue resolution, “it appeared that this was a much smaller change to the standard than the other two suggestions, and any efficiency concerns were more than offset by the advantages of the solution.”

  正如你在解决问题的原理中看到的那样,与其他两个建议相比,这个方案对于标准库的修改似乎要小得多,而且该解决方案的优点足以弥补它对性能方面的损失。

  • However, with C++11, make_pair() had to support move semantics, so that the arguments had to become forwarding references. For this reason, the definition changed roughly again as follows:

  不过,在C++11中,make_pair<>()需要支持移动语义,就必须使用转发引用。因此,函数的定义大体上是这样的:

template<typename T1, typename T2>
constexpr pair<typename decay<T1>::type, typename decay<T2>::type>
make_pair (T1&& a, T2&& b)
{
    return pair<typename decay<T1>::type, typename decay<T2>::type>(forward<T1>(a), forward<T2>(b));
}

The complete implementation is even more complex: To support std::ref() and std::cref(), the function also unwraps instances of std::reference_wrapper into real references.

完整的实现可能会更复杂:为了支持std::ref和std::cref,该函数会将std::reference_wrapper展开为真正的引用。

The C++ standard library now perfectly forwards passed arguments in many places in similar way, often combined with using std::decay<>.

目前C++标准库在很多地方都使用了类似的方法对参数进行完美转发,而且经常结合std::decay<>来使用。

 

7.7 Summary

7.7 小结

 

  • When testing templates, use string literals of different length.

  使用不同长度的字符串字面量来对模板进行测试。

  • Template parameters passed by value decay, while passing them by reference does not decay.

  模板参数的类型在按值传递时会退化,但按引用传递则不会。

  • The type trait std::decay<> allows you to decay parameters in templates passed by reference.

  当函数模板中按引用传递参数时,可以使用std::decay<>来手动退化。

  • In some cases std::cref() and std::ref() allow you to pass arguments by reference when function templates declare them to be passed by value.

  在某些情况下,对于被声明为按值传递参数函数模板,可以使用std::cref/std::ref来模拟实现按引用传递。

  • Passing template parameters by value is simple but may not result in the best performance.

  按值传递模板参数虽然很简单,但性能不是最佳的。

  • Pass parameters to function templates by value unless there are good reasons to do otherwise.

   除非有更好的理由,否则将参数传递给函数模板时,就使用按值传递。

  • Ensure that return values are usually passed by value (which might mean that a template parameter can’t be specified directly as a return type).

   通常情况下,应确保返回类型是按值返回(这也意味着不能直接将模板参数指定为返回值类型)

  • Always measure performance when it is important. Do not rely on intuition; it’s probably wrong.

  当性能很重要时,要经常进行实际测试。但不要相信直觉,因为它可能是错的。