背景

最近准备写 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;
}

基础

教程: https://changkun.de/modern-cpp/zh-cn/01-intro/index.html#1-1-%E8%A2%AB%E5%BC%83%E7%94%A8%E7%9A%84%E7%89%B9%E6%80%A7

怎么理解 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_BYEXCLUDES属性字:

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/

技巧

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/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.comhttps://zh.cppreference.com 就足够了. 更多的需要example 辅助学习.