文章目录
- 一、按值传递
- 1.按值传递会导致类型退化(decay)
- 二、按引用传递
- (1)按 const 引用传递
- (2)按引用传递不会做类型退化(decay)
- (3)按非 const 引用传递
- (4)按转发引用传递参数(Forwarding Reference)
- 三、使用 std::ref()和 std::cref() (限于模板)
一、按值传递
C++就提供了按值传递(call-by-value) 和按引用传递(call-by-reference) 两种参
数传递方式
其中C++中按引用传递的方式,主要有以下三种:
-
X const &(const 左值引用)
参数引用了被传递的对象, 并且参数不能被更改。 -
X &(非 const 左值引用)
参数引用了被传递的对象, 但是参数可以被更改。 -
X &&(右值引用)
参数通过移动语义引用了被传递的对象, 并且参数值可以被更改或者被“窃取” 。 -
eg:简单的按值传递参数的函数模板:
templatevoid printV (T arg) { ... }
如果定义一个 std::string 对象并将其用于上面的函数模板:
std::string s = "hi"; printV(s);
模板参数 T 被实例化为 std::string, 实例化后的代码是:
在传递字符串时, arg 变成 s 的一份拷贝。 此时这一拷贝是通过 std::string 的拷贝构造函数创建的, 这可能会是一个成本很高的操作, 因为这个拷贝操作会对源对象做一次深拷贝, 它需要开辟足够的内存来存储字符串的值。
void printV (std::string arg) { … }
- eg:并不是所有的情况都会调用拷贝构造函数
//在第一次调用中, 被传递的参数是左值(lvalue) , 因此拷贝构造函数会被调用。 std::string returnString(); std::string s = "hi"; printV(s); //copy constructor printV(std::string("hi")); //copying usually optimized away (if not, move constructor) printV(returnString()); // copying usually optimized away (if not, move constructor) printV(std::move(s)); // move constructor
1.按值传递会导致类型退化(decay)
当按值传递参数时, 参数类型会退化(decay)。
- 也就是说, 裸数组会退化成指针, const 和 volatile 等限制符会被删除(就像用一个值去初始化一个用 auto 声明的对象那样)
templatevoid printV (T arg) { … } std::string const c = "hi"; printV(c); // c decays so that arg has type std::string //当传递字符串常量“hi” 的时候, 其类型 char const[3]退化成 char const * printV("hi"); //decays to pointer so that arg has type char const* int arr[4]; printV(arr); // decays to pointer so that arg has type int *
二、按引用传递
按引用传递时参数类型也不会退化(decay),但是并不是在所有情况下都能使用按引用传递。
(1)按 const 引用传递
- eg:
templatevoid printR (T const& arg) { … } std::string returnString(); std::string s = "hi"; printR(s); // no copy printR(std::string("hi")); // no copy printR(returnString()); // no copy printR(std::move(s)); // no copy
- eg:即使是按引用传递一个 int 类型的变量,也依然不会拷贝。 因此如下调用:
int i = 42; printR(i); // passes reference instead of just copying i //会将 printR()实例化为: void printR(int const& arg) { … }
(2)按引用传递不会做类型退化(decay)
也就是说不会把裸数组转换为指针, 也不会移除 const 和 volatile 等限制符。
- 而且由于调用参数被声明为 T const &, 被推断出来的模板参数 T 的类型将不包含 const。
- 在 printR()中用 T 声明的变量, 它们的类型中也不会包含 const。
: templatevoid printR (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]
(3)按非 const 引用传递
如果想通过调用参数来返回变量值(比如修改被传递变量的值) , 就需要使用非 const 引用(要么就使用指针)
- 被调用的函数模板可以直接访问被传递的参数。
- eg:注意对于 outR(), 通常不允许将临时变量(prvalue) 或者通过 std::move()处理过的已存在的变量(xvalue) 用作其参数
templatevoid outR (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 //非 const 类型的裸数组, 其类型也不会 decay int arr[4]; outR(arr); // OK: T deduced as int[4], arg is int(&)[4]
- eg:此时如果传递的参数是 const 的, arg 的类型就有可能被推断为 const 引用, 也就是说这时可以传递一个右值(rvalue) 作为参数
templates void outR (T& arg) { if (std::is_array ::value) { std::cout << "got array of " << std::extent ::value << "elemsn"; } … } std::string const c = "hi"; outR(c); // OK: T deduced as std::string const outR(returnConstString()); // OK: same if returnConstString() returns const string outR(std::move(c)); // OK: T deduced as std::string const6 outR("hi"); // OK: T deduced as char const[3]
- eg:禁止传递const对象
//使用 static_assert 触发一个编译期错误 templatevoid outR (T& arg) { static_assert(!std::is_const ::value, "out parameter of foo (T&) is const"); … } //通过使用 std::enable_if<> 禁用该情况下的模板: template ::value> void outR (T& arg) { … } //在 concepts 被支持之后, 通过 concepts 来禁用该模板 template requires !std::is_const_v void outR (T& arg) { … }
(4)按转发引用传递参数(Forwarding Reference)
使用引用调用(call-by-reference) 的一个原因是可以对参数进行完美转发(perfect forward)。
- 在使用转发引用时(forwarding reference, 被定义成一个模板参数的右值引用(rvalue reference)
- eg:
templatevoid passR (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&& int arr[4]; passR(arr); // OK: T deduced as int(&)[4] (also the type of arg) std::string const c = "hi"; passR(c); //OK: T deduced as std::string const& passR("hi"); //OK: T deduced as char const(&)[3] (also the type of arg) int arr[4]; passR(arr); //OK: T deduced as int (&)[4] (also the type of arg)
- eg:引用对象在创建的时候必须被初始化
templatevoid passR(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 int int i; foo(i); // ERROR: T deduced as int&, which makes the declaration of x in passR() invalid
三、使用 std::ref()和 std::cref() (限于模板)
#include// for std::cref() #include #include void printString(std::string const& s) { std::cout << s << ’ n’ ; } template void printT (T arg) { printString(arg); // might convert arg back to std::string } int main() { std::string s = "hello"; printT(s); // print s passed by value printT(std::cref(s)); // print s passed “as if by reference” }
- eg:比如如果尝试直接输出传递进来的类型为 T 的对象, 就会遇到错误, 因为
std::reference_wrapper中并没有定义输出运算符:
templatevoid printV (T arg) { std::cout << arg << ’ n’ ; } … std::string s = "hello"; printV(s); //OK printV(std::cref(s)); // ERROR: no operator << for reference wrapper defined
- eg:不能将一个 std::reference_wrapper
对象和一个char const*或者 std::string 进行比较
templatebool isless(T1 arg1, T2 arg2) { return arg1 < arg2; } … std::string s = "hello"; if (isless(std::cref(s), "world")) … //ERROR if (isless(std::cref(s), std::string("world"))) … //ERROR
std::reference_wrapper<>总结:
-
std::reference_wrapper<>是为了让开发者能够像使用“第一类对象(first class object) ”一样使用引用, 可以对它进行拷贝并将其按值传递给函数模板。
-
但是通常总是要将其转换会原始类型
-
ref:《C++Template》第二版