资讯 小学 初中 高中 语言 会计职称 学历提升 法考 计算机考试 医护考试 建工考试 教育百科
栏目分类:
子分类:
返回
空麓网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
空麓网 > 计算机考试 > 软件开发 > 后端开发 > Java

C++模板(第二版)笔记之第七章:按值传递还是按引用传递?

Java 更新时间: 发布时间: 计算机考试归档 最新发布

C++模板(第二版)笔记之第七章:按值传递还是按引用传递?

文章目录

  • 一、按值传递
    • 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:简单的按值传递参数的函数模板:

template
void 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 声明的对象那样)
template
void 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:
template
void 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。
:
template
void 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) 用作其参数
template
void 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 触发一个编译期错误
template
void 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:
template
void 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:引用对象在创建的时候必须被初始化
template
void 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中并没有定义输出运算符:
template
void 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 进行比较
template
bool 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》第二版

转载请注明:文章转载自 http://www.konglu.com/
本文地址:http://www.konglu.com/it/1094072.html
免责声明:

我们致力于保护作者版权,注重分享,被刊用文章【C++模板(第二版)笔记之第七章:按值传递还是按引用传递?】因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理,本文部分文字与图片资源来自于网络,转载此文是出于传递更多信息之目的,若有来源标注错误或侵犯了您的合法权益,请立即通知我们,情况属实,我们会第一时间予以删除,并同时向您表示歉意,谢谢!

我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2023 成都空麓科技有限公司

ICP备案号:蜀ICP备2023000828号-2