Google用了很多自己实现的技巧/工具使C++代码更加健壮,我们使用C++的方式可能和你在其它地方见到的有所不同。
5.1. 所有权与智能指针
总述
- 动态分配出的对象最好有单一且固定的所有主,并通过智能指针传递所有权。
定义
所有权是一种登记/管理动态内存和其它资源的技术。动态分配对象的所有主是一个对象或函数,后者负责确保当前者无用时就自动销毁前者。所有权有时可以共享,此时就由最后一个所有主来负责销毁它。甚至也可以不用共享,在代码中直接把所有权传递给其它对象。
智能指针是一个通过重载*和->运算符以表现得如指针一样的类。智能指针类型被用来自动化所有权的登记工作,来确保执行销毁义务到位。std::unique_ptr是C++11新推出的一种智能指针类型,用来表示动态分配出的对象的独一无二的所有权;当std::unique_ptr离开作用域时,对象就会被销毁。std::unique_ptr不能被复制,但可以把它移动(move)给新所有主。std::unique_ptr同样表示动态分配对象的所有权,但可以被共享,也可以被复制;对象的所有权由所有复制者共同拥有,最后一个复制者被销毁时,对象也会随着被销毁。
优点
- 如果没有清晰、逻辑条理的所有权安排,不可能管理好动态分配的内存。
- 传递对象的所有权,开销比复制来得小,如果可以复制的话。
- 传递所有权也比“借用”指针或引用来得简单,毕竟它大大省去了两个用户一起协调对象生命周期的工作。
- 如果所有权逻辑条理,有文档且不紊乱的话,可读性会有很大提升。
- 可以不用手动完成所有权的登记工作,大大简化了代码,也免去了一大波错误之恼。
- 对于
const对象来说,智能指针简单易用,也比深度复制高效。
缺点
- 不得不用指针(不管是智能的还是原生的)来表示和传递所有权。
- 指针语义可要比值语义复杂得许多了,特别是在API里:这时不光要操心所有权,还要顾及别名,生命周期,可变性以及其它大大小小的问题。
- 其实值语义的开销经常被高估,所以所有权传递带来的性能提升不一定能弥补可读性和复杂度的损失。
- 如果API依赖所有权的传递,就会害得客户端不得不用单一的内存管理模型。
- 如果使用智能指针,那么资源释放发生的位置就会变得不那么明显。
std::unique_ptr的所有权传递原理是C++11的move语法,后者毕竟是刚刚推出的,容易迷惑程序员。- 如果原本的所有权设计已经够完善了,那么若要引入所有权共享机制,可能不得不重构整个系统。
- 所有权共享机制的登记工作在运行时进行,开销可能相当大。
- 某些极端情况下(例如循环引用),所有权被共享的对象永远不会被销毁。
- 智能指针并不能够完全代替原生指针。
结论
如果必须使用动态分配,那么更倾向于将所有权保持在分配者手中。如果其他地方要使用这个对象,最好传递它的拷贝,或者传递一个不用改变所有权的指针或引用。倾向于使用 std::unique_ptr 来明确所有权传递,例如:
std::unique_ptr<Foo> FooFactory();void FooConsumer(std::unique_ptr<Foo> ptr);
如果没有很好的理由,则不要使用共享所有权。这里的理由可以是为了避免开销昂贵的拷贝操作,但是只有当性能提升非常明显,并且操作的对象是不可变的(比如说std::shared_ptr<const Foo> )时候,才能这么做。如果确实要使用共享所有权,建议于使用 std::shared_ptr 。
不要使用 std::auto_ptr,使用 std::unique_ptr 代替它。
5.2. Cpplint
总述
使用 cpplint.py 检查风格错误。
说明
cpplint.py 是一个用来分析源文件,能检查出多种风格错误的工具。它不并完美,甚至还会漏报和误报,但它仍然是一个非常有用的工具。在行尾加 // NOLINT,或在上一行加 // NOLINTNEXTLINE,可以忽略报错。
某些项目会指导你如何使用他们的项目工具运行 cpplint.py,如果你参与的项目没有提供, 你可以单独下载 cpplint.py.
5.3. 译者笔记
5.3.1. 译者 (acgtyrant) 笔记
- 把智能指针当成对象来看待的话,就很好领会它与所指对象之间的关系了。
- 原来Rust的Ownership思想是受到了C++智能指针的很大启发啊。
scoped_ptr和auto_ptr已过时。现在是shared_ptr和uniqued_ptr的天下了。- 按本文来说,似乎除了智能指针,还有其它所有权机制,值得留意。
- ArchLinux用户注意了,AUR有对cpplint打包。
