温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

C++中RVO和NRVO怎么用

发布时间:2021-09-30 10:48:20 来源:亿速云 阅读:133 作者:小新 栏目:开发技术

这篇文章主要为大家展示了“C++中RVO和NRVO怎么用”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“C++中RVO和NRVO怎么用”这篇文章吧。

一、移动操作

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怎么用”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注亿速云行业资讯频道!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI