5 minutes
Cpp_basic
背景
最近准备写 cpp 的一些项目, 学习下不太了解的领域.
book
术语
libc是Linux下原来的标准C库, 后来逐渐被glibc取代; glibc是Linux系统中最底层的API,几乎其它任何的运行库都要依赖glibc, glibc最主要的功能就是对系统调用的封装, 以及上层的接口 (malloc 等) glib也是个c程序库,不过比较轻量级,glib将C语言中的数据类型统一封装成自己的数据类型,提供了C语言常用的数据结构的定义以及处理函数 libc++是针对clang编译器特别重写的C++标准库,那libstdc++是gcc
concept ? 这个貌似很后面才有的 20?很像rust 了
模板元编程
参考: https://cloud.tencent.com/developer/article/1347878 策略 标签 特性
另外看书
memory order
gcc 里面 consumer 语意和 acquire 是一样的, 从 cpp11 stckoverflow知道, 不建议使用 consume
https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html https://gcc.gnu.org/wiki/Atomic/GCCMM/AtomicSync 这个例子很丰富, seq_cst 的效果和串行编程类似, relaxed 只是保证看到的只能是 更新的value, 没有线程同步语意, 纯粹是 atomic. Acquire/Release 只提供 有依赖关系的变量的 happen-before。 acquire-release 竟然只要求了 两个线程之间的同步 …. 同步机制中, 本质上flush语意
奇怪了, __atomic_compare_exchange_n
中 success fail 是在哪一步起作用的?为什么 fail 的 memory order 要弱于 success?
虚函数表和指针
https://zhuanlan.zhihu.com/p/98776075
cast
dynamic_cast: https://www.bbsmax.com/A/obzbypybdE/ 主要概你是 RTTI(其存储着类运行的相关信息, 比如类的名字,以及类的基类), 转换的时候会进行检查, 当有虚函数表的时候, 才会有RTTI, 还有 RTTICompleteObjectLocator dynamic_cast的时间和空间代价是相对较高的,在设计时应避免使用。
static_cast 向下转换不会进行检, 编译时确定 static_cast效率比dynamic_cast高,请尽可能用。大部分情况下static_cast编译时就可以确定指针移动多少偏移量,但是对于虚继承要用到虚指针确定一个到虚基类的偏移量,稍微麻烦一些
CRPT 奇异递归末班模式
https://zhuanlan.zhihu.com/p/137879448 貌似 modern cpp 有, 可以参照 还是得多实践下
functor
https://www.bogotobogo.com/cplusplus/functors.php 无非是 重载了 () 实现 template, 另外, 也支持 struct 其中, c98 支持 bind1st bind2nd, 限制太大, 还是使用 std::bind,
#include <iostream>
#include <functional> //std::bind
using namespace std;
void func(int x, int y)
{
cout << x << " " << y << endl;
}
int main()
{
bind(func, 1, 2)(); //输出:1 2
bind(func, std::placeholders::_1, 2)(1);//输出:1 2
using namespace std::placeholders; // adds visibility of _1, _2, _3,...
bind(func, 2, _1)(1); //输出:2 1
bind(func, 2, _2)(1, 2); //输出:2 2
bind(func, _1, _2)(1, 2); //输出:1 2
bind(func,_2, _1)(1, 2); //输出:2 1
//err, 调用时没有第二个参数
//bind(func, 2, _2)(1);
return 0;
}
基础
怎么理解 lock_guard 没有 mutex 的所有权 ? 看看这个: https://www.cnblogs.com/haippy/p/3279565.html 还没细看 顺序一致性 和 全局一致性的差异没有很好的讲明白, 看些 ppt 和 video 增强下 https://changkun.de/modern-cpp/zh-cn/07-thread/index.html#7-4-%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F 看样子 memory_order_seq_cst 并没有啥作用 https://www.cnblogs.com/navono007/p/5746048.html
ISO C++ Concurrency Study Group
锁: 针对 memory_order 不错的解释
std-memory-order-relaxed 重新学习
https://www.cnblogs.com/qingergege/p/7607089.html 浅层copy导致的 两次释放同一块区域 拷贝构造函数中,对于指针,我们一定要采用深层复制,而移动构造函数中,对于指针,我们采用浅层复制
ringbuffer 实现, 和 disruptor 对比 https://gist.github.com/xueliu/f7959c3608fad2bc942a9a4e48accea0 有点高大上. typedef 陈硕的设计: http://www.cppblog.com/Solstice/archive/2015/08/29/144378.html disruptor: https://github.com/Abc-Arbitrage/Disruptor-cpp
奇淫技巧
vector:
_LIBCPP_INLINE_VISIBILITY
: ?
_LIBCPP_NORETURN
: ?
_LIBCPP_EXTERN_TEMPLATE
: ?
小技巧:
copy( rq.begin(), rq.end(),
ostream_iterator<int>( cout, "\n" ) );
如何支持分配器 allocator 的设计?
ringbuffer 的 &T 用T 初始化,&T 作为参数, 那么复制进去会不会 调用copy ?
迭代器设计: https://users.cs.northwestern.edu/~riesbeck/programming/c++/stl-iterator-define.html
C++ _GUARDED_BY
和EXCLUDES
属性字:
http://clang.llvm.org/docs/ThreadSafetyAnalysis.html
移动语意
https://gaodq.github.io/2017/04/16/rvalue-reference-in-c++11/
mutex
abseil 的 mutex 支持 锁检查、读写锁 等. 具体的在对比下 (TODO)
thread_local
默认的thread local: https://spockwangs.github.io/blog/2017/12/01/thread-local-storage/ (讲了很多, https://docs.oracle.com/cd/E19253-01/819-7050/chapter8-1/index.html) ; 默认的 pthread_t pthread_key_t pthread_key_create pthread_getspecific pthread_setspecific, 这个是 posix 的 thread local 设计, 本质上 pthread_key_t 有最大的数组限制 (编译要加-pthread); c++ 11 提供了 thread_local 关键字, elf 的实现 基于 tls 段实现的.
GNUC下通过__thread关键字声明,MSVC下通过__declspec(thread)关键字声明
注意: pthread_key 要保证只被创建了一次, 不然会覆盖
__thread is faster than pthread_getspecific
__thread是GCC内置的线程局部存储设施
folly 中的设计: 使用 ThreadEntry 维护每个线程的 thread local 列表(elememts 对象), 每个thread local 通过 element wrapper 进行封装(支持自定义的deleter), 所有线程的 threadEntry 维护在 static meta(单例) 当中 参考: https://toutiao.io/posts/5e08c1/preview
StaticMeta, ThreadEntry
thread pool
abseil 内部有个线程池实现, 基于vector, 不过内部测试用的
folly 支持的比较完善, IOThreadPoolExecutor/CPUThreadPoolExecutor/ThreadPoolExecutor
Executor, DefaultKeepAliveExecutor, IOExecutor, ThreadPoolExecutor, IOThreadPoolExecutor, GlobalExecutor
facebook wangle: https://gaodq.github.io/2017/08/07/facebook-wangle/ https://engineering.fb.com/2016/04/20/networking-traffic/wangle-an-asynchronous-c-networking-and-rpc-library/
io async库: https://github.com/facebook/folly/tree/master/folly/io/async
CPUThreadPoolExecutor 依赖 LifoSemImpl, MPMCQueue
c10k: http://www.kegel.com/c10k.html fast unix server: https://nick-black.com/dankwiki/index.php/Fast_UNIX_Servers
io
libevent: https://libevent.org/ 基于libevent封装的io库: https://github.com/facebook/folly/tree/master/folly/io/async
排查工具
perf sched
容器
c++11 支持了新的 emplace 方法, 避免了常规的 insert 导致的临时变量的开销(变参模板, 完美转发, 看上去是直接在 容器的内存上构造参数) https://www.cnblogs.com/narjaja/p/10039509.html http://www.cplusplus.com/reference/map/map/emplace/
技巧
- 模板元编程 https://google.github.io/styleguide/cppguide.html#Template_metaprogramming
- style guide https://google.github.io/styleguide/cppguide.html
- diamond dependency problem: d -> c, b -> a, 编译器会报错
- Hyrum’s Law https://www.hyrumslaw.com/
proposal
std::expected
编译器优化
+= 做了什么 ?
abseil
更快的随机数 多种编译器: clang llvm/ gnu gcc compiler/visual studio mscv
fork
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
注意printf 这种缓冲区复制的情况
基本数据类型
long long 和 long 和 int的区别
取决于 编译器实现, 编译器根据系统类型的不同做法不同 (16/32/64位系统). long long 肯定是 8字节
类的多态
https://blog.csdn.net/afei__/article/details/82142775
官方定义: 程序运行时,父类指针可以根据具体指向的子类对象,来执行不同的函数 假设Parent 和 child 两个类. Parent 定义了 虚函数和实现, 子类进行了重新的实现. 这个时候会根据具体的实例化指向具体的实现.
- 对存在虚函数的类,编译器会在类中自动生成一个一维的虚函数表: 存储类成员函数指针
- 虚函数表由编译器自动生成和维护
- 被virtual 修饰的成员函数会被编译器放入虚函数表中
- 存在虚函数时,编译器会为对象自动生成一个指向虚函数表的虚指针(通常称之为 vptr 指针)
- 虚表是和类对应的,虚表指针是和对象对应的
注意: 父类的构造方法中调用虚函数,不会发生多态。这个和 vptr 的分步初始化有关
为什么呢? 这个和创建子类对象的编译器执行顺序有关
- 对象在创建时,由编译器对 vptr 进行初始化
- 子类的构造会先调用父类的构造函数,这个时候 vptr 会先指向父类的虚函数表
- 子类构造的时候,vptr 会再指向子类的虚函数表
- 对象的创建完成后,vptr 最终的指向才确定
纯虚函数是虚函数再加上 = 0, 抽象类是指包括至少一个纯虚函数的类
多态类的内存布局
参考: - https://blog.csdn.net/afei__/article/details/82142775 - https://www.cnblogs.com/cxq0017/p/6074247.html
锁
std::mutex 是基础的锁, 但是使用 std::mutex 很容易经常忘记解锁, 而且缺乏一些常用的特性: 延迟加锁、尝试加锁. 为此, cpp提供了 std::unique_lock 和 std::lock_gurad , 都是简化锁的使用, std::local_guard 是通过栈上变量的 构造和析构 解决加锁和解锁逻辑, std::unique_lock 则提供了更多的特性: 延迟锁定、递归锁定、有时限尝试、所有权转移等.
std::scoped_lock: raii风格的互斥包装器, 创建对象的时候 会获取 互斥的所有权, 离开的时候 逆序释放. 相当于两步操作的封装 (std::lock(a, b), std::lock_quard g1(a1, std::adopt_lock), std::lock_gurad g2(a2, std::adopt_lock))
https://zh.cppreference.com/w/cpp/thread/lock https://zh.cppreference.com/w/cpp/thread/mutex https://zh.cppreference.com/w/cpp/thread/lock_guard https://zh.cppreference.com/w/cpp/thread/unique_lock http://www.cplusplus.com/reference/mutex/unique_lock/ https://zh.cppreference.com/w/cpp/thread/scoped_lock
补充, std::lock 是一个函数, 用来对多个互斥量上锁, 还有尝试加锁的函数: std::try_lock 更多线程相关的信息: https://zh.cppreference.com/w/cpp/thread
智能指针
为了避免在处理指针导致指针对象泄露, cpp提供了多种智能指针的处理方式. 除了 auto, 更常见的是 std::shared_ptr 和 std::unique_ptr.
std::shared_ptr: 通过指针保持对象共享所有权的智能指针, 多个 shared_ptr 对象可占有同一对象, 当最后的shared_ptr 被销毁或者被赋值为其他指针就会释放、销毁 对象 并释放内存. 但是用share_ptr处理双向链表会导致泄露 (pre和next相互引用, 造成循环), 需要借助 weak_ptr
std::weak_ptr: 对shared_pte的弱引用, 在访问所需引用的对象的时候需要转换为 std::shared_ptr, 用来表达临时所有权的概念. 常见的例子是 双向列表
std::unique_ptr: 通过指针占有并管理另一个对象, 并在 unique_ptr离开作用域的时候销毁对象. 可以通过 std::move 转移 控制权.
std::move:
https://zh.cppreference.com/w/cpp/memory/shared_ptr https://zh.cppreference.com/w/cpp/memory/unique_ptr https://zh.cppreference.com/w/cpp/memory/weak_ptr
常用容器
boost.circular_buffer: 用数组维护了一段数据, 超过容量后覆盖开头的数据写入
其他概念
pimpl
pimpl: 指向实现的指针, 将类的实现细节从对象表示中移除, 放到另一个分离的类, 并以一个不透明的指针进行访问, 一开始看的略微懂了, 但是实践不是很懂, 看到这篇文章: https://www.cnblogs.com/senior-engineer/p/9811937.html, 理解了出发点: 基础的头文件修改会导致 依赖的 所有源文件都得重新编译.
https://zh.cppreference.com/w/cpp/language/pimpl
raii
resource acquisition is initializaiton: 资源即获取, 使用局部变量来管理资源. 因为对象的构造和析构是系统调用的, 可以在构造函数中初始化资源, 在析构函数中释放资源, 比如 std::lock_gurard. 也可以自己设计.
函数绑定
这个是最近学习到的, 可以动态申明绑定一个函数. 需要注意的是, std::ref 是引用传递, 外部修改和内部修改 都会奏效.
std::function<void()> bound_f = std::bind(f, n1, std::ref(n2), std::cref(n3));
参照:
- https://zh.cppreference.com/w/cpp/utility/functional/ref
- https://zh.cppreference.com/w/cpp/utility/functional/bind
左值、右值
虽然有文章: https://zh.cppreference.com/w/cpp/language/value_category, 但是一次还是没读懂
赋值、构造
移动构造函数: 构造函数很特别, 还要区分 析构函数阻止隐式移动构造函数 和 强制生成移动构造函数, 不是特别理解, 使用如下
A(A&& o) noexcept :
s(std::move(o.s)), // 类类型成员的显式移动
k(std::exchange(o.k, 0)) // 非类类型成员的显式移动
{ }
https://zh.cppreference.com/w/cpp/language/move_constructor https://zh.cppreference.com/w/cpp/named_req/MoveConstructible
移动赋值运算符: 不是很懂. 定义如下:
V& operator=(V&& other) {}
https://zh.cppreference.com/w/cpp/language/move_assignment https://zh.cppreference.com/w/cpp/named_req/MoveAssignable
noexcept
申明没有异常, 有效的阻止异常的传播与扩散, 编译器会选择 terminate. 有利于编译器对程序做更多的优化: C++中的异常处理是在运行时而不是编译时检测的, 为了实现运行时检测,编译器创建额外的代码. 支持 有条件的noexcecpt: noexcept(noexcept(x.swap(y))). 建议使用的场景:
移动构造函数(move constructor)
移动分配函数(move assignment)
析构函数(destructor)(在新版本的编译器中,析构函数是默认加上关键字noexcept的)
叶子函数(Leaf Function)。叶子函数是指在函数内部不分配栈空间,也不调用其它函数,也不存储非易失性寄存器,也不处理异常 ?
叶子函数
仿函数
函数指针 使函数作为参数 传递给处理逻辑, 解耦了 可插拔逻辑, 但是函数指针并不能维护 一些本地信息: 比如局部变量, 这个时候可以使用 Functor/仿函数,
https://cloud.tencent.com/developer/article/1347883
设计模式
单例
#include <stddef.h>
#include <memory>
#include <mutex> // NOLINT(build/c++11)
#include <utility>
#include "include/macros.h"
namespace byte {
template <typename T>
class SingletonBase {
private:
// Used to check destructed of object.
struct Holder {
T value;
bool is_alive;
template <typename... Args>
Holder(Holder** holder, Args&&... args) // NOLINT(runtime/explicit)
: value(std::forward<Args>(args)...), is_alive(true) {
*holder = this;
}
~Holder() {
is_alive = false;
}
};
protected:
SingletonBase() {}
~SingletonBase() {}
public:
// Construct singleton parameters.
template <typename... Args>
static T* Instance(Args&&... args) {
std::call_once(s_once_flag_, [] (Args&&... holder_args) {
static Holder holder(&s_holder_, std::forward<Args>(holder_args)...);
}, std::forward<Args>(args)...);
return &s_holder_->value;
}
static bool IsAlive() {
return s_holder_.get() && s_holder_->is_alive;
}
private:
static Holder* s_holder_;
static std::once_flag s_once_flag_;
DISALLOW_COPY_AND_ASSIGN(SingletonBase);
};
template <typename T>
typename SingletonBase<T>::Holder* SingletonBase<T>::s_holder_;
template <typename T>
typename std::once_flag SingletonBase<T>::s_once_flag_;
总结
目前参考 https://zh.cppreference.com 和 https://zh.cppreference.com 就足够了. 更多的需要example 辅助学习.