`
jimmee
  • 浏览: 537968 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

【转载】RVO V.S. std::move

阅读更多

Return Value Optimization

Return value optimization, simply RVO, is a compiler optimization technique that allows the compiler to construct the return value of a function at the call site. The technique is also named "elision". C++98/03 standard doesn’t require the compiler to provide RVO optimization, but most popular C++ compilers contain this optimization technique, such as IBM XL C++ compiler, GCC and Clang . This optimization technique is included in the C++11 standard due to its prevalence.  As defined in Section 12.8 in the C++11 standard, the name of the technique is "copy elision".

 

Let’s start with one example to see how the RVO works. Firstly, we have a class named BigObject. Its size could be so large that copying it would have high cost. Here I just define constructor, copy constructor and destructor for convenience.

 

classBigObject{
public:
BigObject(){
cout<<"constructor."<<endl;
}
~BigObject(){
cout<<"destructor."<<endl;
}
BigObject(constBigObject&){
cout<<"copyconstructor."<<endl;
}
};

 

We then define one function named foo to trigger the RVO optimization and use it in the main function to see what will happen.

 

BigObjectfoo(){
BigObjectlocalObj;
returnlocalObj;
}
 
intmain(){
BigObjectobj=foo();
}

 

Some people will call this case using named return value optimization(NRVO), because foo returns one temporary object named localObj. They think that what returns BigObject() is RVO. You don't  need to worry about it, as NRVO is one variant of RVO.

 

Let’s compile this program and run: xlC a.cpp ;./a,out. (The version of the XL C/C++ compiler used is V13 and the environment is Linux little endian.) The output is like this:

constructor.

destructor.

 

It is amazing, right? There is no copy constructor here. When the price of coping is high, RVO enables us to run the program much faster. 

 

However, when we modify our code a little, things will change.

 

BigObjectfoo(intn){
BigObjectlocalObj,anotherLocalObj;
if(n>2){
returnlocalObj;
}else{
returnanotherLocalObj;
}
}
intmain(){
BigObjectobj=foo(1);
}

 

The output will be like this:

constructor.

constructor.

copy constructor.

destructor.

destructor.

destructor.


We can find that copy constructor is called. Why?  It's time to show the mechanism of RVO.

 

image

 

 

 

 

 

 

 

 

 

 

 

 

This diagram is a normal function stack frame. If we call the function without RVO, the function simply allocates the space for the return in its own frame. The process is demonstrated in the following diagram:

image

 

 

 

 

 

 

 

 

 

 

 

What will happen if we call the function with RVO?


image

 

 

 

 

 

 

 

 

 

 

 

We can find that RVO uses parent stack frame (or an arbitrary block of memory) to avoid copying. So, if we add if-else branch, the compiler doesn’t know which return value to put.

 

std::move

We first need to understand what std::move is. Many C++ programmers misunderstand std::move so that they use std::move in wrong situations. Let us see the implementation of std::move.

 

template<typenameT>
decltype(auto)move(T&&param)
{
usingReturnType=remove_reference_t<T>&&;
returnstatic_cast<ReturnType>(param);
}

 

In fact, the key step std::move performs is to cast its argument to an rvalue. It also instructs the compiler that it is eligible to move the object, without moving anything. So you can also call "std::move" as "std::rvalue_cast", which seems to be more appropriate than "std::move".


The price of moving is lower than coping but higher than RVO.  Moving does the following two things:

    1. Steal all the data
    2. Trick the object we steal into forgetting everything

 

If we want to instruct the compiler to move, we can define move constructor and move assignment operator. I just define move constructor for convenience here.

 

BigObject(BigObject&&) {

cout<<"moveconstructor"<<endl;
}

 

Let us modify the code of foo.

 

BigObject foo(int n) {

BigObjectlocalObj,anotherLocalObj;
if(n>2){
returnstd::move(localObj);
}else{
returnstd::move(anotherLocalObj);
}
}

 

Let’s compile it and run: xlC –std=c++11 a.cpp;./a.out. The result is:
constructor.
constructor.
move constructor
destructor.
destructor.
destructor.


We can find that move constructor is called as expected. However, we must also note that the compiler also calls destructor while RVO doesn’t.


Some people would think that returning std::move(localObj) is always beneficial. If the compiler can do RVO, then RVO. Otherwise, the compiler calls move constructor. But I must say: Don't DO THAT!


Let us see what will happen if we do this:

 

BigObject foo(int n) {

BigObjectlocalObj;
returnstd::move(localObj);
}
intmain(){
    auto f = foo(1);
}

 

Maybe you think that the compiler will do RVO, but you actually get this result:
constructor.
move constructor

destructor.
destructor.


The compiler doesn’t do RVO but call move constructor! To explain it, we need to look at the details of copy elision in C++ standard first:

in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value

 

That is to say, we must keep our type of return statement the same as function return type.


Let's then, refresh our memory about std::move a little bit. std::move is just an rvalue-cast. In other words, std::move will cast localObj to localObj&& and the type is BigObject&&, but the function type is just BigObject. BigObject is not BigObject&&, so this is the reason why RVO didn’t take place just now.


We now change the foo function return type and obj type is BigObject&&:

 

BigObject&& foo(int n) {

BigObjectlocalObj;
returnstd::move(localObj);
}
intmain(){
auto f = foo(1);
}

 

Then we compile and run it, and we will get the output like this:

constructor. 
destructor.
move constructor
destructor.


Yes! The compiler does RVO! (Note: We should not use this way in the real development, because it is a reference to a local object. Here just show how to make RVO happened.).  

 

To summarize, RVO is a compiler optimization technique, while std::move is just an rvalue cast, which also instructs the compiler that it's eligible to move the object. The price of moving is lower than copying but higher than RVO, so never apply std::move to local objects if they would otherwise be eligible for the RVO.

 

 

English Editor: Jun Qian Zhou (Ashley). Many thanks to Ashley! 

分享到:
评论

相关推荐

    C++ 17 标准手册.rar

    C++ 17中,返回自赋值优化(RVO)现在适用于移动赋值操作符,进一步提高了性能。 13. 增强的范围基础迭代器(Range-based for loop enhancements): 可以通过`auto`关键字和解构,更简洁地遍历容器和数组,比如`...

    c/c++笔试题目

    - 移动语义:理解`std::move`和`swap`在资源管理中的应用。 以上是C/C++笔试题目中可能涉及的一些关键知识点,深入理解和熟练掌握这些内容将有助于提高你的编程技能,使你在面试或实际工作中更具竞争力。

    C++11的右值引用的具体使用

    这主要体现在移动构造函数和std::move语义上。 一、右值引用的概念 右值引用是一种特殊的引用类型,它能够引用即将销毁或临时的对象,即右值。在C++中,左值是可以取地址并具有名称的表达式,而右值则相反,通常不...

    多套C++面试题(里面有华为等公司部分面试题)

    - **右值引用**:理解move语义,RVO和NRVO优化。 - **lambda表达式**:函数对象的简洁表示。 - **auto关键字**:类型推断,简化代码。 - **范围for循环**:遍历容器的新方式。 这些是C++面试中常见的知识点,每...

    提高C++性能的编程技术

    通过使用`const`引用、`move`语义、以及RVO(Return Value Optimization)和NRVO(Named Return Value Optimization),可以有效减少临时对象的生命周期,从而提高性能。 线程管理是多线程编程中的核心问题。在C++...

    Cpp-Daily:目的是介绍,共享和讨论现代Cpp功能,最佳实践等。

    移动语义(move semantics)利用右值引用实现,特别是在构造和赋值操作中,可以避免昂贵的深拷贝,转而使用更快速的“移动”操作。这在处理大型对象或复杂数据结构时尤其有效。 **返回值优化(RVO)** 返回值优化...

    move

    4. 标准库中的移动操作:C++标准库提供了std::move函数,它将左值转换为右值引用,表明对象可以被移动但不再安全用于其他操作。std::forward则在模板编程中用于保留原始引用类型。 5. RVO(Return Value ...

    C++名家对话(项目中常见问题解决方案)

    对于性能优化,可以使用各种工具分析瓶颈,例如gprof、Valgrind或现代C++特性如move semantics和RVO(Return Value Optimization)来减少不必要的拷贝。内存管理方面,理解智能指针的工作原理和生命周期管理是至关...

    关于C++学习路线的建议.docx

    - **std::move函数**:学习如何利用std::move实现资源的有效转移。 - **智能指针**:了解auto_ptr、unique_ptr、shared_ptr和weak_ptr,以及它们在内存管理中的角色。 - **循环引用解决方案**:学习如何使用weak_...

    More.Effective.C++简体中文版

    - **实施方法**:合理使用std::move等工具函数。 - **ITEM M21:通过重载避免隐式类型转换** - **问题描述**:隐式类型转换可能导致类型安全问题。 - **解决思路**:通过重载操作符或函数来明确控制转换行为。 ...

    More Effective C++ 简体中文版(pdf 版).pdf

    - 为了避免这种问题,可以考虑使用容器如 std::vector 或 std::array。 - **Item 4:避免无用的缺省构造函数** - 缺省构造函数可能会无意间创建不完整的对象状态。例如,对于包含指针成员的类来说,如果没有显式...

    详解C++ 临时量与临时对象及程序的相关优化

    1. 使用`std::move`和右值引用,以更有效地转移资源所有权。 2. 尽可能使用`const`关键字,以告知编译器某些对象不会被修改,从而可能避免不必要的拷贝。 3. 考虑使用移动构造函数和移动赋值操作符,它们允许更高效...

Global site tag (gtag.js) - Google Analytics