【问题标题】:Overload on reference, versus sole pass-by-value + std::move?参考重载,而不是唯一的按值传递+ std::move?
【发布时间】:2011-02-19 19:32:36
【问题描述】:

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

但是,如果您使用 VC10,等待是一种失败的策略,因为自动生成可能要到 VC10 SP1,或者在最坏的情况下,VC11 才会出现。很可能,等待的时间会以年为单位。

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

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

然后我有了一个想法。我猜是很多人分享的。

为什么不按值传递所有内容? std::move + 复制省略不会使这几乎是最优的吗?

示例 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

缺点:浪费了右值的副本。

示例 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 没有移动构造函数,性能会受到很大影响。


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

【问题讨论】:

  • 你是 not the first 想到这个 ;-)
  • @dean 如果我知道参数有一个移动构造函数,我将在我的代码中执行按值然后移动。如果我不知道,我会做重载右值引用的舞蹈。
  • @FredOverflow:我读过那篇文章。我喜欢它,但它在某些部分是错误的(“在最坏的情况下,性能不会更差”)而且对我的口味来说太理论化了——没有任何类型的测量。无论如何,我的帖子并不是要提出新的建议,而是要问一个问题:您更喜欢哪个?显然这取决于任务和人,但我对回应很感兴趣。 (顺便说一句,我现在想知道编译器是否可以消除 #3 中的额外分配。)
  • @dean 查看 cmets 中对此答案的讨论:stackoverflow.com/questions/2794369/…
  • @dean 我不认为说“在最坏的情况下,性能不会更差”是错误的。完全正确。如果以后还是要复制,可以省略,直接修改参数。我不认为该声明适用于这种复制到成员的情况,在某些情况下,性能确实会差一些。

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


【解决方案1】:

我对您的问题很感兴趣,因为我是该主题的新手并且做了一些研究。 让我介绍一下结果。

首先,你的叹息。

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

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

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

我个人更喜欢在复杂和重要的情况下使用冗长,因为它让我和我的代码的可能读者保持警惕。

以 C 风格转换 (T*)pT 和 C++ 标准 static_cast<T*>(pT) 为例 更加冗长 - 但向前迈出了一大步。

其次,我对您的示例 3(最后一个测试用例)有点怀疑。我认为可能涉及另一个移动构造函数来从右值创建按值传递的参数。所以我在我的新 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,意味着我们检查“kosher” " c++0x 中描述的方式!

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

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

在任何情况下,我都不想在包装类中编写另一个构造函数更好。这只是几行的问题,因为所有繁琐的工作已经在SomeClass 中完成,必须有一个移动构造函数。

【讨论】:

  • +1 表示对显式代码价值的努力和观点。
  • 额外的移动构造函数并不是一个真正的问题。只要您避免不必要的复制构造函数,事情就会非常好。 SomeClass(const SomeClass&amp;&amp; r) 不应该编译...
  • __dummy 应该是 _dummy 因为有两个下划线 >o
猜你喜欢
  • 1970-01-01
  • 2019-01-13
  • 1970-01-01
  • 1970-01-01
  • 2014-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多