2

Effective Modern C++(3): Modern C++(1)

 1 year ago
source link: https://keys961.github.io/2022/05/30/Effective-Modern-C++(3)/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

1. (){}初始化

{}:统一初始化

  • 可用于初始化非静态数据成员

  • 能防止隐式变窄转换(如double -> int

  • 调用构造函数,一般和()初始化效果一样,但优先匹配std::initializer_list参数的构造函数

    • 例子:std::vector

    • 无法转化时(除了拒绝变窄转换),才会回退匹配其它构造函数

    • 若调用类似T w{},优先匹配无参构造函数

()初始化:

  • 符合旧版本C++

  • 避开不经意调用std::initializer_list的问题

所以{}()初始化的选择:

  • 广泛使用{},但需要确保构造函数重载不要和std::initializer_list弄混

  • 两种初始化大部分效果一样,但某些情况可能会有很大不同(如std::vector

2. 优先使用nullptr

优先用nullptr,而不用NULL0

  • NULL是一个宏,不同编译器的行为可能不同

  • 重载时,传入NULL可能不会调用参数是指针的函数

    • nullptr类型为std::nullptr_t,可以隐式转换为指向任何内置类型的指针,因此不会有这个问题
  • 模板推导时,NULL可能被推导为intlong类型,当参数要求为指针时,会编译不通过

3. 优先使用using声明类型别名

C++98提供typedef实现这个功能,C++11后推荐使用using

using Alias = Type; 

using声明别名:

  • 提供了typedef的能力,还能被模板化

  • using在模板中使用,可避免使用::type类似的后缀,

    • 使用typedef时不仅需要加后缀,而且使用模板时,还要在前面加上typename

    • 此外使用typedef时,后面的::type类似的后缀可能和成员名冲突

  • C++14提供了C++11所有type traits转换的别名声明版本

4. 优先使用限域enum

非限域enum是C++98风格,如下所示。其枚举类型+枚举值都会作用到其定义的作用域:

enum Enum { Value1, Value2, };
auto Value1 = false; // cannot compile

限域enum是C++11风格,如下所示。其枚举值只会作用在大括号内:

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


Related Issues not found

Please contact @keys961 to initialize the comment


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK