移动构造函数和移动赋值运算符与复制省略
Move constructor and move assignment operator vs. copy elision
相关问题:
- Why this move constructor is not called wtih rvalue temporary? [duplicate]
我发布这个问题是因为移动语义这个东西真的让我感到困惑。起初它们对我来说似乎很清楚,但是当我试图向自己演示它们的用法时,我意识到也许我误解了什么。
我试图将以下文件安排为一个不那么简单的类似向量的实现 class 利用移动语义(实际上 main
函数在那里好吧,还有一个免费功能,可以更轻松地打印到屏幕上,...)。这并不是一个真正的 最小 工作示例,但它向屏幕产生的输出是相当可读的,恕我直言。
不过,如果您觉得瘦身更好,请建议我该怎么做。
反正代码如下,
#include<iostream>
using namespace std;
int counter = 0; // to keep count of the created objects
class X {
private:
int id = 0; // hopefully unique identifyier
int n = 0;
int * p;
public:
// special member functions (ctor, dtor, ...)
X() : id(counter++), n(0), p(NULL) { cout << "default ctor (id " << id << ")\n"; }
X(int n) : id(counter++), n(n), p(new int[n]) { cout << "param ctor (id " << id << ")\n"; };
X(const X& x) : id(counter++), n(x.n), p(new int[n]) {
cout << "copy ctor (id " << id << ") (allocating and copying " << n << " ints)\n";
for (int i = 0; i < n; ++i) {
p[i] = x.p[i];
}
};
X(X&& x) : id(counter++), n(x.n), p(x.p) {
cout << "move ctor (id " << id << ")\n";
x.p = NULL;
x.n = 0;
};
X& operator=(const X& x) {
cout << "copy assignment (";
if (n < x.size() && n > 0) {
cout << "deleting, ";
delete [] p;
n = 0;
}
if (n == 0) {
cout << "allocating, and ";
p = new int[n];
}
n = x.size();
cout << "copying " << n << " values)";
for (int i = 0; i < n; ++i) {
p[i] = x.p[i];
}
cout << endl;
return *this;
};
X& operator=(X&& x) {
this->n = x.n;
this->p = x.p;
x.p = NULL;
x.n = 0;
cout << "move assignment (\"moving\" " << this->n << " values)\n";
return *this;
};
~X() {
cout << "dtor on id " << id << " (array of size " << n << ": " << *this << ")\n";
delete [] p;
n = 0;
}
// getters/setters
int size() const { return n; }
// operators
int& operator[](int i) const { return p[i]; };
X operator+(const X& x2) const {
cout << "operator+\n";
int n = min(x2.size(), this->size());
X t(n);
for (int i = 0; i < n; ++i) {
t.p[i] = this->p[i] + x2.p[i];
}
return t;
};
// friend function to slim down the cout lines
friend ostream& operator<<(ostream&, const X&);
};
int main() {
X x0;
X x1(5);
X x2(5);
x1[2] = 3;
x2[3] = 4;
cout << "\nx0 = x1 + x2;\n";
x0 = x1 + x2;
cout << "\nX x4(x1 + x2);\n";
X x4(x1 + x2);
cout << x4 << endl;
cout << '\n';
}
// function to slim down the cout lines
ostream& operator<<(ostream& os, const X& x) {
os << '[';
for (int i = 0; i < x.size() - 1; ++i) {
os << x.p[i] << ',';
}
if (x.size() > 0) {
os << x.p[x.size() - 1];
}
return os << ']';
}
当我用
编译并运行它时
$ clear && g++ moves.cpp && ./a.out
输出如下(#
-注释是手工添加的)
default ctor (id 0)
param ctor (id 1)
param ctor (id 2)
x0 = x1 + x2;
operator+
param ctor (id 3)
move assignment ("moving" 5 values)
dtor on id 3 (array of size 0: [])
X x4(x1 + x2);
operator+
param ctor (id 4)
[0,0,3,4,0]
dtor on id 4 (array of size 5: [0,0,3,4,0])
dtor on id 2 (array of size 5: [0,0,0,4,0])
dtor on id 1 (array of size 5: [0,0,3,0,0])
dtor on id 0 (array of size 5: [0,0,3,4,0])
从输出的第一部分来看,我想我确实展示了移动赋值运算符的预期用途。在这方面我是对的吗? (从下一个输出来看,我好像不是,但我不确定。)
在这一点上,如果我关于复制省略阻止了对复制构造器的调用的推论是正确的,那么一个问题对我来说很自然 (and not only me, see OP's comment here):
不就是根据另一个临时对象创建对象的情况吗(例如 x4
根据[=18=的结果] in X x4(x1 + x2);
) 正是应该引入移动语义的那个?如果不是,显示移动构造函数用法的基本示例是什么?
然后我读到可以通过添加适当的选项来防止复制省略。
的输出
clear && g++ -fno-elide-constructors moves.cpp && ./a.out
然而,是下面的:
default ctor (id 0)
param ctor (id 1)
param ctor (id 2)
x0 = x1 + x2;
operator+
param ctor (id 3)
move ctor (id 4)
dtor on id 3 (array of size 0: [])
move assignment ("moving" 5 values)
dtor on id 4 (array of size 0: [])
X x4(x1 + x2);
operator+
param ctor (id 5)
move ctor (id 6)
dtor on id 5 (array of size 0: [])
move ctor (id 7)
dtor on id 6 (array of size 0: [])
[0,0,3,4,0]
dtor on id 7 (array of size 5: [0,0,3,4,0])
dtor on id 2 (array of size 5: [0,0,0,4,0])
dtor on id 1 (array of size 5: [0,0,3,0,0])
dtor on id 0 (array of size 5: [0,0,3,4,0])
+enrico:CSGuild$
我期望的对 move ctor 的调用现在似乎在那里,但在该调用和对 move 赋值的调用之前分别调用了另一个 move ctor。
为什么会这样?我是否完全误解了移动语义的含义?
您似乎有两个问题:
- 为什么
X x4(x1 + x2)
没有调用移动构造函数?
- 为什么在禁用复制省略时,移动构造函数被调用了两次?
第一题
Isn't that situation (X x4(x1 + x2);) exactly the one for which move
semantics where supposed to be introduced?
嗯,不。为了使用移动语义,您有效地建议我们应该选择 在 operator+
中构造一个 X
,然后 移动 到结果 x4
,与 copy-elided 相比,在 operator+
[ 期间构建最终结果 (x4
) 显然效率低下=61=].
第二题
已禁用 copy-elision,为什么我们在 X x4(x1 + x2)
期间看到对移动构造函数的两次调用?考虑到这里有三个作用域:
operator+
作用域,我们构造一个X
和return它;
- 我们调用的
main
作用域 X x4(x1 + x2)
;
- 我们从
x1 + x2
; 构建 X
的 X constructor
那么,在没有省略的情况下,编译器是:
- 将结果从
operator+
移动到 main
(进入 x1 + x2
);和
- 将
x1 + x2
的内容移动到 x4
。
相关问题:
- Why this move constructor is not called wtih rvalue temporary? [duplicate]
我发布这个问题是因为移动语义这个东西真的让我感到困惑。起初它们对我来说似乎很清楚,但是当我试图向自己演示它们的用法时,我意识到也许我误解了什么。
我试图将以下文件安排为一个不那么简单的类似向量的实现 class 利用移动语义(实际上 main
函数在那里好吧,还有一个免费功能,可以更轻松地打印到屏幕上,...)。这并不是一个真正的 最小 工作示例,但它向屏幕产生的输出是相当可读的,恕我直言。
不过,如果您觉得瘦身更好,请建议我该怎么做。
反正代码如下,
#include<iostream>
using namespace std;
int counter = 0; // to keep count of the created objects
class X {
private:
int id = 0; // hopefully unique identifyier
int n = 0;
int * p;
public:
// special member functions (ctor, dtor, ...)
X() : id(counter++), n(0), p(NULL) { cout << "default ctor (id " << id << ")\n"; }
X(int n) : id(counter++), n(n), p(new int[n]) { cout << "param ctor (id " << id << ")\n"; };
X(const X& x) : id(counter++), n(x.n), p(new int[n]) {
cout << "copy ctor (id " << id << ") (allocating and copying " << n << " ints)\n";
for (int i = 0; i < n; ++i) {
p[i] = x.p[i];
}
};
X(X&& x) : id(counter++), n(x.n), p(x.p) {
cout << "move ctor (id " << id << ")\n";
x.p = NULL;
x.n = 0;
};
X& operator=(const X& x) {
cout << "copy assignment (";
if (n < x.size() && n > 0) {
cout << "deleting, ";
delete [] p;
n = 0;
}
if (n == 0) {
cout << "allocating, and ";
p = new int[n];
}
n = x.size();
cout << "copying " << n << " values)";
for (int i = 0; i < n; ++i) {
p[i] = x.p[i];
}
cout << endl;
return *this;
};
X& operator=(X&& x) {
this->n = x.n;
this->p = x.p;
x.p = NULL;
x.n = 0;
cout << "move assignment (\"moving\" " << this->n << " values)\n";
return *this;
};
~X() {
cout << "dtor on id " << id << " (array of size " << n << ": " << *this << ")\n";
delete [] p;
n = 0;
}
// getters/setters
int size() const { return n; }
// operators
int& operator[](int i) const { return p[i]; };
X operator+(const X& x2) const {
cout << "operator+\n";
int n = min(x2.size(), this->size());
X t(n);
for (int i = 0; i < n; ++i) {
t.p[i] = this->p[i] + x2.p[i];
}
return t;
};
// friend function to slim down the cout lines
friend ostream& operator<<(ostream&, const X&);
};
int main() {
X x0;
X x1(5);
X x2(5);
x1[2] = 3;
x2[3] = 4;
cout << "\nx0 = x1 + x2;\n";
x0 = x1 + x2;
cout << "\nX x4(x1 + x2);\n";
X x4(x1 + x2);
cout << x4 << endl;
cout << '\n';
}
// function to slim down the cout lines
ostream& operator<<(ostream& os, const X& x) {
os << '[';
for (int i = 0; i < x.size() - 1; ++i) {
os << x.p[i] << ',';
}
if (x.size() > 0) {
os << x.p[x.size() - 1];
}
return os << ']';
}
当我用
编译并运行它时$ clear && g++ moves.cpp && ./a.out
输出如下(#
-注释是手工添加的)
default ctor (id 0)
param ctor (id 1)
param ctor (id 2)
x0 = x1 + x2;
operator+
param ctor (id 3)
move assignment ("moving" 5 values)
dtor on id 3 (array of size 0: [])
X x4(x1 + x2);
operator+
param ctor (id 4)
[0,0,3,4,0]
dtor on id 4 (array of size 5: [0,0,3,4,0])
dtor on id 2 (array of size 5: [0,0,0,4,0])
dtor on id 1 (array of size 5: [0,0,3,0,0])
dtor on id 0 (array of size 5: [0,0,3,4,0])
从输出的第一部分来看,我想我确实展示了移动赋值运算符的预期用途。在这方面我是对的吗? (从下一个输出来看,我好像不是,但我不确定。)
在这一点上,如果我关于复制省略阻止了对复制构造器的调用的推论是正确的,那么一个问题对我来说很自然 (and not only me, see OP's comment here):
不就是根据另一个临时对象创建对象的情况吗(例如 x4
根据[=18=的结果] in X x4(x1 + x2);
) 正是应该引入移动语义的那个?如果不是,显示移动构造函数用法的基本示例是什么?
然后我读到可以通过添加适当的选项来防止复制省略。
的输出clear && g++ -fno-elide-constructors moves.cpp && ./a.out
然而,是下面的:
default ctor (id 0)
param ctor (id 1)
param ctor (id 2)
x0 = x1 + x2;
operator+
param ctor (id 3)
move ctor (id 4)
dtor on id 3 (array of size 0: [])
move assignment ("moving" 5 values)
dtor on id 4 (array of size 0: [])
X x4(x1 + x2);
operator+
param ctor (id 5)
move ctor (id 6)
dtor on id 5 (array of size 0: [])
move ctor (id 7)
dtor on id 6 (array of size 0: [])
[0,0,3,4,0]
dtor on id 7 (array of size 5: [0,0,3,4,0])
dtor on id 2 (array of size 5: [0,0,0,4,0])
dtor on id 1 (array of size 5: [0,0,3,0,0])
dtor on id 0 (array of size 5: [0,0,3,4,0])
+enrico:CSGuild$
我期望的对 move ctor 的调用现在似乎在那里,但在该调用和对 move 赋值的调用之前分别调用了另一个 move ctor。
为什么会这样?我是否完全误解了移动语义的含义?
您似乎有两个问题:
- 为什么
X x4(x1 + x2)
没有调用移动构造函数? - 为什么在禁用复制省略时,移动构造函数被调用了两次?
第一题
Isn't that situation (X x4(x1 + x2);) exactly the one for which move semantics where supposed to be introduced?
嗯,不。为了使用移动语义,您有效地建议我们应该选择 在 operator+
中构造一个 X
,然后 移动 到结果 x4
,与 copy-elided 相比,在 operator+
[ 期间构建最终结果 (x4
) 显然效率低下=61=].
第二题
已禁用 copy-elision,为什么我们在 X x4(x1 + x2)
期间看到对移动构造函数的两次调用?考虑到这里有三个作用域:
operator+
作用域,我们构造一个X
和return它;- 我们调用的
main
作用域X x4(x1 + x2)
; - 我们从
x1 + x2
; 构建
X
的 X constructor
那么,在没有省略的情况下,编译器是:
- 将结果从
operator+
移动到main
(进入x1 + x2
);和 - 将
x1 + x2
的内容移动到x4
。