C++:深入理解C++11新特性:Chapter3:左值和右值

这篇具有很好参考价值的文章主要介绍了C++:深入理解C++11新特性:Chapter3:左值和右值。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


在C语言中,我们常常会提起左值(lvalue),右值(rvalue)这样的称呼,而在编译程序时,编译器有时也会报出错误信息中包含 左值,右值说法。不过左值、右值通常不是通过一个严谨的定义而为人所知。下面我通过这样一个例子,来引导大家认识: 左值,右值,左值引用,右值引用,常量左值引用
#include<iostream>

struct Copyable{
	Copyable() {
		std::cout<< "copied...." << std::endl;
	}
	Copyable(const Copyable &copy)
	{
		std::cout<< "copied" << std::endl;
	}
};

Copyable ReturnRvalue()
{
	// 这是返回的 右值  
	return Copyable();
}

// 1. 接收右值表达式
void AcceptValue(Copyable copy)
{
	
}

// 2. 右值引用减少对象开销,并延迟对象生命周期
//  直观意义:为临时变量续命,也就是为右值续命,因为右值在表达式结束后就消亡了,
// 如果想继续使用右值,那就会动用昂贵的拷贝构造函数。
void AcceptRef(Copyable && copy)
{
	
}

// 3. 常量左值引用减少对象开销,并延迟对象生命周期
void AcceptRef_2(const Copyable& copy){}

int main()
{
	Copyable copy;
	std::cout << "Pass by value:" << std::endl;
	AcceptValue(ReturnRvalue());
	
	std::cout << "Passs by reference: " << std::endl;
	AcceptRef(ReturnRvalue());
	
	std::cout << "Passs by reference_2: " << std::endl;
	AcceptRef(ReturnRvalue());
}
// 打印结果: g++ -std=c++11 main.cpp -fno-elide-constructors

// Copyable copy
construct.....       

Pass by value:

// ReturnRvalue() 函数:调用一次构造函数构造Copyable ,一次拷贝构造函数作为ReturnRvalue函数返回值,一次拷贝函数作为AcceptValue函数实参
construct.....     
copied construct
copied construct


Passs by reference:
// ReturnRvalue() 函数:调用一次构造函数构造Copyable ,一次拷贝构造函数作为ReturnRvalue函数返回值,由于是引用传递,那么直接将此返回值作为AcceptRef函数实参
construct.....
copied construct

// ReturnRvalue() 函数:调用一次构造函数构造Copyable ,一次拷贝构造函数作为ReturnRvalue函数返回值,由于是引用传递,那么直接将此返回值作为AcceptRef函数实参
Passs by reference_2:
construct.....
copied construct

	

上面的例子:我们用到了

  1. 函数形参为左值,然后将右值表达式作为实参绑定到左值
  2. 函数形参为右值引用,然后将右值作为实参绑定右值引用
  3. 函数形参为常量左值引用,然后将右值作为实参绑定到 常量左值引用

1. 将右值绑定到 左值

C++:深入理解C++11新特性:Chapter3:左值和右值
💚💚 这种绑定方式的特点:

  1. 函数 ReturnRvalue() 在运行结束后,返回值(右值临时变量)复制一份作为实参传递给 AcceptValue() 函数后就不会存活下去了。
    这样会导致:Copyable 这个对象构建两次。浪费内存。

2. 将右值绑定到 常量左值引用

C++:深入理解C++11新特性:Chapter3:左值和右值

💚💚

  1. AcceptRef使用了引用传递,在 以 ReturnRvalue 返回的右值为参数的时候,AcceptRef 就可以直接使用产生的临时值(并延长其生命周期)。
  2. 这个临时值的生命周期就和 AcceptRef() 生命周期一致

3. 将右值绑定到右值引用

C++:深入理解C++11新特性:Chapter3:左值和右值
💚💚

  1. AcceptRef使用了引用传递,在 以 ReturnRvalue 返回的右值为参数的时候,AcceptRef 就可以直接使用产生的临时值(并延长其生命周期)。
  2. 这个临时值的生命周期就和 AcceptRef() 生命周期一致。

总结:

🧡 通过上面三个小节,我们总结了 将右值绑定到 :左值,常量左值引用,右值引用的情况,下面以这个为例子,分析左值、右值含义,通常情况下,哪些是左值哪些是右值。
C++:深入理解C++11新特性:Chapter3:左值和右值

5. 左值,右值和右值引用

C++:深入理解C++11新特性:Chapter3:左值和右值

6. 引用类型可以引用的的值类型

C++:深入理解C++11新特性:Chapter3:左值和右值

C++:深入理解C++11新特性:Chapter3:左值和右值

