知乎里面看到的一个问题及回答,记录一下

#大佬的回答很有意义,指导、分析、解决、拓展,主要学习一下这个斯文#

下面是有一个提问:

问题:

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没有被延迟析构。

这是为啥嘞? 是因为纯右值和将亡值内部构造不同吗?


以下是大佬的回答

首先我给楼主几个建议:

  1. 提问题的时候请尊重回答者的时间,不要问和你的困惑无关的东西。比如你这个例子里的 shape 以及相应的各种类继承就和你的问题无关。你应该去掉无关的代码,使问题本身简明扼要,切合主干。
  2. C/C++ 的 include 要做到清晰准确。你这个例子里没有一处用到 string 和 cstring,为什么要 include 它们?你用了 std::move,为什么不 #include <utility> ?
  3. using namespace std 是一个很坏的习惯。但更坏的习惯是,using 了之后还要写 std::,比如你这里的 std::move。
  4. 代码风格要尽量统一,否则会对读码者造成极大困扰。比如你这里为什么一会 cout 一会又 printf? 你的 constructor 里的 info printing 为啥一会类后面带括号(例如:result() )一会又不带( 例如:shape) ?
  5. 代码缩进和空行也是体现一个人修养的地方。你函数/类之间都不加空行,为什么要在 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
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值