c++11--左值,右值,移动语义,引用折叠,模板类型推断,完美转发

1.移动语义
移动构造和移动赋值均属于移动语义范畴。
移动语义的实现依赖于右值概念,右值引用。
1.1.一个移动构造的实例

#include <iostream>
using namespace std;
class HasPtrMem{
public:
    HasPtrMem():d(new int(3)){
        cout << "Construct: " << ++n_cstr << endl;
    }
    HasPtrMem(const HasPtrMem& h):d(new int(*h.d)){
        cout << "Copy construct: " << ++n_cptr << endl;
    }
    HasPtrMem(HasPtrMem&& h):d(h.d){
        h.d = nullptr;
        cout << "Move construct: " << ++n_mvtr << endl;
    }
    ~HasPtrMem(){
        delete d;
        cout << "Destruct: " << ++n_dstr << endl;
    }
    int *d;
    static int n_cstr;
    static int n_dstr;
    static int n_cptr;
    static int n_mvtr;
};

int HasPtrMem::n_cstr = 0;
int HasPtrMem::n_dstr = 0;
int HasPtrMem::n_cptr = 0;
int HasPtrMem::n_mvtr = 0;
HasPtrMem GetTemp(){
    HasPtrMem h;// 1.局部对象构造
    cout << "Resource from " << __func__ << ": " << hex << h.d << endl;
    return h;
}// 3.局部对象析构

int main()
{
    // 2.匿名对象拷贝构造--实参为即将析构的局部变量时匹配到右值版本
    // 4.对象a拷贝构造,实参为匿名临时对象时匹配到右值版本
    HasPtrMem a = GetTemp();
    // 3.匿名对象析构
    cout << "Resource from " << __func__ << ": " << hex << a.d << endl;
    // 4.对象a析构
}

对其执行:g++ -std=c++11 44.cpp -fno-elide-constructors -o 44,再执行./.44
在这里插入图片描述
上述以函数内临时变量为实参拷贝构造匿名临时变量,以匿名临时变量为实参拷贝构造变量a时均触发了移动拷贝构造。
移动拷贝构造的形参是一个右值引用类型。

我们需要先理解:
(1). 左值,右值。
(2). 左值引用,常量左值引用,右值引用作为形参时可接收的实参。及各类实参和形参匹配的优先级。

才能较好的把握移动拷贝,移动赋值的使用。
从理解上来说,针对需要管理资源的类型,我们将拷贝和赋值区分为浅拷贝,浅赋值和深拷贝,深赋值。
浅指的是我们通过窃取目标对象资源来完成资源转移。深指的是我们自己产生资源,然后使其和目标对象内容一致。
这种场景下,类型通过提供移动拷贝,移动赋值来实现浅拷贝,浅赋值。通过提供拷贝构造,赋值来实现深拷贝,深赋值。

1.2.左值,右值
c++程序中,所有的值必属于左值,将亡值,纯右值三者之一。
将亡值,纯右值均属于右值范畴。

(1). 纯右值
和C中右值概念一致。
a. 非引用返回的函数返回的匿名临时变量值。
b. 一些运算表达式,诸如 1 + 3 产生的匿名临时变量值。
c. 不跟对象关联的字面量值,比如:2、‘c’、true。
d. 类型转换函数返回的匿名临时变量值。
e. lambda表达式返回的匿名临时变量值。

(2). 将亡值
C++特有。
a. 返回右值引用T&&的函数返回值–匿名右值引用类型。
b. std::move的返回值–匿名右值引用类型。
c. 转换为T&&的类型转换函数的返回值–匿名右值引用类型。

(3). 左值
排除(1),(2)后的可标识函数,对象值都属于左值。

1.3.非常量左值引用,常量左值引用,非常量右值引用,常量右值引用
常量和非常量,左值和右值是可以同时用于修饰对象的两类性质。

