Cpp核心
Core Modern C++
constness
const
constant expressions
constexpr
在编译期使用函数计算常量表达式
example
1 | constexpr int f(int x) { |
Notes
- 一个constexpr函数可能会在编译期执行
- Arugments must be
constexpror literals in order to allow compile time evaluation - Function body must be visible to compiler
- Arugments must be
- 一个constexpr函数可以用于运行时
- 一个constexpr变量必须在编译期被初始化
- 类可以有constexpr成员函数
- 被用于constant expressions必须是字面量类型
- inegral, floating-point, enum, reference, pointer type
- union(of at last one) or array of literal types
- class type with a
constexprconstructor and the destructor is trivial(orconstexprsince C++20)
- 一个constexpr函数隐含
inline(header files)
Limitation of constexpr functions
C++11
- Non-virtual function with a single return statement
C++14/C++17
- no try-catch, goto or asm statements
- no uninitialized/static/thread_local/non-literal-type variables
C++20
- no coroutines or static/thread_local/non-literal-type variables
- throw and asm statements allowed, but may not be executed
- transient memory allocation (memory allocated at compile-time must be freed again at compile-time)
- virtual functions and uninitialized variables allowed
C++23
- no coroutines, or execution of throw and asm statements
- transient memory allocation
- everything else allowed
compile-time branches
if constexpr
- 使用一个
constexpr表达式作为条件 - 在编译期计算出来
- key benefit: the discarded branch can contain invalid code
example
1 | template <typename T> |
Immediate functions
Motivation
- Force a function to be executed at compile-time
- Runtime evaluation is forbidden
- Same restrictions as
constexprfunctions - New keyword:
consteval
example
1 | consteval int f(int x) { |
constinit
Motivation
- Like a
constexprvariable, aconstinitvariable guarantees compile-time initialization, but can be modifided afterwards - Only allowed for
static/globalandthread_localvariables - Initializer must be a constant expression, but
constexprdestruction is not required
1 | constexpr int f(int x) { |
Exceptions
当一个异常可以在内部被解决,使用try ... catch
1 | try { |
当一个异常不能在内部解决,使用throw来抛出异常
1 | void func(...) { |
Standard exceptions
- std::exception, defined in header
<exception>- 所有标准异常的基类
- 通过
virtual const char* what() const;获取异常信息 - 从这些标准异常类中派生自定义异常类
- From
<stdexcept>- std::runtime_error, std::logic_error, std::out_of_range, std::invalid_arguments, …
- Store a string:
throw std::runtime_error{"msg"}
- std::bad_alloc, defined in header
<new>- 由分配函数抛出(e.g. new)
- 分配失败的信号
Catching exceptions
使用 const reference 来捕获异常
1 | try { |
Rethrowing exceptions
- a caught exception can be rethrow inside the catch handler
- useful when we want to act on an error, but cannot handle and want to propagate it
1 | try { |
use
catch(...){}to catch everything
Stack Unwinding
Stack unwinding是C++中的一个异常处理机制,涉及到异常被抛出后,程序控制流程从抛出异常的点回溯到捕获异常的点的过程;在这个过程中,程序自动释放从异常抛出点到异常捕获点之间的栈帧,确保调用栈上的所有局部对象都能得到适当处理
- 异常抛出:当程序执行到
throw语句时,异常被抛出 - 栈回溯:程序开始回溯调用栈,寻找能够处理这个异常的
catch块 - 局部对象销毁:在回溯过程中,程序离开了某些函数的作用域;函数中的所有局部对象都将被销毁,这包括自动调用这些对象的析构函数
- 异常捕获:一旦找到合适的
catch块,栈回溯停止,程序控制流转移到该catch块
确保了资源的正确管理,防止了资源泄漏,无论异常是否被捕获,通过自动销毁局部对象,它确保程序的健壮性和稳定性
example
1 | class C {...}; |
异常最佳实践
use exceptions for unlikely runtime errors outside the program’s control\
bad inputs, files unexpectedly not found, DB connections
don’t use exceptions for logic errors in your code
use assert and tests
don’t use exceptions to provide alternative/skip return values
- you can use std::optional or std::variant
- avoid using the global C-style errno
never throw in destructors
tips: for performance, when error raising and handling are close, or errors occur often, prefer error code or a dedicated class
Avoid
1 | for (string const &num: nums) { |
Prefer
1 | for (string const &num: nums) { |
noexcept specifier
a fucntion with the
noexceptspecifier states that it guarantees to not throw an exception1
int f() noexcept;
- checked at compile time
a function with
noexcept(expression) is only noexcept when expression evaluates to true at compile time1
int safe_if_8B() noexcept(sizeof(long) == 8);
1 | constexpr bool callCannotThrow = noexcept(f()); |
1 | template <typename Function> |
Exception Safety Guarantees
一个函数可以提供下面这些保证
- NO guarantee(没有保证,异常可能会导致进入未定义程序状态)
- Basic exception safety guarantee
- 异常会是程序进入已定义状态
- 没有资源会被泄漏
- Strong exception safety guarantee
- 当异常发生时,初始状态必须要被恢复
- No-throw guarantee
- 函数永远不会抛出异常(e.g. is marked
noexcept) - Errors are handled internally or lead to termination
- 函数永远不会抛出异常(e.g. is marked
Alternatives to exceptions
The global variable errno (avoid)
Return an error code (e.g. an int or an enum)
Return a std::error_code(C++ 11)
if failing to produce a value is not a hard error, consider returning std::optional
(C++ 17) return std::expected<T, E> (C++ 23) where T is the return type on success and E is the type on failure
Terminate the program
e.g. by calling std::terminate() or std::abort()
Copy elision
拷贝省略:是C++编译器的一个优化技术,可以消除对象的不必要拷贝,以提高程序的性能
- 返回值优化(RVO): 当函数返回一个局部对象时,编译器可以直接在调用函数的返回空间构造这个对象,避免了拷贝和移动构造
- 命名返回值优化(NRVO): 与RVO类似,但适合于具有名称的局部对象
- 临时对象优化: 当使用临时对象初始化另一个对象时,编译器可以消除这个临时对象的创建和销毁,直接在目标位置构造对象
1 | class A {...}; |