7. 全能类型,常量左值引用用途

7.1 拷贝构造函数

对C++程序员来说,编写C++程序有一条必须注意的规则,就是在类中红包含了一个指针成员的话,那么就要特别注意 拷贝函数的编写,因为一不小心,就会出现内存泄漏。

#include<iostream>
using namespace std;

class HasPtrMem {
public:
	// 默认构造函数
	HasPtrMem():d(new int(0)) {}
	~HasPtrMem()
	{
		delete d;
	}
	int *d;
	
};

int main()
{
	HasPtrMem a;
	HasPtrMem b(a);
	
	cout<< *a.d << endl;
	cout<< *b.d << endl;
}
// 打印结果
free(): double free detected

C++:深入理解C++11新特性:Chapter3:左值和右值
💚💚 根据上图我们作如下分析

  1. 由于没有提供拷贝函数,C++会默认提供一个拷贝函数 (这个编译器源码编译后可以看出)
  2. 默认的拷贝函数类似于 memcpy按位拷贝,这样会造成一个问题:a.d 和 b.d 都指向一个内存地址
  3. 那么当main函数结束后,a和b 对象纷纷调用析构函数,当对象a 析构完毕之后,b.d 就变成了一个悬挂指针,不能指向有效的内存地址
    如果在不小心的情况下,对此指针做解引用,那么就势必会引起严重的错误。
    这个问题在C++中非常经典,这样的拷贝构造方式在C++上被称为 “浅拷贝” ,在位声明定义拷贝构造函数的情况下,C++会为每个类生成一个 浅拷贝构造函数。

💚💚 解决浅拷贝带来的问题 :自定义拷贝函数,实现深拷贝
我们为 HasPtrMem添加一个拷贝构造函数,拷贝构造函数从堆中分配内存,并将分配的新内存交还给 d,又用 *(h.d) 进行初始化,通过这样的方法很好的避免了 悬挂指针的困扰
C++:深入理解C++11新特性:Chapter3:左值和右值

7.2解决浅拷贝(深拷贝)

在章节7.1 中,拷贝构造函数为指针成员分配新的内存再进行内容拷贝的做法在C++编程中是不可违背的。
但是这个里面会有一个问题,这个问题就是:临时对象a 以及 对象的成员 int* d 指针指向的堆内容,但是又没有使用到,这就是一种浪费。
看下面这个例子

#include<iostream>
using namespace std;
class HasPtrMem {
public:
	HasPtrMem():d(new int(0))
	{
		cout<< "Construct: "<< ++ n_cstr<< endl;
	}
	HasPtrMem(const HasPtrMem& h):d(new int(*h.d))
	{
		cout<< "copy construct: " << ++n_cptr << endl;
	}
	~HasPtrMem()
	{
		cout<< "Destruct: " << ++ n_dstr<< endl;
    }
    int *d;
    static int n_cstr;
    static int n_cptr;
    static int n_dstr;
};
int HasPtrMem::n_cstr = 0;
int HasPtrMem::n_dstr= 0;
int HasPtrMem::n_cptr= 0;
HasPtrMem GetTemp() {return HasPtrMem();}

int main()
{
	HasPtrMem a = GetTemp();
}
// 编译选项  
 g++ -std=c++11 main_3.18.cpp -fno-elide-constructors
Construct: 1
copy construct: 1
Destruct: 1
copy construct: 2
Destruct: 2
Destruct: 3

💚💚 根据上面的打印可知:HasPtrMem 类构造函数调用了一次,拷贝构造函数调用了两次。析构函数调用了3次,为什么会出现这个情况了 。下面,我通过一张图来说明。
C++:深入理解C++11新特性:Chapter3:左值和右值

上图显示:GetTemp() 函数需要经历 两次拷贝函数,才可以让 对象 a 使用,由于拷贝函数进行了深拷贝,虽然解决了 指针悬挂问题,但是拷贝函数会 新建 堆内存(new int()) , 这是非常昂贵了。

  1. 可以想象一下如果类 HasPtrMem 成员 的指针指向非常大的堆内存,那么拷贝构造的过程就会非常昂贵。
  2. 更加令人堪忧的是,临时变量的产生和销毁对程序员是不可见的,并不会影响程序的运行,即使是性能有所下降,也不容易察觉。

8. 解决深拷贝问题

在第七节中,我们知道深拷贝带来临时对象复制,如果 一个类中存在指针变量,且指向非常大内存,那么在拷贝过程中必然会耗费内存,如果解决这个问题了,C++11 引入了右值和 move 语义,可以极大的提高性能,详细分析见我的下一篇文章。
move语义文章来源地址https://www.toymoban.com/news/detail-444541.html

