Google-Specific Magic 谷歌独家黑科技
我们采用多种技巧和工具来增强 C++ 代码的健壮性,同时我们在使用 C++ 的方式上也存在一些与别处不同的地方。
所有权与智能指针
对于动态分配的对象,应确保其拥有唯一且固定的拥有者。在转移所有权时,应优先使用智能指针。定义:
“所有权” 是一种用于管理动态分配内存(及其他资源)的记账技术。所谓动态分配对象的“拥有者”,是指那些负责在对象不再需要时将其删除的对象或函数。所有权有时可以共享,在这种情况下,通常是最后一位拥有者负责将其删除。即使所有权不被共享,它也可以在代码的不同部分之间进行转移。
“智能”指针 是行为类似于指针的类,例如它们会重载 * 和 -> 运算符。某些类型的智能指针可用于自动化所有权记账,以确保上述责任得到履行。
std::unique_ptr 是一种表达对动态分配对象独占所有权的智能指针类型。当 std::unique_ptr 离开作用域时,其所拥有的对象会被自动删除。它不可复制,但可以通过移动(Move)来表示所有权的转移。
std::shared_ptr 则是一种表达共享所有权的智能指针类型。std::shared_ptr 可以被复制,所有副本共同共享该对象的所有权。当最后一个 std::shared_ptr 被销毁时,对象才会被删除。
优点:
若不采用某种所有权逻辑,实际上几乎不可能妥善管理动态分配的内存。
转移对象的所有权通常比复制对象本身成本更低(况且有些对象根本无法复制)。
相较于“借用”指针或引用的方式,转移所有权往往更为简单,因为它减少了双方用户协调对象生命周期的需要。
智能指针通过显式地表达所有权逻辑,能够提升代码的可读性,使其具备自文档化和无歧义的特点。
智能指针可以省去手动进行所有权记账的繁琐工作,从而简化代码,并彻底杜绝一大类潜在错误。
对于常量对象而言,共享所有权相比深拷贝,往往是一种更简单且高效的替代方案。
缺点:
所有权必须通过指针(无论是智能指针还是原始指针)来表示和转移。指针的语义比值语义要复杂得多,尤其是在 API 中:你不仅需要担心所有权,还必须处理别名、生命周期和可变性等诸多问题。
值语义的性能开销常常被高估了,因此,所有权转移带来的那点性能收益,可能并不足以抵消其在可读性和复杂度上的代价。
强制要求转移所有权的 API,会迫使使用者只能采用单一的内存管理模型。
使用智能指针的代码,往往不够明确地指出资源释放的具体位置。
std::unique_ptr 使用移动语义来表达所有权转移,这可能比较复杂,让部分程序员感到困惑。
共享所有权有时会成为一种诱人的选择,从而替代精心设计的所有权模型,但这往往会模糊系统的整体设计。
共享所有权需要在运行时进行显式的引用记账,这可能带来性能开销。
在某些情况下(例如循环引用),拥有共享所有权的对象可能永远不会被删除。
智能指针并不是原始指针的完美替代品。
结论:
如果必须进行动态分配,建议将所有权保留在执行分配操作的代码中。如果其他代码需要访问该对象,可以考虑传递一个副本,或者传递一个指针/引用(但不转移所有权)。
建议使用 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。
ccpplint(C++ 代码检查工具)
使用 cpplint.py 来检测代码风格错误。
cpplint.py 是一款读取源文件并识别其中多种风格错误的工具。虽然它并不完美,且存在误报(假阳性)和漏报(假阴性)的情况,但它依然是一个非常有价值的辅助工具。
部分项目会提供通过其项目工具运行 cpplint.py 的具体说明。如果你所参与的项目没有提供此类说明,你可以单独下载 cpplint.py。

晋公网安备14030302000174号 |