diff --git a/google-cpp-styleguide/others.rst b/google-cpp-styleguide/others.rst index 29a4e0f..55a062c 100644 --- a/google-cpp-styleguide/others.rst +++ b/google-cpp-styleguide/others.rst @@ -39,7 +39,32 @@ 5.2. 右值引用 ~~~~~~~~~~~~~~~~~~~~~~ - TODO + +.. tip:: + + 只在定义移动构造函数与移动赋值操作时使用右值引用. 不要使用 ``std::forward``. + +定义: + + 右值引用是一种只能绑定到临时对象的引用的一种, 其语法与传统的引用语法相似. 例如, ``void f(string&& s)``; 声明了一个其参数是一个字符串的右值引用的函数. + +优点: + + 用于定义移动构造函数 (使用类的右值引用进行构造的函数) 使得移动一个值而非拷贝之成为可能. 例如, 如果 ``v1`` 是一个 ``vector``, 则 ``auto v2(std::move(v1))`` 将很可能不再进行大量的数据复制而只是简单地进行指针操作, 在某些情况下这将带来大幅度的性能提升. + + 右值引用使得编写通用的函数封装来转发其参数到另外一个函数成为可能, 无论其参数是否是临时对象都能正常工作. + + 右值引用能实现可移动但不可拷贝的类型, 这一特性对那些在拷贝方面没有实际需求, 但有时又需要将它们作为函数参数传递或塞入容器的类型很有用. + + 要高效率地使用某些标准库类型, 例如 ``std::unique_ptr``, ``std::move`` 是必需的. + +缺点: + + 右值引用是一个相对比较新的特性 (由 C++11 引入), 它尚未被广泛理解. 类似引用崩溃, 移动构造函数的自动推导这样的规则都是很复杂的. + +结论: + + 只在定义移动构造函数与移动赋值操作时使用右值引用, 不要使用 ``std::forward`` 功能函数. 你可能会使用 ``std::move`` 来表示将值从一个对象移动而不是复制到另一个对象. .. _function-overloading: @@ -161,8 +186,8 @@ - 在现有函数中添加 ``throw`` 语句时,您必须检查所有调用点。要么让所有调用点统统具备最低限度的异常安全保证,要么眼睁睁地看异常一路欢快地往上跑,最终中断掉整个程序。举例,``f()`` 调用 ``g()``, ``g()`` 又调用 ``h()``, 且 ``h`` 抛出的异常被 ``f`` 捕获。当心 ``g``, 否则会没妥善清理好。 - 还有更常见的,异常会彻底扰乱程序的执行流程并难以判断,函数也许会在您意料不到的地方返回。您或许会加一大堆何时何处处理异常的规定来降低风险,然而开发者的记忆负担更重了。 - - - Exception safety requires both RAII and different coding practices. Lots of supporting machinery is needed to make writing correct exception-safe code easy. Further, to avoid requiring readers to understand the entire call graph, exception-safe code must isolate logic that writes to persistent state into a "commit" phase. This will have both benefits and costs (perhaps where you're forced to obfuscate code to isolate the commit). Allowing exceptions would force us to always pay those costs even when they're not worth it. TODO + + - 异常安全需要RAII和不同的编码实践. 要轻松编写出正确的异常安全代码需要大量的支持机制. 更进一步地说, 为了避免读者理解整个调用表, 异常安全必须隔绝从持续状态写到 "提交" 状态的逻辑. 这一点有利有弊 (因为你也许不得不为了隔离提交而混淆代码). 如果允许使用异常, 我们就不得不时刻关注这样的弊端, 即使有时它们并不值得. - 启用异常会增加二进制文件数据,延长编译时间(或许影响小),还可能加大地址空间的压力。 @@ -192,27 +217,56 @@ 定义: - RTTI 允许程序员在运行时识别 C++ 类对象的类型. + RTTI 允许程序员在运行时识别 C++ 类对象的类型. 它通过使用 ``typeid`` 或者 ``dynamic_cast`` 完成. 优点: - RTTI 在某些单元测试中非常有用. 比如进行工厂类测试时, 用来验证一个新建对象是否为期望的动态类型. + RTTI 的标准替代 (下面将描述) 需要对有问题的类层级进行修改或重构. 有时这样的修改并不是我们所想要的, 甚至是不可取的, 尤其是在一个已经广泛使用的或者成熟的代码中. + + RTTI 在某些单元测试中非常有用. 比如进行工厂类测试时, 用来验证一个新建对象是否为期望的动态类型. RTTI 对于管理对象和派生对象的关系也很有用. + + 在考虑多个抽象对象时 RTTI 也很好用. 例如: + + .. code-block:: c++ - 除测试外, 极少用到. + bool Base::Equal(Base* other) = 0; + bool Derived::Equal(Base* other) { + Derived* that = dynamic_cast(other); + if (that == NULL) + return false; + ... + } 缺点: - 在运行时判断类型通常意味着设计问题. 如果你需要在运行期间确定一个对象的类型, 这通常说明你需要考虑重新设计你的类. + 在运行时判断类型通常意味着设计问题. 如果你需要在运行期间确定一个对象的类型, 这通常说明你需要考虑重新设计你的类. + + 随意地使用 RTTI 会使你的代码难以维护. 它使得基于类型的判断树或者 switch 语句散布在代码各处. 如果以后要进行修改, 你就必须检查它们. 结论: - 除单元测试外, 不要使用 RTTI. 如果你发现自己不得不写一些行为逻辑取决于对象类型的代码, 考虑换一种方式判断对象类型. - - 如果要实现根据子类类型来确定执行不同逻辑代码, 虚函数无疑更合适. 在对象内部就可以处理类型识别问题. - - 如果要在对象外部的代码中判断类型, 考虑使用双重分派方案, 如访问者模式. 可以方便的在对象本身之外确定类的类型. - - 如果你认为上面的方法你真的掌握不了, 你可以使用 RTTI, 但务必请三思 :-) . 不要试图手工实现一个貌似 RTTI 的替代方案, 我们反对使用 RTTI 的理由, 同样适用于那些在类型继承体系上使用类型标签的替代方案. + RTTI 有合理的用途但是容易被滥用, 因此在使用时请务必注意. 在单元测试中可以使用 RTTI, 但是在其他代码中请尽量避免. 尤其是在新代码中, 使用 RTTI 前务必三思. 如果你的代码需要根据不同的对象类型执行不同的行为的话, 请考虑用以下的两种替代方案之一查询类型: + + 虚函数可以根据子类类型的不同而执行不同代码. 这是把工作交给了对象本身去处理. + + 如果这一工作需要在对象之外完成, 可以考虑使用双重分发的方案, 例如使用访问者设计模式. 这就能够在对象之外进行类型判断. + + 如果程序能够保证给定的基类实例实际上都是某个派生类的实例, 那么就可以自由使用 dynamic_cast. 在这种情况下, 使用 dynamic_cast 也是一种替代方案. + + 基于类型的判断树是一个很强的暗示, 它说明你的代码已经偏离正轨了. 不要像下面这样: + + .. code-block:: c++ + + if (typeid(*data) == typeid(D1)) { + ... + } else if (typeid(*data) == typeid(D2)) { + ... + } else if (typeid(*data) == typeid(D3)) { + ... + + 一旦在类层级中加入新的子类, 像这样的代码往往会崩溃. 而且, 一旦某个子类的属性改变了, 你很难找到并修改所有受影响的代码块. + + 不要去手工实现一个类似 RTTI 的方案. 反对 RTTI 的理由同样适用于这些方案, 比如带类型标签的类继承体系. 而且, 这些方案会掩盖你的真实意图. 5.9. 类型转换 ~~~~~~~~~~~~~~~~~~~~~~