记录下写得比较好的文章!
extern
C/C++中extern关键字详解
一、对变量和函数:
主要是对变量或函数进行声明。告知编译器,此变量在其他地方进行了定义。可能在本模块的后方、可能在其他模块中。在编译阶段找不到定义不会报错,会在链接时在其他地方找。
A.h中声明了extern变量,B.cpp include 了A.h,那么B编译时不会报错,在链接A.a时寻找。
在源文件定义了数组int a[6];
则头文件应该声明extern int a[];
,而非extern int *a;
。
二、extern “C”:
让编译器在编译函数时,按照C语言 而非C++的方式进行编译。因C++支持重载,会在函数编译时加上参数信息。
C++调用C:
1、站在被调用方角度。可以在C语言的头文件以宏的形式控制是否extern “C”:
这样头文件被C++ include之后,就会默认带 extern “C”。
2、站在调用方角度。如果C的头文件没有extern “C”,则调用者自己要在extern "C"内includeC的头文件。
归根结底,在C++眼中,C的函数声明要在extern "C"中。
C调用C++:
C++头文件把函数声明放入extern "C"内。
但因C不支持extern "C"的语法,故不能直接include C++相应的头文件。而要在C文件内手动声明相应的函数或变量。
最后再链接起来。
三、C调用C++类:
https://blog.csdn.net/fengfengdiandia/article/details/82704375
static
右值引用
从4行代码看右值引用
作用:
1、在关闭编译器的返回值优化的时候,用右值引用去承接返回值,能够减少类构造的次数;
2、在类内维护了堆内存的时候,用右值引用的移动语义,实现类的移动构造函数。在函数返回类的时候 能代替拷贝构造函数,减少临时变量对堆内存的无意义的申请释放;
3、借助移动语义、universal references、完美转发,能在模板函数内根据用户传入的值类型,调用相应的函数、或者类相应的构造函数。
universal reference:
<template T> void f(T&&)
模板类型的引用,或为auto,为未定的引用类型。左值还是右值取决于传入的值。
引用折叠:
所有的右值引用叠加到右值引用上仍然还是一个右值引用
所有的其他引用类型之间的叠加都将变成左值引用
提供移动构造函数的同时,也要提供拷贝沟通函数,以防止移动失败了,还能拷贝。
完美转发:
std::forward。根据参数的实际类型进行转发(左值or右值)。
拷贝构造函数和赋值函数
虚函数
C++ 虚函数表解析
含有虚函数的类实例化的 对象 中都有一个虚函数指针,且同一个虚函数类实例化的对象的虚函数指针都指向同一个地址,即一个类只有一个虚函数表,实例化的对象共用一个虚函数表。
如果虚函数的调用次数很多,可能会对性能有一定影响。(因为每次都要通过指针寻找函数地址)
C++11
内存序(memory order)
理解memory order
brpc-Memory fence
SC:Sequential Consistency(顺序一致性):单核内的执行顺序和代码一致,多核之间可以相互交错
SC-DRF:Data Race Free:以加锁或atomic达到DRF。
6种order:
memory order:1、保证了重排的次序;2、保证了各个线程之间的可见性
其中,
relaxed:只确保单线程内结果与代码一致。(即在不影响单线程结果的前提下,编译器有可能调换代码顺序),但是在多线程之间的顺序不确定。
C++ memory order循序渐进(一)—— 多核编程和memory model:有时间拜读整个专栏
C++ memory order循序渐进(二)—— C++ memory order基本定义和形式化描述所需术语关系详解
C++ memory order循序渐进(三)—— 原子变量上组合应用memory order实现不同的内存序
C++ memory order循序渐进(四)—— 在std::atomic_thread_fence 上应用std::memory_order实现不同的内存序
互斥锁:悲观锁。在一个线程获得锁,其他线程都在等待的时候,这个线程被OS调度切走了,那么会导致整个程序都在等待。所以不是lock-free。
lock-free:允许单独的线程个体阻塞,但是会保证系统整体上的吞吐,如果一个算法对应的程序的线程在运行了足够长时间的情况下,至少有一个线程取得了进展,那么我们说这个算法是lock-free的。不管OS如何调度线程,至少有一个线程在做有用的事。
lock-free价值在于保证至少一个线程在做有用的事情,而不是绝对的高性能。
wait-free:工业可用的还在探索中。
order受编译时和cpu执行时的重排,而memory order提供了跨平台的限制重排
Sequentially-consistent ordering:这是约束最强的ordering,保证所有的线程观察一组内存操作的顺序完全一样。即全部线程对内存的观察,不会产生二义性。
mutex
scoped_lock:在作用域内占有至少一个互斥锁
std::scoped_lock lock(e1.m, e2.m)
等价于:
std::lock(e1.m, e2.m);
// 这里加lock_guard只是为了离开作用域时自动解锁
std::lock_guard<std::mutex> lk1(e1.m, std::adopt_lock);
std::lock_guard<std::mutex> lk2(e2.m, std::adopt_lock);
其中adopt_lock:声明互斥锁已被锁,不用再加锁
等价于:
// 这里lock_guard不加锁,lock才加锁
std::lock_guard<std::mutex> lk1(e1.m, std::defer_lock);
std::lock_guard<std::mutex> lk2(e2.m, std::defer_lock);
std::lock(lk1, lk2);
其中defer_lock:表示不进行加锁(用于延迟加锁)
try_lock:
尝试lock多个互斥锁。都锁成功了则返回-1,否则返回锁失败的锁索引。
unique_lock:
可移动的通用互斥锁包装器。
以及可用于条件变量。(条件变量也可以用其他包装器)
recursive_mutex:
在一个线程内能被递归锁定的互斥锁。例如在一个线程,两个需要锁的函数间调用。
condition_variable
condition_variable类:
wait函数:原子地释放 lock ,阻塞当前线程,并将它添加到等待在 *this 上的线程列表。线程将在执行 notify_all() 或 notify_one() 时,或度过相对时限 rel_time 时被解除阻塞。
解除阻塞时,重获lock。
wait函数只支持unique_lock,是为了最高效率。
wait有两个重载版本,1:、传入lock;2、传入lock和pred函数。
如果有pred函数,需要pred返回True时,唤醒才会生效。
wait_for函数:
唤醒有两种情况:1、被notify;2、超时
有两种重载版本:1、传入lock time;2、传入lock time pred
重载版本1中,notify或超时都会唤醒。版本2中,notify且pred()=true(和wait一样) 或 超时 会唤醒。
两个版本的返回值不同,前者返回cv_status,后者返回bool。
https://zh.cppreference.com/w/cpp/thread/condition_variable/wait_for
通知线程在notify时不必保有和等待线程相同的互斥锁,(即notify之前可以手动先unlock锁),因为这样等待线程在被通知之后,立马又会再次阻塞(等锁),等待通知线程释放锁。
notify_all_at_thread_exit全局函数:
操作:1. 销毁 thread_local 对象, 2. 解锁互斥, 3. 通知 cv
(这里也是先解除互斥锁,再notify)
thread_local
SFINAE
C++模板进阶指南:SFINAE
Substitution failure is not an error
在多个模板签名完全一样的时候,会造成redefination,但其实这些模板对于不同的类型,可能会有具体不同的具体操作。这时候要在模板函数声明的时候,对参数加上限定。
例如,可以借助std::enable_if,使得T在满足一定的规则下,这个模板函数才是有效的,也即才会生效。如果不满足规则,则函数根本就不会生效。
在需要使用enable_if的时候,需要考虑是否能用其他替代:
- 重载(对模板函数)
- 偏特化(对模板类而言)
- 虚函数
Expression SFINAE:
用std::decay_t和decltype对形参的匹配规则加以限定。
还有一种情况只能用SFINAE:universal reference
这种情况只能用模板。
因此在这种情况下,只能用模板,再加上类型限定。
SFINAE最主要的作用:是保证编译器在泛型函数、偏特化、及一般重载函数中遴选函数原型的候选列表时不被打断。除此之外,它还有一个很重要的元编程作用就是实现部分的编译期自省和反射。