右值存在的价值一个是用于实现移动语义,一个是用于实现完美转发。
一般,常量右值引用没有存在的意义,因为:
(1). 右值用于移动语义时,我们窃取其资源后,需要对其修改以便阻止资源多次释放,野指针等问题。
(2). 如果需要引用右值且让右值不可通过引用更改,直接使用常量左值来引用即可。

使用右值作为实参来初始化右值引用,常量左值引用后,右值的生命期会被延长。
通过右值引用可以对引用对象进行修改,通过常量左值引用无法对引用对象进行修改。

#include <iostream>

class A
{
public:
    A()
    {
        printf("A()_%x\n", this);
    }
    A(const A& a)
    {
        printf("A(const A&_%x)\n", this);
    }
    ~A()
    {
        printf("~A()_%x\n", this);
    }
public:
    int i = 0;
};

A fun()
{
    A a;
    return a;
}

int main()
{
    // 这里的变量除了a1,还有一个隐藏起来的匿名临时变量。
    // 汇编执行时:
    // 是匿名临时变量先接受了函数的返回值
    // 再通过匿名临时变量完成了a1的初始化

    // 这里函数内部临时变量a,变量a1均是左值
    // 匿名临时变量是右值
    A a1 = fun();
    printf("tag1\n");

    A&& a2 = fun();
    printf("tag2\n");
    a2.i = 1;

    const A& a3 = fun();
    printf("tag3\n");
    return 0;
}

在这里插入图片描述
上述实例反映了两点:
(1). 通过采用右值引用或常量左值引用接受函数fun的非引用类型返回值。使得返回值对应的匿名临时右值对象的生命期延长了。
(2). 通过右值引用可以修改所引用变量,通过常量左值引用则不可。

以下表格整理了引用类型及每种类型可以接受的初始化实参类型。

引用类型可以引用的值类型注记
非常量左值引用非常量左值
常量左值引用非常量左值,常量左值,非常量右值,常量右值全能类型,用于深拷贝
非常量右值引用非常量右值移动语义,完美转发
常量右值引用非常量右值,常量右值

1.4.std::move

#include <iostream>

class A 
{
public:
    A():m_c('a'){
        printf("A()_%x\n", this);
    }

    A(A& a):m_c(a.m_c){
        printf("A(&)_%x\n", this);
        fun(a);
    }

    A(const A& a):m_c(a.m_c){
        printf("A(const&)_%x\n", this);
        fun(a);
    }

    A(A&& a):m_c(a.m_c){
        printf("A(&&)_%x\n", this);
        fun(a);
    }

    A(const A&& a):m_c(a.m_c)
    {
        printf("const A(&&)_%x\n", this);
        fun(a);
    }

    A& operator=(A& a)
    {
        printf("=(A&)_%x\n", this);
        m_c = a.m_c;
        fun(a);
        return *this;
    }

    A& operator=(const A& a)
    {
        printf("=(const A&)_%x\n", this);
        m_c = a.m_c;
        fun(a);
        return *this;
    }

    A& operator=(A&& a)
    {
        printf("=(A&&)_%x\n", this);
        m_c = a.m_c;
        fun(a);
        return *this;
    }

    A& operator=(const A&& a)
    {
        printf("=(const A&&)_%x\n", this);
        m_c = a.m_c;
        fun(a);
        return *this;
    }

    void fun(A&& a)
    {
        printf("fun(A&&)_%x\n", this);
    }

    void fun(const A&& a)
    {
        printf("fun(const A&&)_%x\n", this);
    }

    void fun(A& a)
    {
        printf("fun(A&)_%x\n", this);
    }

    void fun(const A& a)
    {
        printf("fun(constA&)_%x\n", this);
    }

    ~A()
    {
        printf("~A()_%x\n", this);
    }

private:
    char m_c;
};


void funn(A a)
{

}

