没有#define
不要使用预处理器来定义可以用 C++ 表示的实体。它们应该仅用于需要基于文本的预处理器并超越语言的事物:例如,条件编译和抽象特定于编译器的扩展。即便如此,请注意它们可能无法与模块一起很好地工作(在我撰写本文时即将推出)。
应使用const
/定义简单值常量constexper
。相关值组可能使用枚举 ( enum
) 类型。
类似函数的宏应该替换为inline
函数。如果宏与不同类型一起使用,请将其设为模板。
<span style="color:#000000"><span style="background-color:#fbedbb"><span style="color:#808080">#define PORT 254 // <em>replace with...</em>
</span><span style="color:#0000ff">constexpr</span> <span style="color:#0000ff">int</span> PORT = <span style="color:#000080">254</span>;
<span style="color:#808080">#define foo(x,y) ((x)+(y))/((x)-(y)) // <em>replace with...</em>
</span><span style="color:#0000ff">template</span> <span style="color:#0000ff"><</span><span style="color:#0000ff">typename</span> T<span style="color:#0000ff">></span>
T foo (T x, T,y) { <span style="color:#0000ff">return</span> (x+y)/(x-y); }</span></span>
在需要的地方声明变量并初始化它们
C 编程语言最初(在C99之前)要求在任何代码之前首先声明变量。很多程序员都是这样学来的,并代代相传。最好在所需的最小范围内声明变量,直到您准备好实际使用它为止。二十多年前,C 语言就采用了这种改进!
使用初始值设定项声明变量。当然,在您知道初始值是多少之前,您不能这样做。变量不是挤在一起取暖的企鹅。
虚拟功能标记
虚函数的声明应该只使用virtual
oroverride
关键字之一。
请参阅核心指南C.128。
在 C++11 之前,层次结构中的虚函数被证明是一种维护风险。如果您更改虚函数的签名,并且不更改该虚函数的每个版本,编译器不会给出任何错误,但那些未更改的函数不再覆盖预期的虚函数。现在,您声明一个函数旨在覆盖基类中的现有虚函数,如果找不到匹配项,编译器将报错。
使用常量
一些熟悉某些其他语言的人忘记了那const
是一回事。自由使用const
定义变量。将大对象作为const &
.
accessors
应声明的成员函数const
。
DRY——不要重复自己
- 没有复制/粘贴/粘贴/粘贴
不要使用新建/删除
不要编写执行 、 或其他标准容器为您执行的操作的临时std::vector
代码std::string
。
如果您需要指针,请使用std::unique_ptr
或std::shared_ptr
。通过调用make_unique/进行分配make_shared
,而不是通过编写new-expression进行分配。释放是自动完成的。另见核心指南C.150和C.151。
不要使用旧式或 C 式转换
<span style="color:#000000"><span style="background-color:#fbedbb"><span style="color:#0000ff">double</span> f = <span style="color:#000080">3</span>.<span style="color:#000080">14</span>;
<span style="color:#0000ff">unsigned</span> <span style="color:#0000ff">int</span> n1 = (<span style="color:#0000ff">unsigned</span> <span style="color:#0000ff">int</span>)f; <span style="color:#008000"><em>// C-style cast
</em></span><span style="color:#0000ff">unsigned</span> <span style="color:#0000ff">int</span> n2 = <span style="color:#0000ff">unsigned</span>(f); <span style="color:#008000"><em>// functional cast</em></span></span></span>
通常,显式类型转换(任一表示法)会破坏语言的正常类型检查。此外,它以一种“狂野”的方式这样做,根据所涉及的特定类型执行不同的操作。考虑这样的(C*)p
强制转换,旨在简单地从 pointer 中删除 const-ness p
。现在,的类型p
已更改为其他类型;这个转换现在意味着完全不同的东西——它将地址重新解释为不相关的类型,在程序最终崩溃之前导致各种内存损坏。
使用命名的转换运算符,您可以指定要执行的操作类型。如果改用上面的示例const_cast<C*>(p)
,原始代码具有相同的效果。但是当 的定义p
被改变时,编译器现在给出一个错误。(当然,如果你一开始就不必丢弃就更好了const
。)
一些强制转换可以做隐式转换可以做的事情,也可以做额外的事情,比如隐式转换的逆过程。因此,如果根本不需要强制转换就可以完成某些操作,请不要使用强制转换。在对程序进行更改之后,相同的转换可能会指示不同的、不需要的操作,而不是错误。如果要为清晰起见或消除调用歧义而命名目标类型,请改用临时变量。
<span style="color:#000000"><span style="background-color:#fbedbb">foo ((C*)p); <span style="color:#008000"><em>// <em>avoid</em>
</em></span>
C* p_as_base = p; <span style="color:#008000"><em>// <em>this is an implicit conversion</em>
</em></span>foo (p_as_base);</span></span>
将代码中的任何旧式或 C 式转换视为代码审查问题。在考虑之后,如果确实需要强制转换,则消除强制转换或使用命名的强制转换运算符。
编写(好的)函数
- 单一职责https://en.wikipedia.org/wiki/Single_responsibility_principle
- 更深入地实施到一个层次的细节
- 名字很好,清楚如何使用
常规类型
请参阅“常规类型以及我为何关心?” Victor Ciura 在 Meeting C++ 2018 上的演讲。一个概念性的总结是类型应该表现得像内置类型;即,照做int
。
3/5/0 规则
►三:如果您实施任何基本的内务处理操作:
- 析构函数
- 复制构造函数
- 复制赋值运算符
那么您可能需要实施所有这些。
如果不是这种情况,您应该明确说明并始终将它们声明为(as =default
)。
►五:C++11增加了两个更特殊的管家操作:
- 移动构造函数
- 移动赋值运算符
如果您没有定义任何特殊的内务处理操作,那么移动语义将根据成员的移动语义自动生成,如果所有成员都支持的话。但是,如果您定义了任何内务处理操作,编译器将不再假设它知道发生了什么,也不会自动生成它们。如果您可以支持移动语义,请定义这些功能;如果内置逻辑确实适合您,请将其定义为=default
. 如果您想禁用移动语义或只是明确表示您知道它们不会工作而不是忘记它们,请将它们定义为=delete
.
►Zero:如果您不需要定义内务处理操作,则不要。当你编写一个类时,基本的内务处理操作(复制构造函数、赋值、析构函数)都会自动生成,以对所有数据成员执行该操作。如果您的对象由其他正确定义的对象组成,那么它应该都是自动的。只有这种新类型独有的特殊约束或成员之间的关系才会促使您为此类指定管理细节。
如果您发现自己仅仅因为个别数据成员而添加了析构函数(或其他内务处理操作),那么应该由该成员来处理。例如,如果您有一个原始指针并且析构函数将其删除,您应该改用智能指针。
如果默认构造函数只是为某些数据成员提供(非默认)值,您可以改为编写内联成员初始值设定项,而不是显式定义构造函数。