Cpp核心

Cpp核心

fetch150zy

Core Modern C++

constness

const

constant expressions

constexpr

在编译期使用函数计算常量表达式

example

1
2
3
4
5
6
7
constexpr int f(int x) {
...
}
constexpr int a = f(5); // computed at compile-time
int b = f(5); // maybe computed at compile-time
int n = ...; // runtime value
int c = f(n); // computed at runtime

Notes

  • 一个constexpr函数可能会在编译期执行
    • Arugments must be constexpr or literals in order to allow compile time evaluation
    • Function body must be visible to compiler
  • 一个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 constexpr constructor and the destructor is trivial(or constexpr since 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
2
3
4
5
6
7
8
9
10
11
template <typename T>
auto remove_ptr(T t) {
if constexpr (std::is_pointer_v<T>) {
return *t;
} else {
return t;
}
}
int i = ...; int *j = ...;
int r = remove_ptr(i); // = i
int q = remove_ptr(j); // = *j

Immediate functions

Motivation

  • Force a function to be executed at compile-time
  • Runtime evaluation is forbidden
  • Same restrictions as constexpr functions
  • New keyword: consteval

example

1
2
3
4
5
6
7
consteval int f(int x) {
...
}
constexpr int a = f(5); // computed at compile-time
int b = f(5); // computed at compile-time
int n = ...; // runtime value
int c = f(n); // compilation error

constinit

Motivation

  • Like a constexpr variable, a constinit variable guarantees compile-time initialization, but can be modifided afterwards
  • Only allowed for static/global and thread_local variables
  • Initializer must be a constant expression, but constexpr destruction is not required
1
2
3
4
5
6
constexpr int f(int x) {
...
}
constexpr int a = f(5); // compile-time init, not modifiable
int b = f(5); // maybe compile-time init, modifiable
constinit int c = f(5); // compile-time init, modifiable

Exceptions

当一个异常可以在内部被解决,使用try ... catch

1
2
3
4
5
try {
...
} catch (error) {
...
}

当一个异常不能在内部解决,使用throw来抛出异常

1
2
3
void func(...) {
throw error;
}

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
2
3
4
5
6
7
try {
...
} catch (const std::error& e) {
...
} catch (...) { // catch everything
...
}

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
2
3
4
5
try {
...
} catch (...) {
throw; // rethrow
}

use catch(...){} to catch everything

Stack Unwinding

Stack unwinding是C++中的一个异常处理机制,涉及到异常被抛出后,程序控制流程从抛出异常的点回溯到捕获异常的点的过程;在这个过程中,程序自动释放从异常抛出点到异常捕获点之间的栈帧,确保调用栈上的所有局部对象都能得到适当处理

  1. 异常抛出:当程序执行到throw语句时,异常被抛出
  2. 栈回溯:程序开始回溯调用栈,寻找能够处理这个异常的catch
  3. 局部对象销毁:在回溯过程中,程序离开了某些函数的作用域;函数中的所有局部对象都将被销毁,这包括自动调用这些对象的析构函数
  4. 异常捕获:一旦找到合适的catch块,栈回溯停止,程序控制流转移到该catch

确保了资源的正确管理,防止了资源泄漏,无论异常是否被捕获,通过自动销毁局部对象,它确保程序的健壮性和稳定性

example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class C {...};
void f() {
C c1;
throw exception{}; // start unwinding
C c2; // not run
}
void g() {
C c3; f();
}
int main() {
try {
C c4;
g();
...; // not run
} catch (const exception&) {
// c1 c3 c4 have been destructed
}
}

异常最佳实践

  • 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
2
3
4
5
6
7
8
for (string const &num: nums) {
try {
int i = convert(num);
process(i);
} catch (not_an_int const &e) {
... // log and continue
}
}

Prefer

1
2
3
4
5
6
7
8
for (string const &num: nums) {
optional<int> i = convert(num);
if (i) {
process(*i);
} else {
... // log and continue
}
}

noexcept specifier

  • a fucntion with the noexcept specifier states that it guarantees to not throw an exception

    1
    int f() noexcept;
    • checked at compile time
  • a function with noexcept(expression) is only noexcept when expression evaluates to true at compile time

    1
    int safe_if_8B() noexcept(sizeof(long) == 8);
1
2
constexpr bool callCannotThrow = noexcept(f());
if constexpr (callCannotThrow) {...}
1
2
3
4
5
template <typename Function>
void g(Function f) noexcept(noexcept(f())) {
...;
f();
}

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

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++编译器的一个优化技术,可以消除对象的不必要拷贝,以提高程序的性能

  1. 返回值优化(RVO): 当函数返回一个局部对象时,编译器可以直接在调用函数的返回空间构造这个对象,避免了拷贝和移动构造
  2. 命名返回值优化(NRVO): 与RVO类似,但适合于具有名称的局部对象
  3. 临时对象优化: 当使用临时对象初始化另一个对象时,编译器可以消除这个临时对象的创建和销毁,直接在目标位置构造对象
1
2
3
4
5
6
7
8
9
10
class A {...};

A createA() {
A a;
return a; // NRVO
}
int main() {
A myA = createA(); // 拷贝或移动构造可能被省略
}

Templates

Lambdas

Ranges

RAII and smart pointers

Initialization