到了这里,关于C++:深入理解C++11新特性:Chapter3:左值和右值的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Go语言】Golang保姆级入门教程 Go初学者chapter3

    下划线“_”本身在Go中一个特殊的标识符,成为空标识符。可以代表任何其他的标识符,但是他对应的值就会被忽略 仅仅被作为站维度使用, 不能作为标识符使用 因为Go语言中没有private public 所以标记变量首字母大写代表其他包可以使用 小写就是不可使用的 注意:Go语言中

    2024年02月13日
    浏览(37)
  • 深入理解PriorityQueue的特性

    Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的 使用操作: 1.在我们使用优先级队列时,必须导入相应的包 2.PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否

    2023年04月27日
    浏览(30)
  • 深入理解 Redis 新特性:Stream

    该数据结构需要 Redis 5.0.0 + 版本才可用使用 Redis stream 是 Redis 5 引入的一种新的数据结构,它是一个高性能、高可靠性的消息队列,主要用于异步消息处理和流式数据处理。在此之前,想要使用 Redis 实现消息队列,通常可以使用例如:列表,有序集合、发布与订阅 3 种数据结

    2023年04月17日
    浏览(29)
  • 【b站咸虾米】chapter3_vue基础_新课uniapp零基础入门到项目打包(微信小程序/H5/vue/安卓apk)全掌握

    课程地址:【新课uniapp零基础入门到项目打包(微信小程序/H5/vue/安卓apk)全掌握】 https://www.bilibili.com/video/BV1mT411K7nW/?p=12share_source=copy_webvd_source=b1cb921b73fe3808550eaf2224d1c155 目录 三、vue基础 3.1 vue介绍与传统js的差异化  3.2 模板语法 3.3 data属性 3.4 条件渲染  3.4.1 v-if和v-else 3.4

    2024年01月21日
    浏览(53)
  • 深入理解Java虚拟机:JVM高级特性与最佳实践

    Java虚拟机 Java虚拟机(Java Virtual Machine,JVM)是Java语言的核心,是执行Java二进制代码的虚拟计算机。 JVM本身是一个进程,负责解析Java程序并将其转换为特定平台可以执行的指令集。 通过JVM,Java程序可以实现“一次编写,到处运行”的特性,使Java具有很强的平台无关特性。

    2024年02月07日
    浏览(45)
  • 《深入理解C语言中的逻辑运算符及其短路特性》

    在C语言中,除了关系运算符之外,我们还可以使用逻辑运算符。逻辑运算符主要包括与运算()、或运算(||)和非运算(!)三种。这些运算符可以用来进行复杂的条件判断,简化程序的执行流程。在进行逻辑运算时,C语言规定非0即真,0即假。本篇博客主要围绕这个特性展

    2024年02月05日
    浏览(37)
  • Java 11 新特性与功能:深入了解长期支持版本的亮点

    Java 11,作为一个长期支持版本(LTS),在2018年9月发布。它引入了许多新特性和,为开发者提供了更多的工具和选项。在本文中,我们将探讨 11的主要新特性,以及何在实际项目中应用这些特性。以下是Java 11中值得关注的新特性和新功能: HTTP 客户端 API:Java 11引入了一组标

    2024年02月06日
    浏览(38)
  • 重温《深入理解Java虚拟机:JVM高级特性与最佳实践(第二版)》 –– 学习笔记(一)

    第1章:走近Java 1.1 Java的技术体系 SUN 官方所定义的 Java 技术体系包括:Java程序设计语言、Java虚拟机、Class文件格式、Java API类库、第三方(商业机构和开源社区)Java类库。 其中,「Java程序设计语言」、「Java虚拟机」、「Java API类」这三个被称为 JDK(Java Deployment Kit),即

    2024年01月23日
    浏览(38)
  • 【C++】C++11特性

    1.1 {}初始化 C++11引入了初始化列表(Initializer lists)的特性,通过花括号 {} 可以方便地初始化数组、容器和自定义类型的成员变量。这种语法可以一次性地指定多个初始值,而不需要显式地编写多个赋值语句。 下面是一些示例用法: 初始化数组: 初始化容器(如std::vector)

    2024年02月16日
    浏览(26)
  • 深入理解ArkTS:Harmony OS 应用开发语言 TypeScript 的基础语法和关键特性

    Harmony OS应用开发的主力语言ArkTS的前身TS语言的基本语法。通过学习变量的声明和数据类型、条件控制、函数声明、循环迭代等基本知识,并了解内核接口的声明和使用。同时还介绍了模块化开发的概念,提高代码的复用性和开发效率。该对话还涉及了if else和switch条件控制语

    2024年02月04日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包