请教关于 C++中类的构造/析构的一些问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
fourstring
V2EX    C++

请教关于 C++中类的构造/析构的一些问题

  •  
  •   fourstring 2019-01-01 17:03:25 +08:00 3730 次点击
    这是一个创建于 2539 天前的主题,其中的信息可能已经有所发展或是发生改变。

    这个问题来源于一道 C++考试题,要求阅读代码写出输出,代码如下:

    #include <iostream> #include <iomanip> using namespace std; class sample { private: int x; public: sample(int val = 0) { x= val; cout << "构造" << x << endl; } sample(const sample &obj) { x = obj.x; cout << "拷贝构造" << x << endl; } ~sample() { cout << "析构" << x << endl; } void operator++() { x++; } friend sample operator+(const sample &a, const sample &b) { sample tmp; tmp.x = a.x + b.x; return tmp; } }; void foo(sample i); int main() { sample s1, s2(1); foo(s1); foo(2); cin.get(); return 0; } void foo(sample i) { static sample s3 = i + 1; ++s3; } 

    问题主要集中在foo函数中

    static sample s3 = i + 1; 

    这一行。当执行到foo(s1)时,我认为函数中关于 s3 的这一句执行顺序是这样的:

    1. 表达式 i+1 中 i 与 1 类型不匹配,由于 sample 的构造函数重载之一 sample(int val=0)没有 explicit 参数,并且 sample 类对+的重载实现要求+的操作数为两个 sample 对象,故编译器使用该构造函数重载将 1 转换为一个临时 sample 对象,这里输出“构造 1 ”
    2. 执行 i 与由 1 转换来的临时对象的加法。在加法函数中声明局部 sample 对象 tmp,输出“构造 0 ”。然后在加法重载函数返回时,由于返回值类项为 sample,因此新建一个临时 sample 对象并将 tmp 的值用于初始化这个临时 sample 对象,输出“拷贝构造 1 ”,此后,局部变量 tmp 被回收,输出“析构 1 ”。
    3. 加法重载函数将上一步中最后生成的临时对象返回到调用处,s3 使用该临时对象初始化,输出“拷贝构造 1 ”。
    4. 最后这一行代码中为了类型转换而生成的临时对象被销毁,输出“析构 1 ”。

    我使用 Visual Studio 2017 编译,Debug x86 编译预设编译运行来检验我的设想,实际输出如下:

    构造 1 构造 0 拷贝构造 1 析构 1 析构 1 

    实际输出和我的设想不同之处在于,输出中没有上述 3.的输出。但是问题在于,进行单步调试后,我观察到在这句代码执行完毕进入++s3时,s3 对象确实已经被创建了,那么 s3 对象是用什么样的方式创建的呢?因为无论如何要创建一个新对象一定要调用某个构造函数,但是我没有能得到任何 s3 构造时产生的输出。

    请问 s3 对象是以怎样的方式被构造的呢?感激不尽!

    17 条回复    2019-01-10 20:58:11 +08:00
    huaouo
        1
    huaouo  
       2019-01-01 17:13:38 +08:00 via Android   1
    返回值优化 RVO?
    fcten
        2
    fcten  
       2019-01-01 17:29:46 +08:00   1
    对象如果是静态局部变量和全局变量,其构造函数调用在执行 main 函之前,析构函数调用在 main 函数结束之后
    fourstring
        3
    fourstring  
    OP
       2019-01-01 17:34:18 +08:00
    @fcten #2 谢谢您,我添加 s3 监视以后发现确实当执行流进入 foo 以后 s3 就已经存在了,但是问题在于我没有得到 s3 构造时的输出。我从监视窗口里看到 s3 是使用了默认构造函数,但是在“拷贝构造 0 ”后并没有“构造 0 ”的输出,这可能是什么原因呢?
    fourstring
        4
    fourstring  
    OP
       2019-01-01 17:35:30 +08:00
    @fcten #2 另外我也尝试过删除 static 限定,依然没有得到 s3 的构造输出。而我把 s3=i+1 改为 s3=i 后,就得到了调用拷贝构造函数的输出。
    fourstring
        5
    fourstring  
    OP
       2019-01-01 17:38:27 +08:00
    @huaouo #1 去查了一下这个优化的概念,但是我觉得应该不是这个优化导致的。我开启单步调试后,观察到“拷贝构造 0 ”这一句输出是在加法重载函数 return 时产生的,也就是说将 tmp 的值用于了初始化临时 sample 对象。
    allanzyne
        6
    allanzyne  
       2019-01-01 17:45:12 +08:00   1
    @fourstring 确实是 RTO 优化。s3 的指针被当作"参数"传给 operator+,在 return 的时候调用拷贝构造
    allanzyne
        7
    allanzyne  
       2019-01-01 17:46:24 +08:00   1
    写错了。。RVO
    fourstring
        8
    fourstring  
    OP
       2019-01-01 17:50:30 +08:00
    @allanzyne #7 非常感谢!不过我还有一个问题就是如果使用 Release 编译预设会进行优化可以理解,但 Debug 预设也会开启编译优化吗?
    allanzyne
        9
    allanzyne  
       2019-01-01 17:55:24 +08:00   1
    @fourstring 大概因为它是一个很基础的优化
    fourstring
        10
    fourstring  
    OP
       2019-01-01 18:01:42 +08:00
    @allanzyne #9 明白了,想问一下哪里可以查到还有什么类似于 RVO 这样“基础”的优化呢?(或者哪些书以及其他途径可以获取这样的信息?)
    fcten
        11
    fcten  
       2019-01-01 18:09:58 +08:00   1
    @fourstring 仔细瞅了一眼,刚才的回复有误,顺序应该是下面这样的。

    构造 0 s1 构造函数
    构造 1 s2 构造函数
    拷贝构造 0 临时对象 1(i)构造函数
    构造 1 临时对象 2(1)构造函数
    构造 0 s3 构造函数
    析构 1 临时对象 2(1)析构函数
    析构 0 临时对象 1(i)析构函数
    构造 2 临时对象 3(i)构造函数br />析构 2 临时对象 3(i)析构函数
    回车
    析构 1 s2 析构函数
    析构 0 s1 析构函数
    析构 3 s3 析构函数
    fcten
        12
    fcten  
       2019-01-01 18:17:01 +08:00   1
    上面提到的 RVO,省略了临时变量 tmp 的构造和析构函数。
    allanzyne
        13
    allanzyne  
       2019-01-01 18:19:12 +08:00 via iPhone   1
    lrxiao
        14
    lrxiao  
       2019-01-02 02:26:45 +08:00   1
    用 gcc 可以-fno-elide-constructors 看下
    因为 copy-elision 允许违背 as-if
    wwqgtxx
        15
    wwqgtxx  
       2019-01-02 12:50:05 +08:00   1
    有个简单的方法,可以在 operator+和 foo 中分别打印一下 tmp 和 s3 的地址就会发现两个的地址是完全一样的
    Nasei
        16
    Nasei  
       2019-01-02 13:01:15 +08:00 via Android
    @fcten c 和 cpp 的局部静态变量应该不太一样,cpp 的并不是 main 之前初始化的


    @fourstring 允许的优化参见 cppreference 中的 copy elision (复制消除) 里的注意部分
    FrankHB
        17
    FrankHB  
       2019-01-10 20:58:11 +08:00   1
    你认为的知识点是所谓的抽象机语义。C++的实现被允许按 as-if rule 做任何保留抽象机语义等价性的变换。但除此之外,还有一些特例允许改变抽象机语义,例如:
    12.8/31 When certain criteria are met, ...
    (算了懒得背了,反正现在也不是 12.8 了。)具体上面都提了,搜 copy elision,具体实现里可能叫 RVO。
    C++14 之前这应该是唯一的 as-if 例外。C++14 还有 global new merging。
    @wwqgtxx 这个就看脸吧,不管是<<还是%p 都是 impl-def,都不保证跟 object identity 有什么关系,更别指望一定是地址了(即便常见实现确实会给你某种意义上的地址)。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3834 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 05:20 PVG 13:20 LAX 21:20 JFK 00:20
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86