我有这段代码,以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将其转换为右值。我会错过什么吗,还是真的是教程的错误? 顺便说一句,增加举动并不能解决我的第一个问题。
答案 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);
};