int main()
{
    A a;
    const A a2;
    printf("tag\n");
    // std::move针对非常量左值,返回非常量右值引用。
    // 非常量右值引用虽然是一个非常量右值的别名,但由于有了名字。所以,右值引用被当成一个左值。
    // 故,通过非常量右值引用调用fun时,匹配到非常量左值引用版本。
    A a3 = std::move(a);
    printf("tag1\n");
    // std::move针对常量左值,返回常量右值引用。
    // 常量右值引用虽然是一个常量右值的别名,但由于有了名字。所以,右值引用被当成一个左值。
    // 故,通过常量右值引用调用fun时,匹配到常量左值引用版本。
    A a4 = std::move(a2);
    printf("tag2\n");
    // std::move针对非常量右值,返回非常量右值引用。
    // 非常量右值引用虽然是一个非常量右值的别名,但由于有了名字。所以,右值引用被当成一个左值。
    // 故,通过非常量右值引用调用fun时,匹配到非常量左值引用版本。
    A a5 = std::move(A());
    printf("tag3\n");

    A& aa1 = a;
    // std::move针对非常量左值引用,返回非常量右值引用。
    A aaa1 = std::move(aa1);
    printf("tag4\n");
    const A& aa2 = a2;
    // std::move针对常量左值引用,返回常量右值引用。
    A aaa2 = std::move(aa2);
    printf("tag5\n");


    A&& aa3 = A();
    // std::move针对非常量右值引用,返回非常量右值引用。
    A aaa3 = std::move(aa3);
    printf("tag6\n");
    const A&& aa4 = A();
    // std::move针对常量右值引用,返回常量右值引用。
    A aaa4 = std::move(aa4);
    printf("tag7\n");

    A b;
    funn(b);
    printf("tag5\n");
    return 0;
}

在这里插入图片描述
在这里插入图片描述
针对std::move简单的总结是:
(1). 针对左值实参,左值引用实参会返回此实参的右值引用类型。
(2). 针对右值实参,右值引用实参会返回此实参的右值引用类型。
(3). 保持实参的常量属性不变。

注意点:
(1). 右值引用虽然是一个右值的引用,但由于引用本身是有名字的,所以,右值引用是一个左值类型。
(2). std::move返回实参的右值引用,但这里相当于一个得到了匿名右值引用。所以将此返回值传参调用函数时,右值引用会被当成右值类型。而非(1)中的左值类型。

关于std::move保持实参的常量属性不变的辅助实例

#include <iostream>

void fun(int* && a)
{
    printf("int* &&\n");
}

void fun(const int* && a)
{
    printf("const int* &&\n");
}

void fun(int* const && a)
{
    printf("int* const &&\n");
}

void fun(const int* const && a)
{
    printf("const int* const &&\n");
}


int main()
{
    int a1 = 10;
    const int a11 = 10;

    int *a = &a1;
    const int* a2 = &a11;
    int * const a3 = &a1;
    const int* const a4 = &a11;
    fun(std::move(a));
    fun(std::move(a2));
    fun(std::move(a3));
    fun(std::move(a4));
    return 0;
}

在这里插入图片描述

1.5.编译器的返回值优化
上述各个观察函数返回值的实例我们编译时,加了-fno-elide-constructors选项来关闭编译器针对返回值的优化。当开启此优化时:

#include <iostream>

class A
{
public:
    A(){
        printf("A()_%x\n", this);
    }
    A(A&a){
        printf("A(&)_%x\n", this);
    }
    A(const A&){
        printf("A(const A&)_%x\n", this);
    }
    A(A&&){
        printf("A(&&)_%x\n", this);
    }
    A(const A&&){
        printf("A(const&&)_%x\n", this);
    }
    ~A(){
        printf("~A()_%x\n", this);
    }
};

A fun()
{
    A a;
    return a;
}

int main()
{
    A b= fun();
    return 0;
}

在这里插入图片描述
b直接霸占了fun内部的变量a
此优化并不是对任何情况均有效。还有一些情形即使存在此优化,也不能达到最好效果。
而移动语义总是可以在显式控制下采用最有效的方法实现目标。

2.完美转发
完美转发指的是在函数模板中,完全依照模板参数的类型,将参数传递给函数模板中调用的另外一个函数。
2.1.引用折叠
为了支持完美转发,c++既需要引入右值,右值引用。也需要引入引用折叠。
引用折叠进一步分为左值引用折叠,右值引用折叠。

// TR的类型定义为:T&,声明v的类型为TR& -> v的实际类型为:T&,又因为T为const int所以,v的实际类型为const int&,所以TR& v = 1;对应const int& v = 1;
#include <iostream>
typedef const int T;
typedef T& TR;
int main()
{
    TR& v = 1;
}
TR的类型定义声明v的类型v的实际类型
T&TRT&
T&TR&T&
T&TR&&T&
T&&TRT&&
T&&TR&T&
T&&TR&&T&&

将上述引用折叠结合模板类型推断可以实现完美转发。

2.2.模板类型推断
当模板的形参中对模板类型采用T&,T&&的形式时,推断T的类型时:
当转发函数的实参是类型X的一个左值或左值引用,则模板参数T被推导为X&类型。
当转发函数的实参是类型X的一个右值或右值引用,则模板参数T被推导为X&&类型。

此时需结合引用折叠规则来最终确定参数的实际类型。
一个实例

template<typename T>
void IamForwording(T&& t){
	IrunCodeActually(static_cast<T&&>(t));
}

对于上述形式的转发函数,如果我们调用转发函数时传入了一个X类型的左值或左值引用。则:
(1). 模板参数T被推断为X&类型。
(2). 引用折叠完成后的实际的转发函数是

void IamForwording(X& t){
	IrunCodeActually(<X&>(t));
}

这样通过左值或左值引用调用转发函数,最终调用实际函数的左值引用版本。

如果我们调用转发函数时传入了一个X类型的右值或右值引用。则:
(1).模板参数T被推断为X&&类型。
(2).引用折叠完成后的实际的转发函数是

void IamForwording(X&& t){
	IrunCodeActually(<X&&>t);
}

这样通过右值或右值引用调用转发函数,最终调用实际函数的右值引用版本。
上述<X&&>t并不多余,因为若直接才有t的形式调用实际函数,由于右值引用类型自身是左值所以将匹配到左值引用版本。

2.3.完美转发实例

#include <iostream>
using namespace std;

void RunCode(int&& m){ cout << "int&&" << endl; }
void RunCode(int& m){ cout << "int&" << endl; }
void RunCode(const int&& m) { cout << "const int&&" << endl; }
void RunCode(const int& m) { cout << "const int&" << endl; }

template<typename T>
void PerfectForward(T&& t){
	RunCode(forward<T>(t));
}

int main(){
	int a;
	int b;
	const int c = 1;
	const int d = 0;
	PerfectForward(a);
	PerfectForward(move(b));
	PerfectForward(c);
	PerfectForward(move(d));
}

在这里插入图片描述

将上述转发函数改为如下形式,一样可以达到效果:

template<typename T>
void PerfectForward(T&& t){
	RunCode(static_cast<T&&>(t));
}

若上述模板形参变成如下:

template<typename T>
void PerfectForward(T& t){
	RunCode(static_cast<T&&>(t));
}

int main(){
	int a;
	int b;
	const int c = 1;
	const int d = 0;
	PerfectForward(a);
	PerfectForward(move(b));
	PerfectForward(c);
	PerfectForward(move(d));
}

则执行PerfectForward(move(b));会报错,因为此时无法完成将一个右值引用匹配到【T推断为int&&,T&t的实际类型为int&int&的转换。
PerfectForward(move(d));可以执行,此时是一个常量右值引用匹配到【T推断为const int&&,T&t的实际类型为const int&const int&的转换,然后内部执行RunCode会转变为一个const int&&类型参与RunCode的匹配过程。

这里static_cast<T&&>折叠后变为static_cast<const int&&>,这里的右值引用之所以不会当成作值类似std::move返回的右值引用,是因为这里得到的右值引用是一个匿名的右值引用。所以,参与函数传参时,不会被当成左值。

为了完整,我们在main中再补充一组测试

