From ae49946e2c81b676790abd2692d3ec3070658301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bug=E4=B8=80=E5=A4=A7=E9=98=9F?= Date: Fri, 29 Apr 2022 19:05:49 +0800 Subject: [PATCH] Update codestyle-c++.md --- codestyle-c++.md | 85 ------------------------------------------------ 1 file changed, 85 deletions(-) diff --git a/codestyle-c++.md b/codestyle-c++.md index de99c18..87ca936 100644 --- a/codestyle-c++.md +++ b/codestyle-c++.md @@ -8,33 +8,6 @@ > 命名空间将全局作用域细分为独立的, 具名的作用域, 可有效防止全局作用域的命名冲突. -**优点:** - -> 虽然类已经提供了(可嵌套的)命名轴线 (YuleFox 注: 将命名分割在不同类的作用域内), 命名空间在这基础上又封装了一层. -> -> 举例来说, 两个不同项目的全局作用域都有一个类 `Foo`, 这样在编译或运行时造成冲突. 如果每个项目将代码置于不同命名空间中, `project1::Foo` 和 `project2::Foo` 作为不同符号自然不会冲突. -> -> 内联命名空间会自动把内部的标识符放到外层作用域,比如: -> -> ``` -> namespace X { -> inline namespace Y { -> void foo(); -> } // namespace Y -> } // namespace X -> ``` -> -> `X::Y::foo()` 与 `X::foo()` 彼此可代替。内联命名空间主要用来保持跨版本的 ABI 兼容性。 - -**缺点:** - -> 命名空间具有迷惑性, 因为它们使得区分两个相同命名所指代的定义更加困难。 -> -> 内联命名空间很容易令人迷惑,毕竟其内部的成员不再受其声明所在命名空间的限制。内联命名空间只在大型版本控制里有用。 -> -> 有时候不得不多次引用某个定义在许多嵌套命名空间里的实体,使用完整的命名空间会导致代码的冗长。 -> -> 在头文件中使用匿名空间导致违背 C++ 的唯一定义原则 (One Definition Rule (ODR)). ### 1.2 匿名命名空间和静态变量 @@ -42,17 +15,6 @@ > 所有置于匿名命名空间的声明都具有内部链接性,函数和变量可以经由声明为 `static` 拥有内部链接性,这意味着你在这个文件中声明的这些标识符都不能在另一个文件中被访问。即使两个文件声明了完全一样名字的标识符,它们所指向的实体实际上是完全不同的。 -**结论:** - -> 推荐、鼓励在 `.cc` 中对于不需要在其他地方引用的标识符使用内部链接性声明,但是不要在 `.h` 中使用。 -> -> 匿名命名空间的声明和具名的格式相同,在最后注释上 `namespace` : -> -> ```c++ -> namespace { -> ... -> } // namespace -> ``` ### 1.3 局部变量 @@ -105,16 +67,6 @@ for (int i = 0; i < 1000000; ++i) { **定义** >在构造函数中可以进行各种初始化操作. -**优点** -> 无需考虑类是否被初始化. -> 经过构造函数完全初始化后的对象可以为 `const` 类型, 也能更方便地被标准容器或算法使用. - -**缺点** -> 如果在构造函数内调用了自身的虚函数, 这类调用是不会重定向到子类的虚函数实现. 即使当前没有子类化实现, 将来仍是隐患. -> 在没有使程序崩溃 (因为并不是一个始终合适的方法) 或者使用异常 (因为已经被 [禁用](https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/others/#exceptions) 了) 等方法的条件下, 构造函数很难上报错误 -> 如果执行失败, 会得到一个初始化失败的对象, 这个对象有可能进入不正常的状态, 必须使用 `bool IsValid()` 或类似这样的机制才能检查出来, 然而这是一个十分容易被疏忽的方法. -> 构造函数的地址是无法被取得的, 因此, 举例来说, 由构造函数完成的工作是无法以简单的方式交给其他线程的. - ### 2.2 结构体 VS. 类 **总述** @@ -137,13 +89,6 @@ for (int i = 0; i < 1000000; ++i) { > 当子类继承基类时, 子类包含了父基类所有数据及操作的定义. C++ 实践中, 继承主要用于两种场合: 实现继承, 子类继承父类的实现代码; [接口继承](https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/classes/#interface), 子类仅继承父类的方法名称. -**优点** - -> 实现继承通过原封不动的复用基类代码减少了代码量. 由于继承是在编译时声明, 程序员和编译器都可以理解相应操作并发现错误. 从编程角度而言, 接口继承是用来强制类输出特定的 API. 在类没有实现 API 中某个必须的方法时, 编译器同样会发现并报告错误. - -**缺点** - -> 对于实现继承, 由于子类的实现代码散布在父类和子类间之间, 要理解其实现变得更加困难. 子类不能重写父类的非虚函数, 当然也就不能修改其实现. 基类也可能定义了一些数据成员, 因此还必须区分基类的实际布局. ### 2.4 多重继承 @@ -155,14 +100,6 @@ for (int i = 0; i < 1000000; ++i) { > 多重继承允许子类拥有多个基类. 要将作为 *纯接口* 的基类和具有 *实现* 的基类区别开来. -**优点** - -> 相比单继承 (见 [继承](https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/classes/#inheritance)), 多重实现继承可以复用更多的代码. - -**缺点** - -> 真正需要用到多重 *实现* 继承的情况少之又少. 有时多重实现继承看上去是不错的解决方案, 但这时你通常也可以找到一个更明确, 更清晰的不同解决方案. - ### 2.5 接口 **总述** @@ -180,14 +117,6 @@ for (int i = 0; i < 1000000; ++i) { > 接口类不能被直接实例化, 因为它声明了纯虚函数. 为确保接口类的所有实现可被正确销毁, 必须为之声明虚析构函数 (作为上述第 1 条规则的特例, 析构函数不能是纯虚函数). 具体细节可参考 Stroustrup 的 *The C++ Programming Language, 3rd edition* 第 12.4 节. -**优点** - -> 以 `Interface` 为后缀可以提醒其他人不要为该接口类增加函数实现或非静态数据成员. 这一点对于 [多重继承](https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/classes/#multiple-inheritance) 尤其重要. 另外, 对于 Java 程序员来说, 接口的概念已是深入人心. - -**缺点** - -> `Interface` 后缀增加了类名长度, 为阅读和理解带来不便. 同时, 接口属性作为实现细节不应暴露给用户. - ## 3.函数 ### 3.1 输入和输出 @@ -214,13 +143,6 @@ for (int i = 0; i < 1000000; ++i) { > 在 C 语言中, 如果函数需要修改变量的值, 参数必须为指针, 如 `int foo(int *pval)`. 在 C++ 中, 函数还可以声明为引用参数: `int foo(int &val)`. -**优点** - -> 定义引用参数可以防止出现 `(*pval)++` 这样丑陋的代码. 引用参数对于拷贝构造函数这样的应用也是必需的. 同时也更明确地不接受空指针. - -**缺点** - -> 容易引起误解, 因为引用在语法上是值变量却拥有指针的语义. ### 3.3 函数重载 @@ -240,13 +162,6 @@ class MyClass { }; ``` -**优点** - -> 通过重载参数不同的同名函数, 可以令代码更加直观. 模板化代码需要重载, 这同时也能为使用者带来便利. - -**缺点** - -> 如果函数单靠不同的参数类型而重载 (acgtyrant 注:这意味着参数数量不变), 读者就得十分熟悉 C++ 五花八门的匹配规则, 以了解匹配过程具体到底如何. 另外, 如果派生类只重载了某个函数的部分变体, 继承语义就容易令人困惑. ## 4. 命名约定 -- GitLab