引用超载,与单值传递+ std :: move相比?

时间:2010-05-15 06:17:05

标签: c++ c++11 move-semantics rvalue-reference

似乎关于C ++ 0x的rvalues的主要建议是添加移动构造函数并将运算符移动到类中,直到编译器默认实现它们。

但是如果使用VC10,等待是一种失败的策略,因为自动生成可能不会在VC10 SP1之前,或者在最坏的情况下,VC11。可能,等待这个将在几年内测量。

这就是我的问题所在。编写所有这些重复的代码并不好玩。这看起来很不愉快。但对于那些认为很慢的课程来说,这是一个很受欢迎的负担。对于数百甚至数千个较小的班级来说并非如此。

:: sighs :: C ++ 0x应该让我写 less 代码,而不是更多!

然后我有了一个想法。很多人都愿意共享,我猜。

为什么不按价值传递所有内容?不会std :: move + copy elision使这几乎达到最佳状态吗?

示例1 - 典型的Pre-0x构造函数

OurClass::OurClass(const SomeClass& obj) : obj(obj) {}

SomeClass o;
OurClass(o);            // single copy
OurClass(std::move(o)); // single copy
OurClass(SomeClass());  // single copy

缺点: rvalues的浪费副本。

示例2 - 推荐的C ++ 0x?

OurClass::OurClass(const SomeClass& obj) : obj(obj) {}
OurClass::OurClass(SomeClass&& obj) : obj(std::move(obj)) {}

SomeClass o;
OurClass(o);            // single copy
OurClass(std::move(o)); // zero copies, one move
OurClass(SomeClass());  // zero copies, one move

优点: 大概是最快的 缺点: 很多代码!

示例3 - 按值传递+ std :: move

OurClass::OurClass(SomeClass obj) : obj(std::move(obj)) {}

SomeClass o;
OurClass(o);            // single copy, one move
OurClass(std::move(o)); // zero copies, two moves
OurClass(SomeClass());  // zero copies, one move

优点: 无其他代码。
缺点: 案件1和案件中的浪费行动2.如果SomeClass没有移动构造函数,性能将受到很大影响。


你怎么看?它是否正确?与代码减少的好处相比,产生的移动是否是普遍可接受的损失?

1 个答案:

答案 0 :(得分:7)

我对你的问题很感兴趣,因为我对这个话题不熟悉并做了一些研究。 让我介绍一下结果。

首先,你的叹息。

  

:: sighs :: C ++ 0x应该让我写更少的代码,而不是更多!

它还应该让你更好地控制代码。它确实如此。 我会坚持使用额外的构造函数:

OurClass::OurClass(SomeClass&& obj) : obj(std::move(obj)) {}

我个人更喜欢复杂而重要的情况下的冗长,因为它让我和我的代码的可能读者受到警觉。

例如,采用C风格的演员(T*)pT和C ++标准static_cast<T*>(pT) 更冗长 - 但向前迈出了一大步。

其次,我对你的例3,最后一个测试用例有点怀疑。我认为可能有另一个移动构造函数来从rvalue创建pass-by-value参数。所以我在我的新VS2010中创建了一些快速项目,并得到了一些澄清。我将在此处发布代码以及结果。

来源:

// test.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include <utility>
#include <iostream>

class SomeClass{
    mutable int *pVal;
public:
    int Val() const { return *pVal; };
    SomeClass(int val){
        pVal = new int(val);
        std::cout << "SomeClass constructor(pVal = 0x" << std::hex << pVal << std::dec << ")" << std::endl;
    }
    SomeClass(const SomeClass& r){
        pVal = new int(r.Val());
        std::cout << "SomeClass copy constructor(pVal = 0x" << std::hex << pVal << std::dec << ")" << std::endl;
    }   
    SomeClass(const SomeClass&& r){
        pVal = r.pVal;
        r.pVal = 0;
        std::cout << "SomeClass move constructor(pVal = 0x" << std::hex << pVal << std::dec << ")" << std::endl;
    }
    ~SomeClass(){
        if(pVal)
            delete pVal;
        std::cout << "SomeClass destructor(pVal = 0x" << std::hex << pVal << std::dec << ")" << std::endl;
    }
};

class OtherClass{
    SomeClass sc;
public:
    OtherClass(int val):sc(val){
    }

请注意这个部分:

#if 1
    OtherClass(SomeClass r):sc(std::move(r)){
    }
#else
    OtherClass(const SomeClass& r):sc(r){
    }   
    OtherClass(const SomeClass&& r):sc(std::move(r)){
    }
#endif

...

    int Val(){ return sc.Val(); }
    ~OtherClass(){
    }
};

#define ECHO(expr)  std::cout << std::endl << "line " << __LINE__ << ":\t" #expr ":" << std::endl; expr

int _tmain(int argc, _TCHAR* argv[])
{
    volatile int __dummy = 0;
    ECHO(SomeClass o(10));

    ECHO(OtherClass oo1(o));            
    __dummy += oo1.Val();
    ECHO(OtherClass oo2(std::move(o))); 
    __dummy += oo2.Val();
    ECHO(OtherClass oo3(SomeClass(20)));  
    __dummy += oo3.Val();

    ECHO(std::cout << __dummy << std::endl);
    ECHO(return 0);
}

正如您所指出的,有一个编译时开关允许我测试这两种方法。

最好在文本比较模式下查看

The results,在左侧您可以看到#if 1编译,这意味着我们会检查建议的解决方法,右侧是#if 0,意思是我们检查c ++ 0x中描述的“kosher”方式!

怀疑编译器做了蠢事我错了;它在第三个测试用例中保存了额外的移动构造函数。

但说实话,我们必须考虑在建议的解决方法中调用另外两个析构函数,但考虑到如果在被破坏的对象上发生移动则不应执行任何操作,这肯定是一个小缺点。不过,很高兴知道。

无论如何,我并不是说在包装类中编写另一个构造函数更好。这只是几行问题,因为所有繁琐的工作都已在 SomeClass中完成,以便有一个移动构造函数。