C++移动操作,RVO和NRVO详细

目录
  • 一、移动操作
    • 1、移动操作有关的函数
    • 2、何时自动声明移动构造函数和赋值移动构造函数
    • 3、何时自动移动
  • 二、复制消除、RVO和NRVO
    • 1、复制消除
    • 2、RVO和NRVO

移动操作主要参考了cppreference 的这个说明,
优化部分的主要的参考来自于stack overflow 的这篇文章

一、移动操作

1、移动操作有关的函数

和移动操作相关的类函数有两个

移动构造函数:

A(A&& rhs);

移动赋值运算符:

A& operator=(A&& rhs);

注意这两个函数的参数类型都不是const,这也是C++默认会生成的函数声明。
移动构造函数用于在构造类型的时候使用:

A a1;

// 使用std::move强制进行移动
A a2 = std::move(a1);
或
A a2(std::move(a1));

而移动赋值运算符就是在赋值的时候进行移动:

A a1;
A a2;
a1 = std::move(a2); // 使用move进行强制移动

2、何时自动声明移动构造函数和赋值移动构造函数

隐式的移动构造函数将会在可以被生成且满足如下所有条件的情况下自动生成:

  • 没有用户声明的 复制构造函数
  • 没有用户声明的 复制赋值运算符(即operator=(const A&)这类)
  • 没有用户声明的 移动赋值运算符(即operator=(A&&)这类)
  • 没有用户声明的 析构函数

所谓可以被生成的意思是满足以下所有条件:

  • 类中没有不能移动的非静态成员
  • 继承时,基类可以被移动
  • 继承时,基类的构造函数可以被访问

而移动赋值运算符的产生条件也差不多,只不过将没有声明的 移动赋值构造函数改成没有用户声明 移动构造函数即可。

总之,这两个函数生成的条件就一句话:除了普通的构造函数外(指默认构造函数和带其他参数的构造函数),不得声明任何其他的构造函数,operator=函数和析构函数。

3、何时自动移动

使用std::move是一种强制的,显式的移动。但是C++很多时候为了效率会自动帮我们移动。主要的规则其实就是所有的右值都会进行移动,如果不能移动,进行拷贝。但是为了严谨,我们还是摆出cppreference上的规则:

  • 初始化的时候使用std::move():T a = std::move(b)或者T a(std::move(b));这种。这里要加上std::move(),不然会调用复制构造函数。
  • 函数实参传递的时候使用std::move() :func(std::move(a))
  • 函数返回时,如:
class A {};

A CreateA() {
 return A();
}

// call

A a = CreateA();

的时候,使用A()产生的变量会首先移动到CreateA()函数产生的返回值中,这个时候这个返回值是一个临时变量(我们记为temp),接下来就是执行这段代码:A a = temp,然后temp是临时变量, 会再次调用A的移动构造函数给a变量。

前两个是属于显式的移动,最后一种就是隐式移动。移动赋值运算符的规则也是一样,只有等号右边是临时变量就会自动调用。

二、复制消除、RVO和NRVO

虽然C++对移动操作定义的很明确,但编译器却并不总是按照这个定义去做。因为编译器中有三个重要的优化经常会减少拷贝,甚至是移动操作。

GCCClang下可以添加-fno-elide-constructors选项来关闭这三种优化。

1、复制消除

来看一看下面代码:

class C {
public:
  C() {}
  C(const C&) { std::cout << "A copy was made.\n"; }
  C(C&& rhs) { std::cout << "A move was made.\n"; }

};

C f() {
  return C();
}

int main() {
  std::cout << "Hello World!\n";
  C obj = f();
}

这里建议在C++17标准下编译,因为C++17起所有的复制消规则除被写在语言规范内,大部分编译器应该都会做这件事。我的Clang++ 12.0.5上的执行结果仅仅是输出了一行Hello World:

Hello World!

按照上面的规则,函数在返回的时候会进行移动,也就是说在f()的调用内,会先移动给临时变量,然后临时变量再移动给obj,但是这里什么都没发生,没有任何的移动和拷贝,obj就像凭空出现了一样。

在C++17起,复制消除是强制执行的,而C++11中是看编译器心情。
在如下条件下会进行复制消除:

  • return语句中,return的值是和函数返回值类型一样的右值。类型一样是为了防止隐式转换,否则会产生新的变量从而阻止移动,右值是因为C++自动移动只能对右值操作。
  • 在变量初始化的时候,初始化表达式是右值。如:
class A{};

A f() { return A(); } // 这里是第一种情况,会自动复制消除

// call
A a = f(); // 这里函数返回值的临时变量到a的过程中的移动也会被消除

这也就解释了为什么上面的代码没有调用任何的拷贝,移动函数了。

2、RVO和NRVO

RVO是Return Value Optimization(返回值优化)的简写,而NRVONamed Return Value Optimization(命名返回值优化)的简写。这两个优化是复制消除的常见形式。
通过他们的名字就可以看出,这是在函数返回的时候做的优化。

RVO是指在函数返回一个临时变量时的优化,具体的优化如下:

// 原本的函数
T CreateT(int value) {
 return T(value);
}

T a = CreateT(10);

// 优化后的函数(伪代码):
void CreateT(T& v, int value) {
 v.T::T(value); // 直接在内部进行构造
}

即通过将要接收函数返回值的对象以引用的形式放入函数内部初始化,这样就避免了一次移动/拷贝。

而NRVO则是更加宽泛的RVO。对于如下的代码可以执行NRVO:

T CreateT(int values) {
 T t(value);
 return t;
}

编译器也会优化成上面RVO优化的样子。

到此这篇关于C++移动操作,RVO和NRVO详细的文章就介绍到这了,更多相关C++移动操作,RVO和NRVO内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解C++11中的右值引用与移动语义

    C++11的一个最主要的特性就是可以移动而非拷贝对象的能力.很多情况都会发生对象的拷贝,有时对象拷贝后就立即销毁,在这些情况下,移动而非拷贝对象会大幅度提升性能. 右值与右值引用 为了支持移动操作,新标准引入了一种新的引用类型--右值引用,就是必须绑定到右值的引用.我们通过&&而不是&来获得右值引用.右值引用一个重要的特性就是只能绑定到将要销毁的对象. 左值和右值是表达式的属性,一些表达式生成或要求左值,而另一些则生成或要求右值.一般而言,一个左值表达式表示的是一个对象的身份,而右

  • C++学习之移动语义与智能指针详解

    移动语义 1.几个基本概念的理解 (1)可以取地址的是左值,不能取地址的就是右值,右值可能存在寄存器,也可能存在于栈上(短暂存在栈)上 (2)右值包括:临时对象.匿名对象.字面值常量 (3)const 左值引用可以绑定到左值与右值上面,称为万能引用.正因如此,也就无法区分传进来的参数是左值还是右值. const int &ref = a;//const左值引用可以绑定到左值 const int &ref1 = 10;//const左值引用可以绑定到右值 (4)右值引用:只能绑定到右值不能绑

  • C++中的移动构造函数及move语句示例详解

    前言 本文主要给大家介绍了关于C++中移动构造函数及move语句的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 首先看一个小例子: #include <iostream> #include <cstring> #include <cstdlib> #include <vector> using namespace std; int main() { string st = "I love xing"; vec

  • 详解C++中对构造函数和赋值运算符的复制和移动操作

    复制构造函数和复制赋值运算符 从 C++ 11 中开始,该语言支持两种类型的分配:复制赋值和移动赋值. 在本文中,"赋值"意味着复制赋值,除非有其他显式声明. 赋值操作和初始化操作都会导致对象被复制. 赋值:在将一个对象的值赋给另一个对象时,第一个对象将复制到第二个对象中. 因此, Point a, b; ... a = b; 导致 b 的值被复制到 a 中. 初始化:在以下情况下将进行初始化:声明新对象.参数通过值传递给函数或值通过值从函数返回. 您可以为类类型的对象定义"

  • C++11中value category(值类别)及move semantics(移动语义)的介绍

    前言 C++11之前value categories只有两类,lvalue和rvalue,在C++11之后出现了新的value categories,即prvalue, glvalue, xvalue.不理解value categories可能会让我们遇到一些坑时不知怎么去修改,所以理解value categories对于写C++的人来说是比较重要的.而理解value categories离不开一个概念--move semantics.了解C++11的人我相信都了解了std::move,右值引用

  • c++ 移动构造相关总结

    下面随笔给出c++移动构造. 在现实中有很多这样的例子,我们将钱从一个账号转移到另一个账号,将手机SIM卡转移到另一台手机,将文件从一个位置剪切到另一个位置--移动构造可以减少不必要的复制,带来性能上的提升. C++11标准中提供了一种新的构造方法--移动构造. C++11之前,如果要将源对象的状态转移到目标对象只能通过复制.在某些情况下,我们没有必要复制对象--只需要移动它们. C++11引入移动语义: 源对象资源的控制权全部交给目标对象 移动构造函数 问题与解决 当临时对象在被复制后,就不再

  • C++移动操作,RVO和NRVO详细

    目录 一.移动操作 1.移动操作有关的函数 2.何时自动声明移动构造函数和赋值移动构造函数 3.何时自动移动 二.复制消除.RVO和NRVO 1.复制消除 2.RVO和NRVO 移动操作主要参考了cppreference 的这个说明, 优化部分的主要的参考来自于stack overflow 的这篇文章 一.移动操作 1.移动操作有关的函数 和移动操作相关的类函数有两个 移动构造函数: A(A&& rhs); 移动赋值运算符: A& operator=(A&& rhs

  • 详解C++右值引用

    概述 在C++中,常量.变量或表达式一定是左值(lvalue)或右值(rvalue). 左值:非临时的(具名的,可在多条语句中使用,可以被取地址).可以出现在等号的左边或右边.可分为非常量左值和常量左值. 右值:临时的(不具名的,只在当前语句中有效,不能取地址).只能出现在等号的右边.可分为非常量右值和常量右值. 左值引用:对左值的引用就是左值引用.可分为非常量左值引用和常量左值引用. 注:常量左值引用是"万能"的引用类型,可以绑定到所有类型的值,包括非常量左值.常量左值.非常量右值和

  • C++ move semantic移动语义介绍

    目录 前言 移动构造 为什么我们需要move semantic 前言 在说移动语义之前 本文作者假设你已经具备了深拷贝浅拷贝左值右值等基本概念 本文不会再过多叙述 那么接下来 让我们开始吧 Tips:(警告 警告 警告 警告)在阅读本文章之前 作者首先提醒 线代编译器有RVO和NRVO等一系列优化策略 除非你明确知道你要使用std::move 不然我并不是很推荐你使用移动语义 他很有可能是无意义的 移动构造 在说移动语义之前 让我们先来说说移动构造这玩意 我们都知道 深拷贝是会把在堆区的内存一起

  • java文件的重命名与移动操作实例代码

    文件的重命名与移动操作 有时候为了对文件进行统一访问与管理,需要把文件进行重命名,并移动到新的文件夹,如何实现呢? 一枚简单的java小程序即可实现: part_1:需求:我需要把<(E:\BaiduYun\传智播客_张孝祥_Java多线程与并发库高级应用视频教程下载)>文件夹下的所有子文件夹下的视频文件重命名,并移动到新的位置<(E:\BaiduYun\张孝祥_Java多线程与并发库)>; part_2:目录结构如下: E:\BaiduYun E:\BaiduYun\传智播客_张

  • Java编程文件遍历之指定遍历的层数详细代码

    遍历就是把每个元素都访问一次.比如一个二叉树,遍历二叉树意思就是把二叉树中的每个元素都访问一次 本例演示了"文件遍历时,指定遍历的层数"的实现方式. 1.例子代码 package com.myjava.test; import java.io.File; import java.util.ArrayList; import java.util.List; public class JavaTest { /** * @param args */ public static void ma

  • 详解vue3.0 diff算法的使用(超详细)

    前言:随之vue3.0beta版本的发布,vue3.0正式版本相信不久就会与我们相遇.尤玉溪在直播中也说了vue3.0的新特性typescript强烈支持,proxy响应式原理,重新虚拟dom,优化diff算法性能提升等等.小编在这里仔细研究了vue3.0beta版本diff算法的源码,并希望把其中的细节和奥妙和大家一起分享. 首先我们来思考一些大中厂面试中,很容易问到的问题: 1 什么时候用到diff算法,diff算法作用域在哪里? 2 diff算法是怎么运作的,到底有什么作用? 3 在v-f

  • React diff算法原理详细分析

    目录 抛砖引玉 传统diff算法 React diff原理 tree diff component diff element diff 应用实践 页面指定区域刷新 更加方便地监听props改变 react-router中Link问题 结语 抛砖引玉 React通过引入Virtual DOM的概念,极大地避免无效的Dom操作,已使我们的页面的构建效率提到了极大的提升.但是如何高效地通过对比新旧Virtual DOM来找出真正的Dom变化之处同样也决定着页面的性能,React用其特殊的diff算法解

  • C++移动语义详细介绍使用

    目录 1.移动构造函数 2.右值引用 3.std::move()将左值强制转换为右值引用 4.拷贝语义和移动语义 1.移动构造函数 移动语义就是使用移动构造函数来构造对象. 我们知道在类中如果存在指针数据成员,那么我们就一定要写拷贝构造函数,进行深拷贝 如下所示,就是拷贝构造函数的用法: #include<iostream> using namespace std; class A { public: int * ptr; A():ptr(new int(0)) { cout<<&

  • vmware12中安装 RedHat RHEL7.2系统的详细步骤(图文)

    本文介绍了vmware12中安装 RedHat RHEL7.2系统的详细步骤(图文),分享给大家,具体如下: 一.开始安装 1)新建虚拟机 RHEL7.2 2)成功引导系统--开机出现此画面 Install Red Hat EnterpriseLinux 7.2 安装RHLE7.2 操作系统 Test this edia & install RedHat Enterprise Linux 7.2 测试安装文件并安装RHLE7.2 操作系统 Troubleshooting 修复故障 3)选择第一项

  • Mac系统下MySql下载MySQL5.7及详细安装图解

    一.在浏览器当中输入以下地址 https://dev.mysql.com/downloads/mysql/ 二.进入以下界面:直接点击下面位置 ,选择跳过登录 点过这后直接下载. 三.下载完成后, 直接双击打开,弹出以下界面,再继续点击即可 下面 一直点继续,和正学安装其它软件一样 四.启动MySQL 安装完毕后,到设置当中查看以下选项,如果里面有MySQL说明已经安装成功 点击后, 启动MySQL 五.修改数据库密码 启动完成后,打开终端 aliasmysql=/usr/local/mysql

随机推荐