printf("tag\n");
int& aa = a;
const int& caa = c;
int&& aaa = move(a);
const int&& caaa = move(d);
PerfectForward(aa);
PerfectForward(caa);
PerfectForward(aaa);
PerfectForward(caaa);

上述补充内容对应的输出为:
在这里插入图片描述
因为这里的aaa,caaa是有名字的右值引用,所以参与函数传参时,被当作左值对待了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

信号与线性系统翻转课堂笔记4——连续LTI系统的微分方程模型与求解

信号与线性系统翻转课堂笔记4——连续LTI系统的微分方程模型与求解 The Flipped Classroom4 of Signals and Linear Systems 对应教材&#xff1a;《信号与线性系统分析&#xff08;第五版&#xff09;》高等教育出版社&#xff0c;吴大正著 一、要点 &#xff08;1&#x…

gitee提交代码步骤介绍(含git环境搭建)

1、gitee官网地址 https://gitee.com; 2、Windows中安装git环境 参考博客&#xff1a;《Windows中安装Git软件和TortoiseGit软件》&#xff1b; 3、设置用户名和密码 这里的用户名和密码就是登录gitee网站的用户名和密码如果设置错误&#xff0c;可以在Windows系统的“凭据管理…

Kubernetes (k8s) 快速认知

应用部署方式 传统部署时代 早期的时候&#xff0c;各个组织是在物理服务器上运行应用程序。缺点 资源分配问题&#xff1a; 无法限制在物理服务器中运行的应用程序资源使用 维护成本问题&#xff1a; 部署多个物理机&#xff0c;维护许多物理服务器的成本很高 虚拟化部署时…

论文修改润色算学术不端吗 快码论文

大家好&#xff0c;今天来聊聊论文修改润色算学术不端吗&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff0c;可以借助此类工具&#xff1a; 标题&#xff1a;论文修改润色是否算学术不端&#xff1f;专业软件…

U-boot启动流程与加载内核过程

目录 一、U-boot启动过程流程图二、U-boot启动过程函数简单注释 本篇文章梳理了一下对正点原子的驱动开发教程中u-boot启动流程的梳理&#xff0c;制作了一份流程图&#xff0c;并简单的记录了一下各函数的作用&#xff0c;方便回头翻阅。 一、U-boot启动过程流程图 二、U-boot…

git-lfs基本知识讲解

目录 1. 基本知识2. 安装 1. 基本知识 git-lfs 是 Git Large File Storage 的缩写&#xff0c;是 Git 的一个扩展&#xff0c;用于处理大文件的版本控制。 它允许你有效地管理和存储大型二进制文件&#xff0c;而不会使 Git 仓库变得过大和不稳定。以下是一些与 git-lfs 相关…

机器学习——自领域适应作业

任务 游戏里面的话有很多跟现实不一样的情况。 想办法让中间的特征更加的接近&#xff0c;让feat A适应feat B&#xff0c;产生相对正常的输出。 在有标签数据和没有数据的上面进行训练&#xff0c;并能预测绘画图像。 数据集 训练5000张总数&#xff0c;每类有500张测试100…

Jmeter实现服务器端后台接口性能测试!

实现目的 在进行服务器端后台接口性能测试时&#xff0c;需要连接到Linux服务器端&#xff0c;然后通过命令调用socket接口&#xff0c;这个过程就需要用到jmeter的SSH Command取样器实现了。 脚本实现 设置CSV Data Set ConFig配置元件&#xff0c;参数化测试数据 设置SSH…

【线性代数】期末速通!

1. 行列式的性质 1.1 求一个行列式的值 特殊地&#xff0c;对角线左下全为0&#xff0c;结果为对角线乘积。行 r 列 c 1.2 性质 某行&#xff08;列&#xff09;加上或减去另一行&#xff08;列&#xff09;的几倍&#xff0c;行列式不变某行&#xff08;列&#xff09;乘 …

118. 杨辉三角

给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]示例 2: 输入: numRows 1 输出: [[1]]提示: 1 <…

