【问题标题】:Despite passing an rvalue, why isn't my move constructor getting called?尽管传递了一个右值,为什么我的移动构造函数没有被调用?
【发布时间】:2019-10-02 08:14:26
【问题描述】:

我通过定义默认、复制和移动构造函数来创建一个类字符串。我尝试创建期望调用每个构造函数的对象。正如预期的那样,调用了默认和复制构造函数,但是当我传递一个右值(临时对象)时,我仍然看到移动构造函数没有被调用。

#include <iostream>
#include <cstdlib>
#include <cstring>

class string
{
    char * data;
public:
    string(const char * p = nullptr)
    {
        if(p == nullptr) return;
        std::cout << "string(const char * p)" << std::endl;
        size_t size = strlen(p) + 1;
        data = new char[size];
        memcpy(data, p, size);
    }
    ~string()
    {
        std::cout << "~string() - " << data << std::endl;
        delete[] data;
    }
    string(const string & that)
    {
        std::cout << "string(const string &)" << std::endl;
        size_t size = strlen(that.data) + 1;
        data = new char[size];
        memcpy(data, that.data, size);
    }
    string(string && that)
    {
        std::cout << "string(string && )" << std::endl;
        data = that.data;
        that.data = nullptr;
    }
    void showData() { std::cout << data << std::endl; }
    string operator+(const string & other)
    {
        size_t datasize = strlen(data);
        size_t othersize = strlen(other.data);
        size_t totalsize = datasize + othersize + 1;
        char * sData = new char[totalsize];
        memcpy(sData, data, strlen(data));
        memcpy(sData+datasize, other.data, totalsize-datasize);
        string s(sData);
        delete[] sData;
        return s;

    }
    string & operator=(string that)
    {
        char * tmp = data;
        data = that.data;
        that.data = tmp;
        return *this;
    }
};

int main()
{
    string s1{"stackoverflow"};  // s1
    string s2{s1};               // s2
    string s3{string("stack")+string("exchange")};  // s3
}

s1默认构造函数正在按预期调用。

s2复制构造函数被调用,也如预期的那样。

s3:我正在传递一个临时对象。所以,我希望 move 构造函数 被调用,但是 default 构造函数 被调用。

我无法理解我错过了什么。请帮忙,谢谢。

【问题讨论】:

  • 哪个编译器?我看到它被调用了,但怀疑是 UB
  • 使用 gcc HEAD 10.0.0 20190 和 clang HEAD 9.0.0 从 wandbox.org 编译。
  • 这里 string s3{string("stack")+string("exchange")}; // s3 默认构造函数被调用了 3 次:string("stack")string("exchange")operator+ 中的 string s(sData) 行。正如doctorlove所提到的,由于UB,移动构造函数还没有被执行
  • s3 是允许编译器省略移动构造函数(和一个匹配的析构函数)的情况。搜索“C++ 复制省略”有几篇解释的文章。
  • 与问题无关:如果参数为空指针,默认构造函数需要将data设置为nullptr

标签: c++ c++11 move move-semantics move-constructor


【解决方案1】:

您已将data 定义为char *

string(string && that)
{
    std::cout << "string(string && )" << std::endl;
    data = that.data;
    that.data = nullptr;
}

表示移动构造函数中data 的“副本”不是深层副本。

编辑:现在,这没关系,因为您可以“消化”右值,但是,当它析构时,您可以使用 nullptr

当你打电话时

std::cout << "~string() - " << data << std::endl;

在析构函数中,数据为空,你有未定义的行为。任何事情都有可能发生。

【讨论】:

  • 我相信移动构造函数支持使用/获取参数/参数的内存。浅拷贝的原因也是如此。我认为分配“that.data = nullptr”不会为“that”参数/参数调用析构函数。那么,您在哪里看到未定义的行为?
  • 深/浅复制点可能没有帮助。当that析构函数发生时打印that.data(它是nullptr)虽然是UB。 (在编辑中更明确)
【解决方案2】:

确实,它的行为与您在 GCC 和 Clang 中所说的一样,但移动构造函数是使用 VC++ 调用的。看起来像一个错误,那么您应该使用 std::move() 显式移动。

【讨论】:

  • 不是错误(即使在析构函数中 nullptr 的打印是固定的):这是编译器可以选择省略移动构造函数的情况。
猜你喜欢
  • 2018-02-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-09
  • 1970-01-01
相关资源
最近更新 更多