You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
template<typename T>
voidprintV (T arg) {
...
}
std::string const c = "hi";
printV(c); // c decays so that arg has type std::stringprintV("hi"); // decays to pointer so that arg has type char const*int arr[4];
printV(arr); // decays to pointer so that arg has type char const*
template<typename T>
voidprintR (T const& arg) {
...
}
std::string const c = "hi";
printR(c); // T deduced as std::string, arg is std::string const&printR("hi"); // T deduced as char[3], arg is char const(&)[3]int arr[4];
printR(arr); // T deduced as int[4], arg is int const(&)[4]
template<typename T>
voidoutR (T& arg) {
...
}
std::string returnString();
std::string s = "hi";
outR(s); // OK: T deduced as std::string, arg is std::string&outR(std::string("hi")); // ERROR: not allowed to pass a temporary (prvalue)outR(returnString()); // ERROR: not allowed to pass a temporary (prvalue)outR(std::move(s)); // ERROR: not allowed to pass an xvalue
同样,这种情况不会发生decay:
int arr[4];
outR(arr); // OK: T deduced as int[4], arg is int(&)[4]
传递universal reference
这个也是声明参数为引用的一个重要场景:
template<typename T>
voidpassR (T&& arg) { // arg declared as forwarding reference
...
}
std::string s = "hi";
passR(s); // OK: T deduced as std::string& (also the type of arg)passR(std::string("hi")); // OK: T deduced as std::string, arg is std::string&&passR(returnString()); // OK: T deduced as std::string, arg is std::string&&passR(std::move(s)); // OK: T deduced as std::string, arg is std::string&&passR(arr); // OK: T deduced as int(&)[4] (also the type of arg)
但是这里需要额外注意一下,这是T隐式被声明为引用的唯一情况:
template <typename T>
voidpassR(T &&arg) { // arg is a forwarding reference
T x; // for passed lvalues, x is a reference, which requires an initializer
...
}
foo(42); // OK: T deduced as intint i;
foo(i); // ERROR: T deduced as int&, which makes the declaration of x in passR() invalid
auto s = std::make_shared<std::string>("whatever");
auto& c = (*s)[0];
s.reset();
std::cout << c; // run-time ERROR
确保返回值为值传递
如果你确实想将返回值声明为值传递,仅仅声明T是不够的:
forwarding reference的情况,这个上面讨论过
template<typename T>
T retR(T&& p) {
return T{...}; // OOPS: returns by reference when called for lvalues
}
显示的指定模板参数类型:
template<typename T> // Note: T might become a reference
T retV(T p) {
return T{...}; // OOPS: returns a reference if T is a reference
}
int x;
retV<int&>(x); // retT() instantiated for T as int&
所以,有两种方法是安全的:
std::remove_reference<> :
template<typename T>
typename std::remove_reference<T>::type retV(T p) {
return T{...}; // always returns by value
}
auto :
template<typename T>
autoretV(T p) { // by-value return type deduced by compilerreturn T{...}; // always returns by value
}
按值传递
大多数人不喜欢将参数设置为按值传递的原因是怕参数拷贝的过程中带来的性能问题,但是不是所有按值传递都会有参数拷贝,比如:
我们逐一看一下上面的4个调用:
copy constructor
。copy constructor
(这个也是C++17的新特性:Mandatory Copy Elision or Passing Unmaterialized Objects)move constructor
。虽然上面4种情况只有第一种才会调用
copy constructor
,但是这种情况才是最常见的。Decay
之前的文章介绍过,当模板参数是值传递时,会造成参数decay:
这种方式有优点也有缺点:
char const*
还是类似const char[13]
。char const*
按引用传递
按引用传递不会拷贝参数,也不会有上面提到的decay。这看起来很美好,但是有时候也会有问题:
传递const reference
还是上面的例子,但是当模板参数声明改为
const T&
后,所有的调用都不会有拷贝。那么哪里会有问题呢?大家都知道,传递引用时,实际传递的是一个地址,那么编译器在编译时不知道调用者会针对这个地址做什么操作。理论上,调用者可以随意改变这个地址指向的值(这里虽然声明为const,但是仍然有
const_cast
可以去除const)。因此,编译器会假设所有该地址的缓存(通常为寄存器)在该函数调用后都会失效,如果要使用该地址的值,会重新从内存中载入。引用不会Decay
之前文章介绍过,按引用传递不会decay。因此如果传递的数组,那么推断参数类型时不会decay成指针,并且const和volatile都会被保留。
因此,在printR函数内通过T声明的变量没有const属性。
传递nonconst reference
如果想改变参数的值并且不希望拷贝,那么会使用这种情况。但是这时我们不能绑定prvalue和xvalue给一个nonconst reference(这是c++的一个规则)
同样,这种情况不会发生decay:
传递universal reference
这个也是声明参数为引用的一个重要场景:
但是这里需要额外注意一下,这是T隐式被声明为引用的唯一情况:
使用std::ref()和std::cref()
主要用来“喂”reference 给函数模板,后者原本以按值传递的方式接受参数,这往往允许函数模板得以操作reference而不需要另写特化版本:
这个特性被C++标准库运用于各个地方,例如:
make_pair()
用此特性于是能够创建一个 pair<> of references.make_tuple()
用此特性于是能够创建一个tuple<> of references.Binder
用此特性于是能够绑定(bind) reference.Thread
用此特性于是能够以by reference形式传递实参。注意std::ref()不是真的将参数变为引用,只是创建了一个std::reference_wrapper<>对象,该对象引用了原始的变量,然后将std::reference_wrapper<>传给了参数。std::reference_wrapper<>支持的一个重要操作是:向原始类型的隐式转换:
区分指针和数组
前面说过,按值传递的一个缺点是,无法区分调用参数是数组还是指针,因为数组会decay成指针。那如果有需要区分的需求,可以这么写:
std::enable_if
后面会介绍,它的意思是,假如不符合enable_if设置的条件,那么该模板会被禁用。其实现在基本上也不用原始数组和字符串了,都用std::string、std::vector、std::array。但是假如写模板的话,这些因素还是需要考虑进去。
处理返回值
一般在下面情况下,返回值会被声明为引用:
但是将返回值声明为引用需要格外小心:
确保返回值为值传递
如果你确实想将返回值声明为值传递,仅仅声明T是不够的:
所以,有两种方法是安全的:
之前文章讨论过auto推断类型的规则,会忽略引用。
模板参数声明的推荐
一般性建议
对应模板参数,一般建议如下:
不要将模板参数设计的太通用
比如你的模板函数只想接受vector,那么完全可以定义成:
这里就没有必要定义为
const T& v
.std::make_pair()模板参数历史演进
std::make_pair()
是一个很好演示模板参数机制的例子:make_pair<>()
的参数被设计为按引用传递来避免不必要的拷贝:但是当使用存储不同长度的字符串或者数组时,这样做会导致严重的问题。 这个问题记录在See C++ library issue 181 [LibIssue181]
标准库中perfect forward和std::decay是常见的搭配。
(完)
朋友们可以关注下我的公众号,获得最及时的更新:
The text was updated successfully, but these errors were encountered: