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等