Spring Boot 3 + Vue 3 整合 WebSocket (STOMP协议) 实现实时通信

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

08 v-text指令

概述 v-text指令主要是用来渲染文本内容&#xff0c;和双大括号的效果基本一致&#xff0c;所以使用场景非常少。 一般情况下&#xff0c;我们都会使用双大括号语法去渲染文本内容&#xff0c;而不是使用v-text指令。 基本用法 我们创建src/components/Demo08.vue&#xff…

web服务器之——基于虚拟目录和用户控制的web网站

目录 一、虚拟目录 虚拟目录的作用&#xff1a; 二、搭建基于虚拟目录的web网站 1、www服务器配置 2、搭建静态网站 设置防火墙状态 关闭文件访问权限——SeLinux 3、编辑网页资源文件 4、设置虚拟目录 5、向虚拟目录中写入资源 6、重启httpd 三、搭建基…

SCI一区级 | Matlab实现GWO-CNN-GRU-selfAttention多变量多步时间序列预测

SCI一区级 | Matlab实现GWO-CNN-GRU-selfAttention多变量多步时间序列预测 目录 SCI一区级 | Matlab实现GWO-CNN-GRU-selfAttention多变量多步时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现GWO-CNN-GRU-selfAttention灰狼算法优化卷积门控循环…

气候灾害组织:全球红外降水站数据

气候灾害组织红外降水站数据-Prelim (CHIRPS-Prelim) 气候灾害中心红外降水站数据 Prelim (CHIRPS-Prelim) 融合了 CHIRPS 数据与原位降水数据&#xff0c;以消除数据偏差并提高其准确性。生成 CHIRPS-Prelim 的过程与 CHIRPS 过程类似&#xff0c;主要区别在于它仅依赖于近实…

ripro后台登录后转圈和图标不显示的原因及解决方法

最近&#xff0c;好多小伙伴使用ripro主题的小伙伴们都发现&#xff0c;登录后台后&#xff0c;进入主题设置就转圈&#xff0c;等待老半天后好不容易显示页面了&#xff0c;却发现图标不显示了&#xff0c;都统一显示为方框。 这是因为后台的js、css这类静态资源托管用的是js…

快速排序(一)

目录 快速排序&#xff08;hoare版本&#xff09; 初级实现 问题改进 中级实现 时空复杂度 高级实现 三数取中 快速排序&#xff08;hoare版本&#xff09; 历史背景&#xff1a;快速排序是Hoare于1962年提出的一种基于二叉树思想的交换排序方法 基本思想&#xff1a…

和鲸科技CEO范向伟受邀出席港航数据要素流通与生态合作研讨会,谈数据资产入表的战略机会

近日&#xff0c;由上海虹口数字航运创新中心、龙船&#xff08;北京&#xff09;科技有限公司&#xff08;下简称“龙船科技”&#xff09;、华东江苏大数据交易中心联合举办的“港航数据要素流通与生态合作研讨会”圆满落幕&#xff0c;来自港航领域的近百名企业代表共同参与…

【具身智能评估3】具身视觉语言规划(EVLP)度量标准汇总

参考论文&#xff1a;Core Challenges in Embodied Vision-Language Planning 论文作者&#xff1a;Jonathan Francis, Nariaki Kitamura, Felix Labelle, Xiaopeng Lu, Ingrid Navarro, Jean Oh 论文原文&#xff1a;https://arxiv.org/abs/2106.13948 论文出处&#xff1a;Jo…

【开源项目】WPF 扩展 -- 多画面视频渲染组件

目录 1、项目介绍 2、组件集成 2.1 下载地址 2.2 添加依赖 3、使用示例 3.1 启动动画 3.2 视频渲染 3.3 效果展示 4、项目地址 1、项目介绍 Com.Gitusme.Net.Extensiones.Wpf 是一款 Wpf 扩展组件。基于.Net Core 3.1 开发&#xff0c;当前是第一个发布版本 1.0.0&am…