考察以下程序片段
执行结果
代码解析
1) 先看GetString1。这个函数,是内部定义了一个CString对象,其对象是一个栈变量,虽然我们在函数内部对这个CString对象进行了设置,但是,一旦函数GetString1调用完毕,这个CString对象也随即被销毁,所以,这里返回的是类型为LPCTSTR的字符串指针,实际上这个指针指向了一片被销毁了的栈空间。
2) 当我们使用同样的LPCTSTR 变量去接GetString1函数的返回值时,没有其他额外隐藏的操作,程序直接了一次指针赋值。
LPCTSTR psz1 = GetString1();
这里的psz1也会同样的指向一片被销毁了的内存空间,所以,我们这里打印的内容时乱码,也即这片被销毁了的内存空间上的随机数据。
3) 当我们使用CString对象去接GetString1函数的返回值时,情况并没有好多少。
CString str1 = GetString1();
当GetString1返回一个LPCTSTR的指针并赋值给str1时,会自动调用CString对象的构造函数,这里的传给CString对象的构造函数的参数已经是一个销毁了的内存空间的指针,不幸的是,编译器并不会做此项检查,CString对象依然能构造成功,但是其内容同样是随机乱码。
4) 然后我们看GetString2函数。这个函数和GetString1类似,唯一不同之处,是其返回值是一个CString对象。这里返回的CString对象,意味着函数返回时,会调用CString对象的拷贝构造函数创建一个临时的CString对象并返回它。
5) 当我们尝试使用LPCTSTR类型的指针去接GetString2的返回值时,虽然GetString2返回了一个基于栈上CString变量拷贝构造的临时CString对象,但是,临时CString对象出了GetString2的作用域后也会随即销毁,导致的结果,就是我们用来接收返回值的指针变量指向了一片被销毁的内存空间,所以,这里同样的弹出了乱码。
LPCTSTR psz2 = GetString2();
6) 终于可以讨论最后一种情况了,这也是程序正常工作的唯一一种情况。
CString str2 = GetString2();
我们不再使用LPCTSTR类型的指针接收函数发返回值了,而是使用CString类型去接收一个返回CSring对象的函数的返回值(好像有点绕)。那么,这里边发生了什么事情呢?
GetString2按值返回了一个CString临时对象,赋值给str2变量时,现代编译器执行了N(amed)RVO优化(返回值优化)。所以,这里我们在进行单步调试的时候,赋值动作是一次性就执行完毕了,并不需要调用赋值运算符了。
结论
1) 返回小型对象的成本会由编译器RVO吸收,所以,适当的情况下,可以按值返回小型对象。但是,对于大型复杂的对象,还是建议返回指向对象的引用或指针,注意了,永远不要试图返回一个指向临时对象的引用或指针。