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

 更新时间:2021年09月30日 08:53:57   作者:VisualGMQ  
本文将讨论了何时C++会自动进行移动操作,并且说明了复制消除,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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • c++实现高精度加法

    c++实现高精度加法

    高精度运算是指参与运算的数(加数,减数,因子……)范围大大超出了标准数据类型(整型,实型)能表示的范围的运算。例如,求两个200位的数的和。这时,就要用到高精度算法了。
    2017-05-05
  • c++与python实现二分查找的原理及实现

    c++与python实现二分查找的原理及实现

    本文介绍了c++与python实现二分查找的原理及实现,二分查找指首先将数组中间值和目标值进行比较,如果相等则返回;如果不相等,则选择中间值左边的一半或者右边的一半进行比较;不断重复直到检索完毕,下文相关资料需要的朋友可以参考一下
    2022-03-03
  • C语言实现学生学籍管理系统程序设计

    C语言实现学生学籍管理系统程序设计

    这篇文章主要为大家详细介绍了C语言实现学生学籍管理系统程序设计,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-07-07
  • C语言全部内存操作函数的实现详细讲解

    C语言全部内存操作函数的实现详细讲解

    这篇文章主要介绍了C语言全部内存操作函数的实现详细讲解,作者用图文代码实例讲解的很清晰,有感兴趣的同学可以研究下
    2021-02-02
  • opencv3/C++视频中叠加透明图片的实现

    opencv3/C++视频中叠加透明图片的实现

    今天小编就为大家分享一篇opencv3/C++视频中叠加透明图片的实现,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-12-12
  • C++ operator关键字(重载操作符)的用法详解

    C++ operator关键字(重载操作符)的用法详解

    下面小编就为大家带来一篇C++ operator关键字(重载操作符)的用法详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-01-01
  • C++之谈谈构造函数的初始化列表

    C++之谈谈构造函数的初始化列表

    构造函数主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用,这篇文章详细介绍了构造函数的初始化列表,文章中有详细的示例代码,感兴趣的同学可以参考阅读
    2023-04-04
  • 详细讲解C语言中的数据以及位运算

    详细讲解C语言中的数据以及位运算

    这篇文章主要为大家详细介绍了C语言中数据表示方法以及位运算的相关知识点,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-06-06
  • C++文件的操作及小实验示例代码详解

    C++文件的操作及小实验示例代码详解

    这篇文章主要介绍了C++文件的操作及小实验,对于文件,它是一个流对象,对文件的操作无非是读和写,通过本文的学习大家将会理解文件的具体操作
    2022-05-05
  • Qt实现导出QTableWidget/QTableView数据

    Qt实现导出QTableWidget/QTableView数据

    这篇文章主要介绍了在Qt中实现将QTableWidget或者QTableView中的数据直接导出的示例代码,文中的示例代码讲解详细,感兴趣的可以了解一下
    2022-01-01

最新评论