Google C++每周贴士 #5: 消逝的演出

(原文链接:https://abseil.io/tips/5 译者:clangpp@gmail.com)

每周贴士 #5: 消逝的演出

“Don’t know what you got till it’s gone.” --Cinderella
“直到失去之后才追悔莫及”——灰姑娘乐队

有时候,为了正确地运用C++的库,你既需要理解库本身,又需要理解这门语言。那么……下面代码中的问题是什么?

// 别这么干
std::string s1, s2;
...
const char* p1 = (s1 + s2).c_str();             // 别!
const char* p2 = absl::StrCat(s1, s2).c_str();  // 别!

s1+s2absl::StrCat(s1,s2)都创建了临时对象(这里都是字符串对象,但同样的规则适用于任意对象)。成员函数c_str()返回指向底层数据的指针,而底层数据与临时对象生存期一致。临时对象能活多长?根据C++17标准中的[class.temporary],“在临时对象创建点所在的完整表达式中,临时变量的销毁是表达式的最后一步。”(一个“完整表达式”是指“一个表达式,且它不是另一个表达式的子表达式”。)在上面的各个例子中,当赋值运算符右边的表达式结束的时候,临时变量就被销毁了,c_str()的返回值就成了悬挂指针。口诀(tl;dr?)(译者注:tl;dr?是缩写,全称too long; don’t read,后面往往接的是短小精悍的总结性陈述):见到分号(通常是更早)的时候,临时对象就作古了。啊哈!那怎么避免这类问题?

选项1:在完整表达式结束前用完临时对象:

// 安全(虽然弱鸡了一点)
size_t len1 = strlen((s1 + s2).c_str());
size_t len2 = strlen(absl::StrCat(s1, s2).c_str());

选项2:存储临时对象。

既然你都(在栈上)创建对象了,干嘛不多留它一会儿?这可能比它初看上去便宜。因为一个叫“返回值优化”的玩意儿(以及很多值类型上的移动语义,参考(Tip #77)),临时变量会在赋值目标对象上直接构造,而不是复制:

// 安全(且比你想象的更高效)
std::string tmp_1 = s1 + s2;
std::string tmp_2 = absl::StrCat(s1, s2);
// tmp_1.c_str()和tmp_2.c_str()是安全的。

选项3:存储一个指向临时变量的引用。

C++17标准[class.temporary]:“若临时变量绑定到引用,或临时变量的子对象绑定到引用,则临时变量生存期延展到与该引用一致。”

因为返回值优化的存在,这种方式通常并不比存储对象本身(选项2)更便宜,而且还有可能给人整蒙圈(参考Tip #101)。(需要用到生存期延展的特殊情况要注释清楚!)

// 同等安全:
const std::string& tmp_1 = s1 + s2;
const std::string& tmp_2 = absl::StrCat(s1, s2);
// tmp_1.c_str()和tmp_2.c_str()是安全的。

// 如下的行为徘徊在危险的边缘:
// 如果编译器能看出你是在存储一个指向临时对象内部的引用,它就会让整个对象活着。
// struct Person { string name; ... }
// GeneratePerson()返回一个对象;GeneratePerson().name显然是个子对象:
const std::string& person_name = GeneratePerson().name; // 安全

// 如果编译器看不出来,那你就危险了。
// class DiceSeries_DiceRoll { `const string&` nickname() ... }
// GenerateDiceRoll()返回一个对象;编译器可看不出来GenerateDiceRoll().nickname()是不是个子对象。
// 如下代码可能存储了一个悬挂引用:
const std::string& nickname = GenerateDiceRoll().nickname(); // 不好!

选项4:设计函数的时候就别返回对象???

很多函数遵循这条原则;但也有很多函数不遵守。相比要求调用者传进一个指向输出参数的指针,有时候返回个对象真的更好。在创建临时对象的地方多加小心。在操作临时对象的时候,任何返回对象内部的指针或引用的东西都有可能出问题。c_str()是最明显的罪魁祸首,但是protobuf的(可修改的或其他的)访问器(getter)和其他通常的访问器也同样有可能出问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值