本文记录了我学 C++ 以来,需要特别记忆的一些特殊知识点
记录
- 《C++ Core Guidelines》—— 偏好 {} 初始化语法
- Use
=only when you are sure that there can be no narrowing conversions. For built-in arithmetic types, use=only withauto. ={}gives copy initialization whereas{}gives direct initialization. Like the distinction between copy-initialization and direct-initialization itself, this can lead to surprises.{}acceptsexplicitconstructors;={}does not.
- Use
- 《Hacking C++》——
explicit关键字与隐式转换- Make user-defined constructors
explicitby default!
- Make user-defined constructors
- 《C++ Core Guidelines》—— SF.8:为所有头文件使用
#include守卫- 避免文件被多次
#include。多次#include很有可能导致重定义错误。为了避免include守卫冲突,不要仅仅以文件名来命名守卫。请确保还包括一个关键字和良好的区分符,例如头文件所属的库或组件的名称。 - 示例:
// file foobar.h: #ifndef LIBRARY_FOOBAR_H #define LIBRARY_FOOBAR_H // ... declarations ... #endif // LIBRARY_FOOBAR_H- 注意:有些 C++ 实现提供了额外的扩展,比如
#pragma once作为include守卫的替代方案。它不是标准的,并且不可移植。 它将宿主机器的文件系统语义注入到你的程序中,除此之外还将你锁定到某个特定编译器。我们的建议是使用 ISO C++ 编写:参见 rule P.2.
但是其实 UE5 引擎和 Piccolo 引擎的源码里都用了
#pragma once。 - 避免文件被多次
- 《C++ Core Guidelines》—— SF.7:不要在头文件的全局范围内使用
using namespace- 这样做会使
#include的使用者(.cpp 文件)无法有效地消除歧义和使用替代方案。这也使得#include的头文件变得依赖于包含顺序,因为它们在不同的顺序中包含时可能会有不同的含义。
- 这样做会使
- 《C++ Core Guidelines》—— ES.41: 如果对运算符优先级有疑问,请使用括号
- 避免错误。可读性。并不是每个人都记得运算符优先级表。我们建议程序员记住算术运算符和逻辑运算符的优先级表,但在与其他运算符混用位逻辑运算符时考虑使用括号。
- 《C++ Core Guidelines》—— ES.101: 使用无符号类型进行位操作
- 无符号类型支持位操作而不会因符号位引起意外。
- 《C++ Core Guidelines》—— ES.102: 使用有符号类型进行算术运算
- 因为大多数算术运算假设为有符号;例如,当 y > x 时,x - y 会产生负数。如果你没有预期到,无符号算术运算可能会产生意外的结果。对于混合有符号和无符号算术运算,这一点更为明显。例子:
- 示例:
template<typename T, typename T2> T subtract(T x, T2 y) { return x - y; } void test() { int s = 5; unsigned int us = 5; cout << subtract(s, 7) << '\n'; // -2 cout << subtract(us, 7u) << '\n'; // 4294967294 cout << subtract(s, 7u) << '\n'; // -2 cout << subtract(us, 7) << '\n'; // 4294967294 cout << subtract(s, us + 2) << '\n'; // -2 cout << subtract(us, s + 2) << '\n'; // 4294967294 } - 如果你确实需要模运算,请使用无符号类型,并在必要时添加注释。
- 标准库使用无符号类型作为下标。内置数组使用有符号类型作为下标。这会导致意外(和错误)不可避免。
- 《C++ Core Guidelines》—— ES.107: 不要使用无符号类型作为下标,优先使用
gsl::index- 理由:避免有符号/无符号的混淆;实现更好的优化;实现更好的错误检测;避免auto和int带来的陷阱。
- 内置数组允许有符号下标。标准库容器使用无符号下标。因此,不可能有完美且完全兼容的解决方案(除非标准库容器在未来某天改为使用有符号下标)。鉴于无符号和有符号/无符号混合的问题,最好坚持使用大小足够的(有符号)整数,而
gsl::index可以保证这一点。
由于 gsl 库并不是标准库之一,我们可以用
ptrdiff_t来替代gsl::index。- 错误用法示例:
vector<int> vec = /*...*/; for (int i = 0; i < vec.size(); i += 2) // 可能不够大 cout << vec[i] << '\n'; for (unsigned i = 0; i < vec.size(); i += 2) // 存在回绕风险 cout << vec[i] << '\n'; for (auto i = 0; i < vec.size(); i += 2) // 可能不够大 cout << vec[i] << '\n'; for (vector<int>::size_type i = 0; i < vec.size(); i += 2) // 过于冗长 cout << vec[i] << '\n'; for (auto i = vec.size()-1; i >= 0; i -= 2) // 错误 cout << vec[i] << '\n'; for (int i = vec.size()-1; i >= 0; i -= 2) // 可能不够大 cout << vec[i] << '\n'; ··· - 正确用法示例:
vector<int> vec = /*...*/; for (gsl::index i = 0; i < vec.size(); i += 2) // 正确 cout << vec[i] << '\n'; for (gsl::index i = vec.size()-1; i >= 0; i -= 2) // 正确 cout << vec[i] << '\n'; ···
- 关于枚举——《Hacking C++》、关于枚举——《C++ Core Guidelines》:
- 《Hacking C++》: avoid unscoped enumerations
- 《Hacking C++》: if you set enumerator values explicitly, do it for all enumerators
- 《C++ Core Guidelines》: Enum.7: Specify the underlying type of an enumeration only when necessary.
- 《C++ Core Guidelines》: Enum.8: Specify enumerator values only when necessary.
- 关于函数的
[[nodiscard]]属性和noexcept关键字:- 《Hacking C++》:
[[nodiscard]]encourages compilers to issue warnings if function return values are discarded. Declare your function return values[[nodiscard]]- if calling it without using the return value makes no sense in any situation
- if users could be confused about its purpose, if the return value is ignored
- 《Hacking C++》: The
noexceptkeyword specifies that a function promises to never throw exceptions / let exceptions escape. If an exception escapes from a noexcept function anyway, the program will be aborted.-
void foo () noexcept { … }
-
- 《C++ Core Guidelines》:E.12: Use
noexceptwhen exiting a function because of athrowis impossible or unacceptable
- 《Hacking C++》: