#大佬的回答很有意义,指导、分析、解决、拓展,主要学习一下这个斯文#
下面是有一个提问:
问题:
C++为什么纯右值能被延迟析构,将亡值却不行?
我有一段代码:
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
class shape
{
public:
shape() { cout << "shape" << endl; }
virtual ~shape()
{
cout << "~shape" << endl;
}
};
class circle : public shape
{
public:
circle() { cout << "circle" << endl; }
~circle()
{
cout << "~circle" << endl;
}
};
class triangle : public shape
{
public:
triangle() { cout << "triangle" << endl; }
~triangle()
{
cout << "~triangle" << endl;
}
};
class rectangle : public shape
{
public:
rectangle() { cout << "rectangle" << endl; }
~rectangle()
{
cout << "~rectangle" << endl;
}
};
class result
{
public:
result() {
printf("result()\n");
}
~result() {
printf("~result()\n");
}
int a;
};
result process_shape(const shape &shape1, const shape &shape2)
{
printf("process_shape()\n");
return result();
}
int main()
{
result &&r = process_shape(circle(), triangle());
// result &&r = std::move(process_shape(circle(), triangle()));
}
process_shape(circle(), triangle()); 返回是纯右值,运行结果如下:
shape
triangle
shape
circle
process_shape()
result()
~circle
~shape
~triangle
~shape
~result()
可以看到,result被延迟析构了。
但是如果我改成:
result &&r = std::move(process_shape(circle(), triangle()));
std::move(process_shape(circle(), triangle())); 返回将亡值,我运行程序,结果如下:
shape
triangle
shape
circle
process_shape()
result()
~result()
~circle
~shape
~triangle
~shape
result没有被延迟析构。
这是为啥嘞? 是因为纯右值和将亡值内部构造不同吗?
以下是大佬的回答
首先我给楼主几个建议:
- 提问题的时候请尊重回答者的时间,不要问和你的困惑无关的东西。比如你这个例子里的 shape 以及相应的各种类继承就和你的问题无关。你应该去掉无关的代码,使问题本身简明扼要,切合主干。
- C/C++ 的 include 要做到清晰准确。你这个例子里没有一处用到 string 和 cstring,为什么要 include 它们?你用了 std::move,为什么不 #include <utility> ?
- using namespace std 是一个很坏的习惯。但更坏的习惯是,using 了之后还要写 std::,比如你这里的 std::move。
- 代码风格要尽量统一,否则会对读码者造成极大困扰。比如你这里为什么一会 cout 一会又 printf? 你的 constructor 里的 info printing 为啥一会类后面带括号(例如:result() )一会又不带( 例如:shape) ?
- 代码缩进和空行也是体现一个人修养的地方。你函数/类之间都不加空行,为什么要在 main 的最开始加一行空行?还有你的 result 的 constructor/destructor 结尾的右花括号的缩进是怎么回事?另:建议左花括号保持同行,不要换行,节省读 code 人的眼球移动距离。
好心的答主现在开始回答你的问题,首先根据以上几条建议,我们把你的 code 简化如下:
#include <iostream>
#include <utility>
class result {
public:
result() {
std::cout << "result" << std::endl;
}
~result() {
std::cout << "~result" << std::endl;
}
};
int main() {
std::cout << "first line of main" << std::endl;
result&& r = result();
// result&& r = std::move(result());
std::cout << "last line of main" << std::endl;
}
这段 code 会给出这样的 output:
first line of main
result
last line of main
~result
说明 result()
成功地活到了 main 函数结尾。
如果我们更改 main 函数第二/三行的 comment,使其变成:
int main() {
std::cout << "first line of main" << std::endl;
// result&& r = result();
result&& r = std::move(result());
std::cout << "last line of main" << std::endl;
}
那么 output 就会变成:
first line of main
result
~result
last line of main
也就是说 result()
在std::move 那一行结束后就被销毁了。
原因:result&& r = result()
是一个C++ 的语法糖:C++允许把右值(注意,是右值,不是右值引用)绑定给一个右值引用的变量,并延长这个右值的作用域(存活域)为相应的右值引用的作用域(在这个例子里就是到 main 函数结束)。但注意,这个方式是一个仅针对右值 => 右值引用绑定的语法糖,它只对右值绑定有效果。std::move 返回的是一个右值引用,而不是右值,所以这个语法糖不再适用,相应的临时变量 result()
也会在当前语句完成后就地销毁(活不过这一行)。注意,这也会导致右值引用 r
不再指向一个生命周期内的变量 -- 它变成了一个 dangling reference!这是自杀行为,勿尝试。
注:这个例子里如果我们把 result&&
替换成 const result&
也会有同样的效果。本质是因为C++ 的引用绑定的语法糖(来延长临时对象的生命周期)只对右值有效,而对右值引用是无效的。这个argument 对 const 左值引用和 右值引用都是成立的。
题主这个探索精神是很好的,就是下次问问题的时候建议想想我上面说的几点。
个人思考:
答主阅读源代码,指出其中的问题,让我想起来刚入职第一家公司领导给我review代码的场景,一针见血,感慨。
该帖子位置:
作者:hu zhi
链接:https://www.zhihu.com/question/604327959/answer/3055273801
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。