1. ()
与{}
初始化
{}
:统一初始化
-
可用于初始化非静态数据成员
-
能防止隐式变窄转换(如
double -> int
) -
调用构造函数,一般和
()
初始化效果一样,但优先匹配std::initializer_list
参数的构造函数-
例子:
std::vector
-
无法转化时(除了拒绝变窄转换),才会回退匹配其它构造函数
-
若调用类似
T w{}
,优先匹配无参构造函数
-
()
初始化:
-
符合旧版本C++
-
避开不经意调用
std::initializer_list
的问题
所以{}
和()
初始化的选择:
-
广泛使用
{}
,但需要确保构造函数重载不要和std::initializer_list
弄混 -
两种初始化大部分效果一样,但某些情况可能会有很大不同(如
std::vector
)
2. 优先使用nullptr
优先用nullptr
,而不用NULL
或0
。
原因:
-
NULL
是一个宏,不同编译器的行为可能不同 -
重载时,传入
NULL
可能不会调用参数是指针的函数- 而
nullptr
类型为std::nullptr_t
,可以隐式转换为指向任何内置类型的指针,因此不会有这个问题
- 而
-
模板推导时,
NULL
可能被推导为int
或long
类型,当参数要求为指针时,会编译不通过
3. 优先使用using
声明类型别名
C++98提供typedef
实现这个功能,C++11后推荐使用using
1
using Alias = Type;
using
声明别名:
-
提供了
typedef
的能力,还能被模板化 -
using
在模板中使用,可避免使用::type
类似的后缀,-
使用
typedef
时不仅需要加后缀,而且使用模板时,还要在前面加上typename
-
此外使用
typedef
时,后面的::type
类似的后缀可能和成员名冲突
-
-
C++14提供了C++11所有type traits转换的别名声明版本
4. 优先使用限域enum
非限域enum
是C++98风格,如下所示。其枚举类型+枚举值都会作用到其定义的作用域:
1
2
enum Enum { Value1, Value2, };
auto Value1 = false; // cannot compile
限域enum
是C++11风格,如下所示。其枚举值只会作用在大括号内:
1
2
enum class Enum { Value1, Value2, };
auto Value1 = false; // OK
因此优先使用enum class
限域枚举:
-
减少枚举值泄露的污染
-
强类型
- 未限域
enum
会隐式转换为整数,从而歪曲语义
- 未限域
-
可前置声明(例如
enum class Enum;
)- 未限域
enum
需要指定底层类型才能前置,无默认底层类型
- 未限域
-
底层类型编译期确定,默认
int
- 若需更改,只需继承某个类型即可
5. 优先标注函数为delete
,而非未定义私有声明
若不想让对方调用某函数:
-
C++98:将其定义为
private
,且不定义 -
C++11:将其标注为
= delete
改进和优点:
-
= delete
后,友元调用它不能通过编译,而之前只能在链接中检查出来 -
任何函数都能被标记,不仅仅是类成员函数
-
可以禁止一些模版的特化(即禁止模板中的
T
为某个值)
所以= delete
比之前的方法做的更多,所以优先使用它。
6. 使用override
声明重写父类的函数
重写函数的要求:
-
父类函数为
virtual
-
函数名+参数类型一致
-
函数是否
const
一致 -
函数返回值+异常声明兼容
-
函数引用限定符一致(C++11)
-
即后面加上
&
或者&&
,前者只能在*this
为左值时调用,后者只能在*this
为右值时调用w.func()
:左值调用returnval().func()
:右值调用 -
若被右值调用,且返回内部值,考虑使用
std::move
操作数据,并将其和左值调用区分(即重载)
-
由于要求很多,所以很容易犯错,导致想重写却没实现重写,编译器可能连warning都不报。
因此,想要重写函数,就在声明时注明override
,编译器会帮你检查错误。
同样,
final
关键字也能帮助函数不被重写,也推荐使用
7. 优先使用常量迭代器
能用const
就用const
,对于迭代器也是一样。
因此:
-
若不对容器元素修改,优先使用常量迭代器
- 例如调用
cbegin
,cend
- 例如调用
-
若考虑代码通用性(例如在模板中使用),优先考虑非成员函数的
begin
,end
等