加入我们,或者,欢迎回来。
您需要 登录 才可以下载或查看,没有帐号?注册会员  
 
x
 
 本帖最后由 不死鸟之翼 于 2018-3-4 22:05 编辑  
 
今天刷知乎又看到有人在纠结写C++要不要把乘法换成移位之类的,美名其曰“优化”…我比编译器聪明系列 
其实,通常编译器产生的优化机器码比人的自作聪明的产物还要快… 
所以,还是老老实实地用最原始的写法吧 
 
这里大概说说C++函数返回值的事情。 
 
在很多语言比如Java中,在函数中创建一个对象并返回的时候通常是引用语义,也就是返回了局部对象的引用。 
但是C++中直接return一个对象是值语义的,也就是说: 
假设一个巨大的类型叫kls,我们写 
kls get_cls() {         kls k;         return k; } kls x=get_kls(); 
 
 kls get_cls() {  
        kls k;  
        return k;  
}  
kls x=get_kls();  
 
  
的时候,从语法上是将局部变量k复制给了x。显然,如果kls很大的话,复制对象的开销也会很大。 
避免开销的方法当然就是避免无谓的复制。 
理论上C++也是可以返回“引用”的(把返回类型改为kls&),但是由于C++对象生存期的限制,函数结束之后,栈上的k会被销毁 
尝试使用返回的无效的k引用会导致未定义行为 
 
一种选择是把k构造在堆上,然后返回指针kls* get_cls() {         kls* k = new kls;         return k; } 
 
 kls* get_cls() {  
        kls* k = new kls;  
        return k;  
}  
 
  
这样函数结束后,对象会继续存在。但是C++不像Java/Ruby它们有垃圾回收,所以需要手动delete掉返回的对象,否则内存泄漏 
 
为了避免手动管理内存的麻烦,我们可以利用C++的确定性析构,使用带引用计数的智能指针 
shared_ptr<kls> get_cls() {         return make_shared<kls>(); } auto ptr = get_cls(); 
 
 shared_ptr<kls> get_cls() {  
        return make_shared<kls>();  
}  
auto ptr = get_cls();  
 
  
智能指针之间的赋值操作会增加kls对象的引用计数,当指针本身超出生存期后(例如,局部作用域)由于C++的确定性析构,智能指针会被立刻销毁(Java、Ruby等语言是无法控制对象在何时被销毁的,而由垃圾回收控制) 
当没有智能指针指向对象后,它会被自动delete掉 
 
那教练,有没有…不要指针的操作?我喜欢值语义,对象摆在这才安心嘛 
 
有。现代C++多了一种引用,叫右值引用。右值的定义很复杂,但简单地说,它是指计算中产生的临时对象,通常无法用一个实体来指代它(例如c="a"+"b"的运算,字符串"ab"在赋值给c以前是作为右值) 
所以,对于即将消逝的右值(亡值),我们可以在对象复制发生的时候,将它内部持有的资源“偷窃”或者说“转移”到新对象里,这样就避免了巨大的复制开销,而右值将成为一个空壳,最后被销毁。 
 
我们编写以下代码,重载了两个复制构造函数(用来自定义对象复制时的操作),在MSVC的Debug模式下(关闭编译器优化),能够观察到一些输出。 
 
 
0 construction 
 1 copy construction from rvalue ref 0 
 0 destruction 
 1 destruction 
 
 0 construction 
  
1 copy construction from rvalue ref 0 
  
0 destruction 
  
1 destruction  
 
  
get_cls中的临时对象0持有的数据可以在main中的1的构造时被转移,然后0在函数结束后被销毁。最后,main结束后1也被销毁。 
虽然避免了一些复制开销,但是还是要再创建一个对象啊… 
 
然后,我们切换到MSVC的Release模式,允许编译器优化。然后再运行一遍… 
0 construction 
 0 destruction 
 
 0 construction 
  
0 destruction  
 
  
可以看到,编译器应用了“复制消除”(copy elision)优化。整个过程中只有一个对象。 
 
所以,与其想那么多,为什么不放心地交给编译器呢: ) |