此问题与以下所有问题均不同:
extraneous calls to copy-constructor and destructor(与相关,但在我的案例中未使用STL)
[为什么类的析构函数被调用两次?](Why is the destructor of the class called twice?)(无关)
由堆栈溢出建议。
假设您有这个简单的代码。
#include <iostream>
using namespace std;
class Int {
private:
int i_;
public:
Int(Int &&obj) : i_(obj.i_) { //move constructor
print_address_change_(&obj);
cout << i_ << " moved.\n";
}
Int(const Int &obj) : i_(obj.i_) { //copy constructor
print_address_change_(&obj);
cout << i_ << " copied.\n";
}
Int(int i) : i_(i) {
print_address_();
cout << i_ << " constructed.\n";
}
~Int() {
print_address_();
cout << i_ << " destructed.\n";
}
void print_address_() const {
cout << "(" << this << ") ";
}
void print_address_change_(const Int *p) const {
cout << "(" << p << " -> " << this << ") ";
}
const Int operator * (const Int &rhs) const {
return Int(i_ * rhs.i_);
}
};
int main() {
Int i(3);
Int j(8);
cout << "---\n";
Int k = i * j;
cout << "---\n";
}
结果(通过带有默认选项的g ++ 7.3.0)是这样。
(0x7ffd8e8d11bc) 3 constructed. //i
(0x7ffd8e8d11c0) 8 constructed. //j
---
(0x7ffd8e8d11c4) 24 constructed. //tmp
---
(0x7ffd8e8d11c4) 24 destructed. //k
(0x7ffd8e8d11c0) 8 destructed. //j
(0x7ffd8e8d11bc) 3 destructed. //i
好。有点奇怪,但是您可以说copy elision必须已经发生。因此,现在使用-fno-elide-constructors
选项,您将获得以下结果。
(0x7ffd8f7693f8) 3 constructed. //i
(0x7ffd8f7693fc) 8 constructed. //j
---
(0x7ffd8f7693c4) 24 constructed. //tmp
(0x7ffd8f7693c4 -> 0x7ffd8f769404) 24 moved. //tmp -> ??? (WHY?)
(0x7ffd8f7693c4) 24 destructed. //tmp
(0x7ffd8f769404 -> 0x7ffd8f769400) 24 copied. //??? -> k (WHY?)
(0x7ffd8f769404) 24 destructed. //??? (WHY?)
---
(0x7ffd8f769400) 24 destructed. //k
(0x7ffd8f7693fc) 8 destructed. //j
(0x7ffd8f7693f8) 3 destructed. //i
这比我预期的多了三行(标记为“为什么”)。什么是???
?谁能告诉我那里发生了什么事?
答案 0 :(得分:3)
operator*
用Int(i_ * rhs.i_)
构造一个临时对象。它返回该对象,该对象在函数外部构造了另一个临时对象。该临时对象将被复制到k
中。
答案 1 :(得分:1)
写时:
Int k = i * j;
您实际上正在做类似的事情:
Int tmp1(i.operator*(j));
Int k(tmp1);
也就是说,使用=
进行初始化实际上是通过创建临时文件并进行复制构造来使用的。与直接初始化的Int k(i * j)
或Int k{i *j}
进行比较。
在这种情况下,这并不是真的很重要,因为即使您编写Int k(i*j)
,在调用operator*
的副本构造函数之前,仍需要一个临时文件来保存k
的返回值。
operator*
等效于:
Int tmp2(24);
return std::move(tmp2);
所以您的输出表示:
(0x7ffd8f7693f8) 3 constructed. //i
(0x7ffd8f7693fc) 8 constructed. //j
---
(0x7ffd8f7693c4) 24 constructed. //tmp2 inside operator*
(0x7ffd8f7693c4 -> 0x7ffd8f769404) 24 moved. //return value into tmp1
(0x7ffd8f7693c4) 24 destructed. //tmp2
(0x7ffd8f769404 -> 0x7ffd8f769400) 24 copied. //k = tmp1
(0x7ffd8f769404) 24 destructed. //tmp1
---
(0x7ffd8f769400) 24 destructed. //k
(0x7ffd8f7693fc) 8 destructed. //j
(0x7ffd8f7693f8) 3 destructed. //i