织梦 营销型网站,国内新闻最新消息今天简短,专注WordPress网站建设开发,树莓派下载wordpress目录
1、引言
2、值类别及相关概念
3、左值、右值
4、左值引用、右值引用
5、移动语义
5.1、为什么需要移动语义
5.2、移动语义定义
5.3、转移构造函数
5.4、转移赋值函数
6、标准库函数 std::move
7、完美转发 std::forward VC常用功能开发汇总#xff08;专栏文章…目录
1、引言
2、值类别及相关概念
3、左值、右值
4、左值引用、右值引用
5、移动语义
5.1、为什么需要移动语义
5.2、移动语义定义
5.3、转移构造函数
5.4、转移赋值函数
6、标准库函数 std::move
7、完美转发 std::forward VC常用功能开发汇总专栏文章列表欢迎订阅持续更新...https://blog.csdn.net/chenlycly/article/details/124272585C软件异常排查从入门到精通系列教程专栏文章列表欢迎订阅持续更新...https://blog.csdn.net/chenlycly/article/details/125529931C软件分析工具从入门到精通案例集锦专栏文章正在更新中...https://blog.csdn.net/chenlycly/article/details/131405795C/C基础与进阶专栏文章持续更新中...https://blog.csdn.net/chenlycly/category_11931267.html C11新特性很重要作为C开发人员很有必要去学习不仅笔试面试时会涉及到开源代码中会大规模的使用。以很多视频会议及直播软件都在使用的开源WebRTC项目为例WebRTC代码中大篇幅地使用了C11及以上的新特性要读懂其源码必须要了解这些C的新特性。所以接下来一段时间我将结合工作实践给大家详细讲解一下C11的新特性以供借鉴或参考。
1、引言 C11引入了对象移动的概念是一种移动而非拷贝对象的能力移动对象可以有效地提高程序的性能。 为了支持移动操作C11引入了一种新的引用类型 - 右值引用rvalue references。所谓右值引用是必须要绑定到右值的引用通过操作符获得右值引用。今天就来详细讲讲左值、左值引用、右值、右值引用相关的内容。
2、值类别及相关概念 在C11中值类别主要分为左值、左值引用、右值与右值引用。左值引用是绑定到左值的应用右值引用则是绑定到右值的引用。当右值引用T出现在模板函数的参数中T是模板类型T则是万能引用。万能引用可以接收左值参数也可以接收右值参数。然后在推导函数参数类型时可能会发生引用折叠。还会涉及到标准库函数std::move和std::forward。
3、左值、右值 在C语言中我们常常会提起左值lvalue、右值rvalue这样的称呼。一个最为典型的判别方法就是在赋值表达式中出现在等号左边的就是“左值”而在等号右边的则称为“右值”。如
int b 1;
int c 2;
int a a b;
在这个赋值表达式中a就是一个左值而b c则是一个右值。 不过C中还有一个被广泛认同的说法那就是可以取地址的、有名字的就是左值反之不能取地址的、没有名字的就是右值。那么这个加法赋值表达式中a是允许的操作但(b c)这样的操作则不会通过编译。因此a是一个左值(b c)是一个右值。相对于左值右值表示字面常量、表达式、函数的非引用返回值等。 这里总结一下什么是左值 1能被赋值充分非必要条件左值不一定能被赋值比如const变量智能在初始化时赋初值后续不能赋值 2能取址充分非必要条件左值不一定能取址比如C语言中的register变量register int i 3C11已经取消了对register的支持编译时会忽略。再比如C语言中的位域变量是不能取址的 struct St{ int m:3;} St st; st.m 3; 指定int型成员m占3个字节。 3可以初始化左值引用必要不充分条件左值可以初始化左值引用比如int m 3; int n m;右值不能初始化左值引用比如const int m 3; 因为3是右值不能初始化左值引用所以编译会报错。 4字面量属于纯右值比如123等立即数就属于字面量注意常量字符串“xyz”是左值可以对该字符串进行取址即xyz。 5将亡值可以偷其中的资源。函数的返回值则是纯右值返回右值引用比如std::move函数。 4、左值引用、右值引用 左值引用是对一个左值进行引用的类型右值引用则是对一个右值进行引用的类型。左值引用和右值引用都是属于引用类型。无论是声明一个左值引用还是右值引用都必须立即进行初始化。而其原因可以理解为是引用类型本身自己并不拥有所绑定对象的内存只是该对象的一个别名。 左值引用是具名变量值的别名而右值引用则是不具名匿名变量的别名。 左值引用示例
int a 2; // 左值引用绑定到右值编译失败, err
int b 2; // 非常量左值
const int c b; // 常量左值引用绑定到非常量左值编译通过, ok
const int d 2; // 常量左值
const int e c; // 常量左值引用绑定到常量左值编译通过, ok
const int b 2; // 常量左值引用绑定到右值编程通过, ok
“const 类型 ”为 “万能”的引用类型它可以接受非常量左值、常量左值、右值对其进行初始化 右值引用使用表示
int r1 22;
int x 5;
int y 8;
int r2 x y;
T a ReturnRvalue(); 通常情况下右值引用是不能够绑定到任何的左值的
int c;
int d c; //err 下面看一个测试示例
void process_value(int i) //参数为左值引用
{cout LValue processed: i endl;
}void process_value(int i) //参数为右值引用
{cout RValue processed: i endl;
}int main()
{int a 0;process_value(a); //LValue processed: 0process_value(1); //RValue processed: 1return 0;
}
5、移动语义
5.1、为什么需要移动语义 右值引用是用来支持转移语义的。转移语义可以将资源 ( 堆系统对象等 ) 从一个对象转移到另一个对象这样能够减少不必要的临时对象的创建、拷贝以及销毁能够大幅度提高 C 应用程序的性能。临时对象的维护 ( 创建和销毁 ) 对性能有严重影响。 转移语义是和拷贝语义相对的可以类比文件的剪切与拷贝当我们将文件从一个目录拷贝到另一个目录时速度比剪切慢很多。通过转移语义临时对象中的资源能够转移其它的对象里。
5.2、移动语义定义 在现有的 C 机制中我们可以定义拷贝构造函数和赋值函数。要实现转移语义需要定义转移构造函数还可以定义转移赋值操作符。对于右值的拷贝和赋值会调用转移构造函数和转移赋值操作符。 如果转移构造函数和转移拷贝操作符没有定义那么就遵循现有的机制拷贝构造函数和赋值操作符会被调用。普通的函数和操作符也可以利用右值引用操作符实现转移语义。
5.3、转移构造函数 先来看个转移构造函数的实例
class MyString
{
public:MyString(const char *tmp abc){//普通构造函数len strlen(tmp); //长度str new char[len1]; //堆区申请空间strcpy(str, tmp); //拷贝内容cout 普通构造函数 str str endl;}MyString(const MyString tmp){//拷贝构造函数len tmp.len;str new char[len 1];strcpy(str, tmp.str);cout 拷贝构造函数 tmp.str tmp.str endl;}//移动构造函数//参数是非const的右值引用MyString(MyString t){str t.str; //拷贝地址没有重新申请内存len t.len;//原来指针置空t.str NULL;cout 移动构造函数 endl;}MyString operator (const MyString tmp){//赋值运算符重载函数if(tmp this){return *this;}//先释放原来的内存len 0;delete []str;//重新申请内容len tmp.len;str new char[len 1];strcpy(str, tmp.str);cout 赋值运算符重载函数 tmp.str tmp.str endl;return *this;}~MyString(){//析构函数cout 析构函数: ;if(str ! NULL){cout 已操作delete, str str;delete []str;str NULL;len 0;}cout endl;}private:char *str NULL;int len 0;
};MyString func() //返回普通对象不是引用
{MyString obj(mike);return obj;
}int main()
{MyString tmp func(); //右值引用接收return 0;
} 和拷贝构造函数类似有几点需要注意 1参数右值的符号必须是右值引用符号即“”。 2参数右值不可以是常量因为我们需要修改右值。 3参数右值的资源链接和标记必须修改否则右值的析构函数就会释放资源转移到新对象的资源也就无效了。 有了右值引用和转移语义我们在设计和实现类时对于需要动态申请大量资源的类应该设计转移构造函数和转移赋值函数以提高应用程序的效率。
5.4、转移赋值函数 直接看转移赋值函数的实例
class MyString
{
public:MyString(const char *tmp abc){//普通构造函数len strlen(tmp); //长度str new char[len1]; //堆区申请空间strcpy(str, tmp); //拷贝内容cout 普通构造函数 str str endl;}MyString(const MyString tmp){//拷贝构造函数len tmp.len;str new char[len 1];strcpy(str, tmp.str);cout 拷贝构造函数 tmp.str tmp.str endl;}//移动构造函数//参数是非const的右值引用MyString(MyString t){str t.str; //拷贝地址没有重新申请内存len t.len;//原来指针置空t.str NULL;cout 移动构造函数 endl;}MyString operator (const MyString tmp){//赋值运算符重载函数if(tmp this){return *this;}//先释放原来的内存len 0;delete []str;//重新申请内容len tmp.len;str new char[len 1];strcpy(str, tmp.str);cout 赋值运算符重载函数 tmp.str tmp.str endl;return *this;}//移动赋值函数//参数为非const的右值引用MyString operator(MyString tmp){if(tmp this){return *this;}//先释放原来的内存len 0;delete []str;//无需重新申请堆区空间len tmp.len;str tmp.str; //地址赋值tmp.str NULL;cout 移动赋值函数\n;return *this;}~MyString(){//析构函数cout 析构函数: ;if(str ! NULL){cout 已操作delete, str str;delete []str;str NULL;len 0;}cout endl;}private:char *str NULL;int len 0;
};MyString func() //返回普通对象不是引用
{MyString obj(mike);return obj;
}int main()
{MyString tmp(abc); //实例化一个对象tmp func();return 0;
}
6、标准库函数 std::move 编译器只对右值引用才能调用转移构造函数和转移赋值函数而所有命名对象都只能是左值引用如果已知一个命名对象不再被使用而想对它调用转移构造函数和转移赋值函数也就是把一个左值引用当做右值引用来使用怎么做呢标准库提供了函数 std::move这个函数以非常简单的方式将左值引用转换为右值引用。
int a;
int r1 a; // 编译失败
int r2 std::move(a); // 编译通过
7、完美转发 std::forward 完美转发适用于这样的场景需要将一组参数原封不动的传递给另一个函数。“原封不动”不仅仅是参数的值不变在 C 中除了参数值之外还有一下两组属性左值右值和 const/non-const。完美转发就是在参数传递过程中所有这些属性和参数值都不能改变同时而不产生额外的开销就好像转发者不存在一样。在泛型函数中这样的需求非常普遍。 下面举例说明
#include iostream
using namespace std;template typename T void process_value(T val)
{cout T endl;
}template typename T void process_value(const T val)
{cout const T endl;
}
//函数 forward_value 是一个泛型函数它将一个参数传递给另一个函数 process_value
template typename T void forward_value(const T val)
{process_value(val);
}template typename T void forward_value(T val)
{process_value(val);
}int main()
{int a 0;const int b 1;//函数 forward_value 为每一个参数必须重载两种类型T 和 const Tforward_value(a); // Tforward_value(b); // const T forward_value(2); // const Treturn 0;
} 对于一个参数就要重载两次也就是函数重载的次数和参数的个数是一个正比的关系。这个函数的定义次数对于程序员来说是非常低效的。 那C11是如何解决完美转发的问题的呢实际上C11是通过引入一条所谓“引用折叠”reference collapsing的新语言规则并结合新的模板推导规则来完成完美转发。
typedef const int T;
typedef T TR;
TR v 1; //在C11中一旦出现了这样的表达式就会发生引用折叠即将复杂的未知表达式折叠为已知的简单表达式 C11中的引用折叠规则 TR的类型定义 声明v的类型 v的实际类型 T TR T T TR T T TR T T TR T T TR T T TR T
注意一旦定义中出现了左值引用引用折叠总是优先将其折叠为左值引用。 C11中std::forward可以保存参数的左值或右值特性
#include iostream
using namespace std;template typename T void process_value(T val)
{cout T endl;
}template typename T void process_value(T val)
{cout T endl;
}template typename T void process_value(const T val)
{cout const T endl;
}template typename T void process_value(const T val)
{cout const T endl;
}//函数 forward_value 是一个泛型函数它将一个参数传递给另一个函数 process_value
template typename T void forward_value(T val) //参数为右值引用
{process_value( std::forwardT(val) );//C11中std::forward可以保存参数的左值或右值特性
}int main()
{int a 0;const int b 1;forward_value(a); // T forward_value(b); // const T forward_value(2); // T forward_value( std::move(b) ); // const T return 0;
}