【问题标题】:Are Move Constructors 'always' more efficient than Copy Constructors?移动构造函数“总是”比复制构造函数更有效吗?
【发布时间】:2021-01-08 23:27:54
【问题描述】:

我一直在研究与 Copy 构造函数相比的 Move 构造函数。从我在 stackoverflow 和其他信息来源中阅读的内容来看,与如何“调用”它们更相关,但我无法找到一个明确的答案,即它们是否“总是”比复制构造函数更有效,也许我只是在寻找错误的东西。

无论如何,我问的原因是因为以下场景会使移动构造函数比复制构造函数更高效:

class Test {

    std::string *name;
    std::vector<std::string> *friends;

public:
    Test() : name{nullptr}, friends{nullptr} {} // If not initialized they may be pointing to random memory
    Test(std::string name, std::vector<std::string> friends) : name{nullptr}, friends{nullptr} {
        this->name = new std::string{std::move(name)}; // Is the std::move necessary, or is 'name' still considered an r-value even though it has a variable name now (if it had been passed as an r-value to the constructor in the first place)
        this->friends = new std::vector<std::string>{std::move(friends)}; // Is the std::move necessary, or is 'friends' still considered an r-value even though it has now got a variable name (if it had been passed as an r-value to the constructor in the first place)
    }
    
    // Copy constructor
    Test(const Test &source) : name{nullptr}, friends{nullptr} {
        name = new std::string{*source.name};
        friends = new std::vector<std::string>{*source.friends};
    }

    // Move constructor
    Test(Test &&source) noexcept : name{source.name}, friends{source.friends} {
        source.name = nullptr;
        source.friends = nullptr;
    }

    // Destructor
    ~Test() {
        delete name;
        delete friends;
    }

};

从这里开始,或者事实上在我的帖子中的任何时候,如果我陈述不正确,请纠正我。

据我了解,移动构造函数肯定会更有效,尤其是当向量和字符串大小变大时,因为我们正在复制内存地址而不是按值复制对象。如果我能获得关于我在代码中留下的两个 cmets/问题、关于 std::move 的反馈以及在那种情况下是否有必要,我会在两个类似的情况下解释我的思考过程,这将是非常有帮助的cmets/问题。

现在这是我的问题出现的地方......因为当我没有任何指向任何东西的属性/成员变量时,我不确定,或者事实上我对移动构造函数的事实更加宽容不会比复制构造函数快,但我需要一些帮助来确认这一点。

以下是类似的类,但被重构为适合其包含的属性/成员变量的代码:

class Test {

    std::string name;
    std::vector<std::string> friends;

public:
    Test() : name{}, friends{} {} // 'name' is initialzied to "" and 'friends' I am not quite sure what it is initialized to, maybe just an empty vector, but I'm not sure
    Test(std::string name, std::vector<std::string> friends) : name{name}, friends{friends} {} // No need for any code inside the code block --- is that what it's called? Or just code brackets? Or what? lol

    // Copy constructor
    Test(const Test &source) : name{source.name}, friends{source.friends} {} // Again, no need for any extra code in the -- whatever it's called --

    // Move constructor
    Test(Test &&source) noexcept : name{source.name}, friends{source.friends} {}

    // Destructor
    ~Test() {} // No need to define one our selves but I just left it like this anyways, just removed the 'delete' statements

};

当我查看上面的代码时,无论如何我都想不到 Move 构造函数可能比复制 Copy Constructor 更快或更高效。在这种情况下,我无论如何都想不出让 Move Constructor 更快/更高效。

问题可以回答除了上面已经提出的一些问题,无论是在代码中还是在其中一个段落中,如果代码或段落中的cmets也有回答:

  1. 我的两个类定义都正确吗?如果不正确,哪一个/(些)是/不正确的,为什么?
  2. 有没有办法让第二类 Test 定义的移动构造函数更快?
  3. 我的思维过程是否与正确答案相符?换句话说,移动构造函数是否受到限制,并且与 Test 类的第二个定义中的复制构造函数相比不会更快?

【问题讨论】:

  • 第二个 sn-p 中的移动构造函数不会移动任何东西。你只是把它变成了另一个复制构造函数。您必须将std::move 的成员source 转换为新构造对象的成员
  • 案例二的移动构造函数应该是Test(Test &amp;&amp;source) noexcept : name{std::move(source.name)}, friends{std::move(source.friends)} { },否则它会复制两个成员。编辑:实际上,您不应该为情况二定义任何特殊成员,编译器生成的那些本来是正确的。见Rule of 3/5/0
  • @UnholySheep OHHH,字符串类会用它自己的移动构造函数来处理它吗?
  • @Krapnix 是的,当您使用std::move 时,您允许使用成员类型自己的移动构造函数而不是其复制构造函数。
  • @Krapnix 是的。编译器生成的移动构造函数将尝试从移动实例的成员中移动构造每个成员。编辑:请参阅上面提供的链接。如果您的类管理像原始拥有指针这样的资源并且需要做一些特殊的事情,您只需要定义构造函数、移动赋值运算符和析构函数。大多数时候,情况并非如此。

标签: c++


【解决方案1】:

移动构造函数“总是”比复制构造函数更有效吗?

两者都可以有自定义实现,因此“邪恶”类可能会做出低效的移动和正确的复制。

对于常规课程,move 应该比复制更有效或等效。

当我查看上面的代码时,无论如何我都想不到 Move 构造函数可能比复制 Copy Constructor 更快或更高效。在这种情况下,我无论如何都想不出让 Move Constructor 更快/更高效。

您的移动代码确实.. 一个副本,应该是:

class Test {

    std::string name;
    std::vector<std::string> friends;

    Test() : name{}, friends{} {} // empty string, empty vector.

    Test(std::string name, std::vector<std::string> friends) : name{std::move(name)}, friends{std::move(friends)} {}
//                                                                  ^^^^^^^^^^                ^^^^^^^^
    // Copy constructor
    Test(const Test &source) : name{source.name}, friends{source.friends} {}
    // or simply Test(const Test &) = default;

    // Move constructor
    Test(Test &&source) noexcept : name{std::move(source.name)}, friends{std::move(source.friends)} {}
    //                                  ^^^^^^^^^                        ^^^^^^^^
    // or simply Test(Test &&) = default;

    // Destructor
    ~Test() = default;
};

我的两个类定义都正确吗?如果不正确,哪一个/(些)是/不正确的,为什么?

在 std::string/vector 上使用指针很奇怪,但“正确”。您错过了 operator= 的实施以尊重 5 规则。

没有指针的版本会复制而不是移动,但行为正确。

有没有办法让第二类测试定义的移动构造函数更快?

是的,通过移动成员而不是复制,见上文。

我的思考过程是否与正确答案相符?换句话说,Move Constructor 是有限的,与 Test 类的第二个定义中的 Copy Constructor 相比不会更快?

你的拷贝构造函数和移动构造函数都做同样的事情:一个拷贝,所以应该是等价的。区别在于(错误的)noexcept。所以如果在复制过程中抛出异常,move会调用std::terminate

【讨论】:

    猜你喜欢
    • 2017-06-11
    • 1970-01-01
    • 1970-01-01
    • 2022-11-21
    • 2023-03-20
    • 1970-01-01
    • 1970-01-01
    • 2013-03-27
    • 2021-12-25
    相关资源
    最近更新 更多