一个简单的 std::function 实现
一个简单的 std::function
实现, 能够完成基本功能, 即可以代理:
- 普通函数
bind
表达式lambda
表达式以及普通functor
- 成员函数
- 成员变量
functor
存储部分使用继承体系擦除 functor
类型, 保留返回值以及参数列表类型. 核心的调用部分则是直接使用 std::invoke
实现, 同时也没有做 functor
大小的区分, 统一存在堆上.
一个简单的 std::function
实现, 能够完成基本功能, 即可以代理:
bind
表达式lambda
表达式以及普通 functor
functor
存储部分使用继承体系擦除 functor
类型, 保留返回值以及参数列表类型. 核心的调用部分则是直接使用 std::invoke
实现, 同时也没有做 functor
大小的区分, 统一存在堆上.
最近遇到这么一个问题: 给定一个点 P 和三角形的三个点 A,B,C, 如何判断点 P 是否在 △ABC 内部?
「期值」是标准库为异步任务中返回值以及异常的传递提供的基础设施. 具体的做法是将这些值关联到共享的「future」对象上. 异步任务可以向这些期值对象中写入返回值或异常值, 异步任务的发起线程则可以通过这些期值对象等待, 读取, 检查其所需要的异步结果. 标准库中为上述功能提供的设施即 std::promise
和 std::future
.
Condition variable 和 Semaphore 是并发编程中用于同步控制和交互的原语. 多个并发线程可以使用 Condition variable 彼此交互, 一些线程可以通过 Condition variable 等待 (wait) 其他线程的通知 (notification). Condition variable 总是和 mutex 关联使用. Semaphore 相比 Condition variable 更加轻量, 它用来限制多个线程对共享资源的并发访问.
mutex
即 mutual exclusion, 表示一种互斥语义. 所谓互斥, 实际上是指所有权的唯一性, 即同一时刻一个互斥量只能由一个所有者持有. 在并发语境中, 互斥量这一原语的所有者一般是「线程」, 它的作用是用于阻止多个线程同时访问共享的资源, 从而避免 data race 并为多个线程的执行提供实现「同步」的机制.
Atomic 提供了开发高性能无锁 (lock free) 结构的工具和接口, 对于内置类型或自定义类型, 也可以通过 atomic<>
模板包装成支持原子操作的类型, 以实现并发安全.
C++ 17 提供了两个编译时常量, 用于表示 CPU Cache 的一些参数. 合理使用这两个编译时常量可以避免在多级 Cache 结构的计算机上并发程序频繁访问同一个对象不同成员时可能发生的 伪共享 (false sharing) 问题.
C++ 11 及之后的版本中提供了一系列并发编程的工具和对并发控制原语的抽象封装, 如 thread
, atomic
, mutex
, condition_varible
等. 本文介绍 thread
以及 jthread
的基本语义和功能, 它们在实际场景中的 best practice 和与之密切相关的编程模式不在本文讨论范围之内.
偶然在 vscode 的代码提示中看到了 thread_local
这个关键字, 心想 C++ 中什么时候提供了这种东西. 翻了下 cppreference 才知道早在 C++ 11 中就引入了这个修饰符, 用于声明线程生命周期. 趁此机会系统地看了下 C++ 变量的 4 种生命周期, 特此记录.
C++ 中的非继承类有两种访问权限, private
和 public
. 一般来说, private
只允许类型内部使用, 不允许外部访问. 然而, 由于 C++ 语言巨大的复杂性, 这一约束存在若干漏洞. 一种简单的做法就是假设 C++ 编译器不会对类对象的内存布局做奇怪的扰动, 这一假设通常是正确的, 因此可以通过猜测私有成员相对对象整体的地址偏移量访问私有成员. 或者更加方便地通过定义一个成员完全相同但访问权限相反的类, 并将原类对象的指针转换成新类的指针的方式访问原类对象的私有指针.
这两种方法本质上都基于「假设」, 即假设 C++ 编译器不会对类型的成员做特殊的处理. 本文介绍一种更加优雅的方法, 可以稳定地实现对私有成员的访问.