【仿写C++中的move函数和forward函数】

news2024/11/19 19:42:34

仿写实现move函数

一、值的类型

在这里插入图片描述

1.左值

描述:能够取地址的值成为左值

int a = 10;
const int b = 15;
int *pa = &a;
const int *pb = &b;

2.纯右值

描述:赤裸裸的字面值 eg(false , 3 , 12.23等)

int a = 13;
int *p = &a;  //取a的地址
int *ptr = &13; //错误,13是一个字面量,无法取地址 

如字面常量 103, 12.23, nullptr;不可以取地址, 称为为纯右值。

3.将亡值

描述:表达式的运行或计算过程中所产生的临时量或临时对象,称之为将亡值;临时量有可能是字面值,也有可能是一个不具名的对象。

算术表达式(a+b …),逻辑表达式(a || b …),比较表达式(a != b …)取地址表达式(&b)等,产生的计算结果相当于字面量,实际存储在 cpu的寄存器中 ,因为计算结果是字面量,所以为纯右值。

如图 在这里插入图片描述

因为c++是面向对象的,所以 i++ 和 ++i 是不一样的, ++i 相当于 i = i + 1; 所以 不会产生临时变量, 而 i++不同,它相当于 将 i 拷贝一份当做副本,然后将原来的 i 进行加 1 操作,最后将 副本 的值返回。 副本是不具名的,所以是 将亡值,而 返回的值是 字面量 所以 i++ 为纯右值

不具名对象如图

在这里插入图片描述

编译后会报错 test.cpp(17): error C2102: “&”要求左值,。

因为 Int(13) 程序运行过程中所产生了不具名对象,将亡值。 不可以取地址, 所以&Int(13)错误 。

int fun()
{
    int value = 10;
    return value;
}
int main()
{
    int i = 0;
    int a = 1;
    i = a + b;
    i++;
    &b;
    a = fun(); //返回时在主栈帧中构造临时量,是将亡值,纯右值。
    return 0;
}

流程图 图 2.2

在这里插入图片描述

当fun函数return 时 ,其栈帧空间会被回收,此时先在主栈帧中创建一个将亡值对象(xvalue) 将返回的值赋给将亡值,回到主函数栈帧中会再将将亡值对象的值赋给 a.

二、引用

基本原则:

1.声明引用的时候必须初始化,且一旦绑定,不可把引用绑定到其他对象;即引用必须初始化,不能对引用重定义

2.对引用的一切操作,就相当于对原对象的操作

1.引用与函数重载

void fun(int& val)
{
	cout << "LeftRef" << endl;
}
void fun(const int & val)
{
    cout << "Const LeftRef" << endl;
}
void fun(int&& val)
{
	cout << "RightRef" << endl;
}
int main()
{
	int a = 10; 
	const int d = 15;
	int&& f = 12; //右值引用f是具名右值引用,编译器会将已命名的右值引用视为左值
	//int&& g = f;  // error,f为具名右值
	fun(a);  
	fun(d);
	fun(f);
    fun(13);
    return 0;
}

运行结果:

在这里插入图片描述

结论:

​ 1.int& a = 10; 右值引用 a 是具名右值引用。

​ 2.编译器会将已命名的右值引用视为左值,所以不能 int && b = a;

2.函数返回值

int func()
{
    int x = 10;
    cout<<"&x: "<<endl;
    return x;
}

int main()
{
	int a = func();
	//int& b = func(); // error; func返回值为右值.
	const int& c = func(); 
	cout << "&c: " << &c << endl;
	int&& d = func();
	cout << "&d: " << &d << endl;

	return 0;
}

运行结果:

在这里插入图片描述

结论:1.函数返回值为将亡值(右值)

​ 2.常性左值引用为万能引用,可以接受右值

3.自定义类

class Int  
{
	int val;
public:
	Int(int x = 0) :val(x)
	{
		cout << "Create Int: " << this << endl;
	}
	Int(const Int& it) :val(it.val)
	{
		cout << this<<" Copy Create Int: <= " << &it << endl;
	}
	Int(Int&& it) :val(it.val)
	{
		it.val = 0;
		cout << this<<" Move Create Int: <= " << &it << endl;
	}
	Int& operator=(const Int& it)
	{
		if (this == &it) return *this;
		val = it.val;
		cout << this << " = " << &it << endl;
		return *this;
	}
	Int& operator=(Int&& it)
	{
		if (this == &it) return *this;
		val = it.val;
		it.val = 0;
		cout << this << " <= " << &it << endl;
		return *this;
	}
	~Int() { cout << "Destroy Int: " << this << endl; } 
};

Int func(int x)
{
	Int tmp(x);
	return tmp;
}

int main()
{
	Int a = func(1);
	cout << "--------------------" << endl;
	Int x(0);
	cout << "--------------------" << endl;
	x = func(2);
	cout << "--------------------" << endl;
	//Int& b = func(3);
	const Int& c = func(4);
	cout << "--------------------" << endl;
	Int&& d = func(5);
	cout << "--------------------" << endl; 
	Int f(a);
	cout << "--------------------" << endl;
	return 0;
}

运行结果

在这里插入图片描述

结论:

1.通过右值引用,比之前少了一次移动构造和一次析构,原因在于右值引用绑定了右值,让临时右值的生
命周期延长了 <主栈帧里创建的不具名对象(将亡值)>

2.函数返回值构建过程和之前分析的一样( 图 2.2

三、std::move的实现

原理:

本质上是将左值强制转换为右值引用,调用对象的移动构造和移动赋值函数,实现对资源的转移。

优点:

当一个对象内部有较大的堆内存或者动态数组时,进行深拷贝会占用cpu资源,而浅拷贝释放资源时会造成对堆区进行重复释放导致非法访问。使用move()语义可以提高性能

使用范围:

move 对于拥有形如对内存、堆区等资源的成员的对象有效

1.未定义的引用类别 && (函数模板中)

template <class _Ty>
void fun(_Ty&&  x) //未定义的引用类型,它必须被初始化,它是左值还是右值引用,取决于它的初始化
{
    int z = 10;
    _Ty y = z;
}
int main()
{
    int a = 1;
    const int b = 2;
    int& c = a;
    const int& d = b;
    fun(10);  
    fun(a);
    fun(b);
    fun(c);
    fun(d);
    return 0;
}

fun(a),fun©: x 的类型为 int& ,y的类型为 int&;

fun(b),fun(d):x的类型为 const int& ,y的类型为const int&;

fun(10):x的类型为 int&& , y 的类型为 int,不具有引用;

结论:

  1. _Ty && 与左值,普通左值引用结和,_Ty为左值引用

  2. _Ty&& 与左值常引用结合,_Ty为左值常性引用

  3. _Ty&& 与右值结合时,_Ty只保留类型,不具有引用属性

  4. _Ty&& 不会破坏掉 对象的const属性

实现流程:

1.我们需用去除对象的引用属性
template <class _Ty>
struct my_remove_reference
{
    using type = _Ty;

};
template <class _Ty>
struct my_remove_reference<_Ty &>
{
    using type = _Ty;

};
template <class _Ty>
struct my_remove_reference<_Ty &&>
{
    using type = _Ty;
    
};
2.加入适配器,简化代码
template <class _Ty>
using my_remove_reference_t = typename my_remove_reference<_Ty>::type;
3.转换为右值
template <class _Ty>
my_remove_reference_t<_Ty> && my_move(_Ty &&x)
{
    return static_cast<my_remove_reference_t<_Ty>&&>(x);
}

典型错误:使用c语言中的强制类型转换

template <class _Ty>
my_remove_reference_t<_Ty> && my_move(_Ty &&x)
{ 
   return (my_remove_reference_t<_Ty>&&)x; //error,不能使用c语言中的强制类型转换
}

因为常性对象的资源是不能进行移动修改的,而强制类型转换会破坏这一平衡点,而c++的静态类型转换刚好不会去掉const属性。

四、实现forward函数

原理:

利用引用叠加,按照参数原来的值类型转发到另一个函数

先来看std::forward()函数示例

void print(int& val)
{
    cout<<"LReference"<<endl;
}
void print(const int& val)
{
    cout<<"const LReference"<<endl;
}
void print(int&& val)
{
    cout<<"RReference"<<endl;
}
 
 template<class _Ty>
 void fun(_Ty&& val)
 {
    print(std::forward<_Ty>(val));
 }

int main()
{
  int a = 10;
  const int b = 20;
  int& c = a;
  fun(a);
  fun(b);
  fun(c);
  fun(10);
  system("pause"); 
}

运行结果

在这里插入图片描述

引用叠加:

由于存在 (_Ty&&) 这种未定的引用类型,当它作为参数时,可能被一个左值引用或者右值引用的参数初始化,这时经过类型推导的 _Ty 和右值引用(&&)叠加会发生类型的变化,这种变化被称为引用折叠。

即 static_cast<_Ty &&> (val);

c++中规定:

  1. 所有的右值引用叠加到右值引用上仍然还是一个右值引用。

  2. **所有的其他引用类型之间的叠加都将变成左值引用。

设计forward的参数

  1. 需要接受所有类型的数据 -> 联想到万能引用

  2. 需要保留原来数据的类型 -> 联想到去除引用属性的模板

去除引用属性的模板代码在上文move()函数实现中

1.左值版

template <class _Ty>
_Ty&& my_forward(my_remove_reference_t<_Ty>& Arg)//_Ty保留数据原来的属性
{
    return static_cast<_Ty &&> (Arg);  //此处涉及引用折叠
}

2.右值版 (forward还应支持右值参数)

template <class _Ty>
_Ty&& my_forward(my_remove_reference_t<_Ty>&& Arg)
{
    return static_cast<_Ty &&> (Arg);  //此处涉及引用折叠
}

这样就可以实现和系统的forward相同的功能

测试代码

template <class _Ty>
struct my_remove_reference
{
    using type = _Ty;

};
template <class _Ty>
struct my_remove_reference<_Ty &>
{
    using type = _Ty;

};
template <class _Ty>
struct my_remove_reference<_Ty &&>
{
    using type = _Ty;

};
template <class _Ty>
using my_remove_reference_t = typename my_remove_reference<_Ty>::type;

template <class _Ty>
_Ty &&my_forward(my_remove_reference_t<_Ty> &_Arg)
{
    return static_cast<_Ty &&>(_Arg);
}
template <class _Ty>
_Ty &&my_forward(my_remove_reference_t<_Ty> &&_Arg)
{
    return static_cast<_Ty &&>(_Arg);
}
void print(int& val)
{
    cout<<"LReference"<<endl;
}
void print(const int& val)
{
    cout<<"const LReference"<<endl;
}
void print(int&& val)
{
    cout<<"RReference"<<endl;
}
 template<class _Ty>
 void fun(_Ty&& val)
 {
    print(my_forward<_Ty>(val));
 }

int main()
{
  int a = 10;
  const int b = 20;
  int& c = a;
  fun(a);
  fun(b);
  fun(c);
  fun(10);
  print(my_forward(10));//右值版
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1252361.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【电路笔记】-分流器

分流器 文章目录 分流器1、概述2、通用/网络配置3、无功分流器3.1 电阻电容分流器3.2 电阻-电感分流器 4、总结 我们在之前关于分压器的文中已经看到&#xff0c;分压过程是通过在串联配置中关联相同的组件来实现的。 在本文中&#xff0c;我们将重点关注电流分频器执行的电流分…

【Qt之QFileInfo】使用

描述 QFileInfo类提供了与系统无关的文件信息。 QFileInfo提供有关文件的名称和位置&#xff08;路径&#xff09;在文件系统中的信息&#xff0c;以及它的访问权限、是否为目录或符号链接等。还可以获取文件的大小和最后修改/读取时间。QFileInfo还可以用于获取关于Qt资源的信…

强化学习,快速入门与基于python实现一个简单例子(可直接运行)

文章目录 一、什么是“强化学习”二、强化学习包括的组成部分二、Q-Learning算法三、迷宫-强化学习-Q-Learning算法的实现全部代码&#xff08;复制可用&#xff09;可用状态空间检查是否超出边界epsilon 的含义更新方程 总结 一、什么是“强化学习” 本文要记录的大概内容&am…

python自动化测试——自动化基本技术原理

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

【教学类-06-11】20231125(55格版)X-Y之间“除法÷题”(以1-9乘法口诀表倒推)(随机抽取和正序抽取)

图片展示 &#xff08;随机打乱排序&#xff09; 正序&#xff08;每张都一样&#xff09; 背景需求&#xff1a; 前面三篇写到了随机加法、随机减法、随机乘法&#xff0c;既然做了三套&#xff0c;怎么能不试试最后一款“除法”呢 模仿乘法版本&#xff0c;制作打乱版和正…

【手写实现一个简单版的Dubbo,深刻理解RPC框架的底层实现原理】

手写实现一个简单版的Dubbo&#xff0c;深刻理解RPC框架的底层实现原理 RPC框架简介了解Dubbo的实现原理服务暴露服务引入服务调用 手写实现一个简单版的Dubbo服务暴露ServiceBeanProxyFactory#getInvokerProtocol#exportRegistryProtocol#export 服务引入RegistryProto#referD…

3、点亮一个LED

新建工程 project—>New uVision Project LED介绍 中文名&#xff1a;发光二极管 外文名&#xff1a;Light Emitting Diode 简称&#xff1a;LED 用途&#xff1a;照明、广告灯、指引灯 电路图分析 进制的转换 生成下载文件&#xff1a; 代码 //导包 #inclu…

Keil5个性化设置及常用快捷键

Keil5个性化设置及常用快捷键 1.概述 这篇文章是Keil工具介绍的第三篇文章&#xff0c;主要介绍下Keil5优化配置&#xff0c;以及工作中常用的快捷键提高开发效率。 第一篇&#xff1a;《安装嵌入式单片机开发环境Keil5MDK以及整合C51开发环境》https://blog.csdn.net/m0_380…

leetcode刷题详解——买卖股票的最佳时机含手续费

1. 题目链接&#xff1a;714. 买卖股票的最佳时机含手续费 2. 题目描述&#xff1a; 给定一个整数数组 prices&#xff0c;其中 prices[i]表示第 i 天的股票价格 &#xff1b;整数 fee 代表了交易股票的手续费用。 你可以无限次地完成交易&#xff0c;但是你每笔交易都需要付手…

PostgreSQL 分区表插入数据及报错:子表明明存在却报不存在以及column “xxx“ does not exist 解决方法

PostgreSQL 分区表插入数据及报错&#xff1a;子表明明存在却报不存在以及column “xxx“ does not exist 解决方法 问题1. 分区表需要先创建子表在插入&#xff0c;创建子表立马插入后可能会报错子表不存在&#xff1b;解决&#xff1a; 创建子表及索引后&#xff0c;sleep10毫…

动态规划经典例题leetcode思路代码详解

目录 动态规划基础篇例题 leetcode70题.爬楼梯 leetcode746题.使用最小花费爬楼梯 leetcode198题.打家劫舍 leetcode62题.不同路径 leetcode64题.最小路径和 leetcode63题.63不同路径II 动态规划基础篇例题 这一篇的例题解答是严格按照我上一篇写的动态规划三部曲做的&…

中职组网络安全-linux渗透测试-Server2203(环境+解析)

任务环境说明&#xff1a; 服务器场景&#xff1a;Server2203&#xff08;关闭链接&#xff09; 用户名&#xff1a;hacker 密码&#xff1a;123456 1.使用渗透机对服务器信息收集&#xff0c;并将服务器中SSH服务端口号作为flag提交&#xff1b; FLAG:2232 2. 使用渗透机对…

chrome 调试之 - 给微软小冰看病(无论给小冰发送什么内容都只回复“我已经开始升级啦,期待一下吧!”)

微软 Bing 搜索推出了小冰AI智能聊天模块&#xff0c;具体启用方式是用edge或chrome浏览器打开链接 cn.bing.com 后在输入框搜索任意内容&#xff0c;待搜索结果页面加载完并稍等片刻&#xff0c;页面右侧就会出现一个躲在滚动条后面的小萝莉&#xff0c;抚摸...不&#xff0c;…

音频——S/PDIF

文章目录 BMC 编码字帧(sub-frame)格式帧(frame)格式参考S/PDIF 是 SONY 和 Philips 公司共同规定的数字信号传输规范,其实就是在 AES/EBU 上进行改动的家用版本。IEC60958 的标准规范囊括了以上两个规范。spdif 采用了双相符号编码(BMC),是将时钟信号和数据信号混合在一起…

python:傅里叶分析,傅里叶变换 FFT

使用python进行傅里叶分析&#xff0c;傅里叶变换 FFT 的一些关键概念的引入&#xff1a; 1.1.离散傅里叶变换&#xff08;DFT&#xff09; 离散傅里叶变换(discrete Fourier transform) 傅里叶分析方法是信号分析的最基本方法&#xff0c;傅里叶变换是傅里叶分析的核心&…

数据库设计规范(收藏)

本文的目的是提出针对Oracle数据库的设计规范&#xff0c;使利用Oracle数据库进行设计开发的系统严格遵守本规范的相关约定&#xff0c;建立统一规范、稳定、优化的数据模型。 参照以下原则进行数据库设计&#xff1a; 方便业务功能实现、业务功能扩展&#xff1b;方便设计开发…

爪语言 之 如何处理Java异常?

以小我融入大我,青春献给祖国 目录 1.异常的概念与体系 1.1异常的概念 1.2 异常的体系 1.3 异常的分类 2. 异常的处理 2.1 防御式编程 2.2异常的抛出 2.3 异常的捕获 2.3.1 异常声明throws 2.3.2 try-catch捕获并处理 2.3.3 finally 2.4 异常的处理流程总结 3. 自定…

Jmeter性能综合实战——签到及批量签到

提取性能测试的三个方面&#xff1a;核心、高频、基础功能 签 到 请 求 步 骤 1、准备工作&#xff1a; 签到线程组 n HTTP请求默认值 n HTTP cookie 管理器 n 首页访问请求 n 登录请求 n 查看结果树 n 调试取样器 l HTTP代理服务器 &#xff08;1&#xff09;创建线…

h5小游戏--2048

2048 经典2048小游戏&#xff0c;基于JS、Html5改写版 效果预览 点我下载源代码 下载代码解压后&#xff0c;双击index.html即可开始本游戏。 Game Rule 游戏规则 以下为游戏默认规则&#xff0c;若需要修改规则请修改代码。 移动箭头键来移动方块&#xff0c;当两个相同数…

一定要会用selenium的等待,三种等待方式解读

​很多人问&#xff0c;这个下拉框定位不到、那个弹出框定位不到…各种定位不到&#xff0c;其实大多数情况下就是两种问题&#xff1a; 有frame 没有加等待 殊不知&#xff0c;你的代码运行速度是什么量级的&#xff0c;而浏览器加载渲染速度又是什么量级的&#xff0c;就好…