调用复制构造函数而不是移动构造函数-为什么?

时间:2018-11-07 20:13:34

标签: c++ move

我有这段代码,以http://www.cplusplus.com/doc/tutorial/classes2/的方式从这里获取

// move constructor/assignment
#include <iostream>
#include <string>
#include <utility>
using namespace std;

class Example6
{
    string* ptr;
public:
    Example6(const string& str) :
            ptr(new string(str))
    {
        cout << "DONT   MOVE " << '\n';
    }
    ~Example6()
    {
        delete ptr;
    }
// move constructor
    Example6(Example6&& x) :
            ptr(x.ptr)
    {
        cout << "MOVE " << '\n';
        x.ptr = nullptr;
    }
// move assignment
    Example6& operator=(Example6&& x)
    {
        delete ptr;
        ptr = x.ptr;
        x.ptr = nullptr;
        return *this;
    }
// access content:
    const string& content() const
    {
        return *ptr;
    }
// addition:
    Example6 operator+(const Example6& rhs)
    {
        return Example6(content() + rhs.content());
    }
};

int main()
{
    Example6 foo("Exam");
    Example6 bar = Example6("ple"); // move-construction

    foo = foo + bar; // move-assignment

    cout << "foo's content: " << foo.content() << '\n';
    return 0;
}

我只在构造函数中添加了输出,以查看正在调用哪个。令我惊讶的是,它始终是第一个复制构造函数。为什么会发生?我做了一些研究,发现了一些关于省略的信息。是否有可能阻止它并始终调用move构造函数?

此外,正如我所说的,这段代码来自cplusplus.com。但是,我在其他地方读到了move语义,我想知道这里的move构造函数是否正确完成。不应该打电话

  ptr(move(x.ptr))

而不是

ptr(x.ptr)

我的理解方式是,如果我们使用第二个选项,那么我们将调用字符串的拷贝构造函数,而不是move,因为x是具有名称的右值引用,所以它实际上是左值,我们需要使用move将其转换为右值。我会错过什么吗,还是真的是教程的错误? 顺便说一句,增加举动并不能解决我的第一个问题。

3 个答案:

答案 0 :(得分:2)

所以任何带有名称的东西都是左值。

带有名称的右值引用是左值。

右值引用将绑定到右值,但它本身是左值。

因此x中的ptr(x.ptr)是一个右值引用,但是它有一个名称,所以它是一个左值。

要将其视为右值,您需要执行ptr( std::move(x).ptr )

当然,这几乎没有用,因为移动ptr不会执行任何操作,因为ptr是愚蠢的原始指针。

您应该在此处遵循0规则。

class Example6 {
    std::unique_ptr<string> ptr;
  public:
    Example6 (string str) : ptr(std::make_unique<string>(std::move(str))) {cout << "DONT   MOVE " << '\n';}
    Example6():Example6("") {}
    ~Example6 () = default;
    // move constructor
    Example6 (Example6&& x) = default;
    // move assignment
    Example6& operator= (Example6&& x) = default;
    // access content:
    const string& content() const {
       if (!ptr) *this=Example6{};
       return *ptr;
    }
    // addition:
    Example6 operator+(const Example6& rhs) {
      return Example6(content()+rhs.content());
    }
};

因为业务逻辑和生命周期管理不属于同一类。

我们在这里:

    // addition:
    Example6& operator+=(const Example6& rhs) & {
      if (!ptr) *this = Example6{};
      *ptr += rhs.content();
      return *this;
    }
    // addition:
    friend Example6 operator+(Example6 lhs, const Example6& rhs) {
      lhs += rhs;
      return lhs;
    }

答案 1 :(得分:1)

  

复制构造函数称为...-为什么?

您的问题的前提是错误的:未调用复制构造函数。实际上,该类不可复制。

第一个构造函数是std::string的转换构造函数。之所以调用转换构造函数,是因为Example6对象是使用字符串参数初始化的。在这些表达式中的每个表达式中都有一次:

  • Example6 foo("Exam")
  • Example6("ple")
  • Example6(content() + rhs.content()
  

...而不是移动构造函数

在程序中有一些通过移动进行的复制初始化。但是,所有这些都可以被编译器消除。

  

是否可以通过某种方式阻止它并始终调用move构造函数?

有一些错误可以防止复制省略。例如,如果您这样编写加法运算符:

return std::move(Example6(content()+rhs.content()));

如果幸运的话,编译器将无法避免这一步,并且可能会告诉您有关情况:

warning: moving a temporary object prevents copy elision

  

不是吗

ptr(move(x.ptr))
     

不只是

ptr(x.ptr)

没有必要。移动指针与复制指针完全相同。所有基本类型都一样。

  

我的理解方式是,如果我们使用第二个选项,那么我们将调用字符串的副本构造函数,而不是移动

ptr不是字符串。它是指向字符串的指针。复制指针对指向的对象没有任何作用。


PS。示例程序的质量很差。在C ++中永远不会拥有裸指针。

答案 2 :(得分:0)

我可以说您的类没有复制构造函数。 因为复制ctor参数必须为const并引用

class Example6{ 
public:
    Example6(const Example6 &r);
};