文章目录
1、常量表达式
1.1 运行时常量
使用const关键字描述的都是一些运行时常量性;,即运行时数据不可改变;
以下是不通过编译:以下使用到的是编译器常量,故将不可以使用const;
1.2 编译时常量
C++11提供constexpr关键字,该关键字是编译时期的常量;
上述代码中,若将const转换为constexpr将会得到解决;
【注意】:
- 不能将该关键字作用于virtual成员函数上;
-
常量表达式函数
满足条件:
- 函数体只有单一的return语句;
- 特例可以在return前使用static_assert()、using、typedef等;
- 函数必须有返回值,不能为void;
- 在使用前必须已有定义(由于在编译时需要将其计算);
- return表达式中,不能使用非常量表达式的函数、全局数据,且必须是一个常量表达式;
- 表达式中不能包含运行时才能确定的值;
- 不能出现赋值;
- const int f() { return 1; } === > constexpr int g() { return e(); }
常量表达式值
当该变量被constexpr所修饰,则该变量为常量表达式值,其必须被一个常量表达式赋值;
当`constexpr int j = 1`此类变量位于全局中,若代码中没有显示地使用它的地址,可以不为他生成数据;
【浮点常量】:
- 要求编译时的浮点常量表达式精度至少要>=运行时的浮点数常量的精度;
常量构造函数
【常量表达式的构造函数】:当常量表达式修饰自定义类型不能通过编译时
- 将定义一个常量构造函数即可;
struct MyType {
constexpr MyType(int x) : i(x){}
int i;
};
constexpr MyType mt = {0};
定义该构造函数的约束:
- 函数体必须为空;
- 初始化列表只能由常量表达式来赋值;
其他应用
常量表达式可以用于函数模板;
- 当该函数的实例化结果不满足常量表达式的需求,则constexpr将会被自动忽略;
【注意】:
- 并不是使用constexpr,编译器就一定会对其在编译期进行优化计算;
2、变长模板
2.1 变长函数
在C中就具有函数变长参数特性;
va_list:传入参数,辅助获取参数;
va_start:对参数进行初始化;
va_arg:从参数列表一个一个取出;
【注意】:
- 函数本身无法知道参数类型以及参数的数量;
- 对于没有定义转移字的非POD,使用变长函数会导致未定义;
int SumOfFloat(int cnt, ...) {
va_list ap;
int sum = 0;
va_start(ap, cnt);
for (int i = 0; i < cnt; ++i) {
sum += va_arg(ap, int);
}
va_end(ap);
return sum;
}
int main() {
cout << SumOfFloat(3, 12, 10, 1) << endl;
return 0;
}
2.2 变长模板参数
STL【tuple】| tuple源码刨析,为什么能传入自定义个数参数?
template<typename... Elements>class tuple;
其中Elements为模板参数包,tupple可以接收任意参数;
// 上述tuple为类型模板,以下为非类型模板
template<long... nums>
struct Multiply;
template<long first, long... last>
struct Multiply<first, last...> {
static const long val = first * Multiply<last...>::val;
};
template<>
struct Multiply<> {
static const long val = 1;
};
void test() {
cout << Multiply<1, 2, 3>::val << endl; // 6
}
案例:C++11实现printf函数
void Printf(const char* s) {
while (*s) {
if(*s == '%' && *++s != '%') {
throw runtime_error("invalid format string: missing argument");
}
cout << *s++;
}
}
template<typename T, typename... Args>
void Printf(const char* s, T value, Args... args) {
while (*s) {
if(*s == '%' && *++s != '%') {
cout << value;
return Printf(++s, args...);
}
cout << *s++;
}
throw runtime_error("extra arguments provided to Printf");
}
void test() {
Printf("%s, %d", "word", 12);
}
2.3 进阶
C++11中,在以下位置可以展开参数包:
- 表达式;
- 初始化列表;
- 基类描述列表;
- 类成员初始化列表;
- 模板参数列表;
- 通用属性列表;
- lambda函数的捕捉列表;
// 多重继承
template<typename... A> class T:private B<A>... {};
解包==> class T<X, Y>: private B<X>, private B<Y> {};
template<typename... A> class T:private B<A...> {};
解包==> class T<X, Y>:private B<X, Y> {};
sizeof…
可以计算参数包中的参数个数;
==> sizeof...(A); // 获取参数包中A的个数
两个模板参数包的使用
template<typename A, typename B>
struct S {
// 构造函数
S() {
cout << "A, B" << endl;
}
};
template<
template<typename... > class T, typename... TArgs,
template<typename... > class U, typename... UArgs
>
struct S<
T <TArgs... >,
U <UArgs... >
>{
// 构造函数
S() {
cout << "Args..." << endl;
}
};
void test() {
S<int, double> a;
S<tuple<char, int>, tuple<int, char>> s;
}
变长模板和完美转发
struct A {
A() {}
A(const A& a) {
cout << "Copy" << __func__ << endl;
}
A(A&& a) {
cout << "Move" << __func__ << endl;
}
};
struct B {
B() {}
B(const B& b) {
cout << "Copy" << __func__ << endl;
}
B(B&& b) {
cout << "Move" << __func__ << endl;
}
};
template<typename... T>
struct MultiTypes;
template<typename T1, typename... T>
struct MultiTypes<T1, T...> : public MultiTypes<T...> {
T1 t1;
MultiTypes<T1, T...>(T1 a, T... b) : t1(a), MultiTypes<T...>(b...) {
cout << "MultiTypes" << endl;
}
};
template<> struct MultiTypes<> {
MultiTypes<>() {
cout << "MultiTypes<>()" << endl;
}
};
template<
template<typename...>
class VariadicType,
typename... Args
>
VariadicType<Args...> Build(Args&&... args) {
return VariadicType<Args...>(std::forward<Args>(args)...);
}
void test() {
A a;
B b;
Build<MultiTypes>(a, b);
}
3、原子类型与原子操作
3.1 原子操作与C++11原子类型
原子操作:多线程中最小的不可并行化的操作,故在多线程下,只有一个线程对其进行操作(不会出现例外);
- 通过互斥的访问来保证;
- 不需要通过加锁去锁的繁杂操作;
用法:
- atomic<T> t ==> 可以通过T来指定任意类型;
- 原子类型只能从其模板参数类型中构造,不允许原子类型进行拷贝、移动构造;
- 可以隐式转换T类型;
【atomic_flag】:该类型为无锁的,可以用该类型来实现一个自旋锁;
#include <atomic>
#include <thead>
#include <iostream>
using namespace std;
// 测试案例
atomic_llong total{0}; // 原子数据类型
void func(int) {
for (long long i = 0; i < 100000000LL; ++i) {
total += i;
}
}
void test() {
thread t1(func, 0);
thread t2(func, 0);
t1.join();
t2.join();
cout << total << endl;
}
void test2() {
atomic<int> a;
int b = a; // ==> int b = a.load();
a = 1; // ==> a.store(1)
}
atomic_flag实现自旋锁
atomic_flag lock = ATOMIC_FLAG_INIT; // 设置为false状态
void f(int n){
// test_and_set会不断设置新值并返回旧值
while (lock.test_and_set(std::memory_order_acquire))
cout << "Waiting from thread " << n << endl;
cout << "Thread" << n << " starts working" << endl;
}
void g(int n) {
cout << "Thread " << n << " is going to start" << endl;
lock.clear(); // 将lock设置false
cout << "Thread " << n << " starts working" << endl;
}
void test() {
lock.test_and_set();
thread t1(f, 1);
thread t2(g, 2);
t1.join();
sleep(5);
t2.join();
}
// 当t1执行后,lock为true,则一直自旋等待,直到t2加入后,clear将其设置为false,t1才终止自旋,执行后续代码;
3.2 内存模型
【硬件内存模型】
当执行按照一定的顺序执行,则该内存模型为强顺序;
当执行不一定按照顺序执行,则该模型为弱顺序;
在多线程中,当线程间的内存数据被改变的顺序与机器指令中声明的不一致,则为弱顺序;
而上述的原子操作时强顺序的;
- 弱顺序的指令执行性能一般较高,当没有要求需要顺序执行时,使用该方式可以给程序提高性能;
【C++内存模型】
- 编译器保证原子操作间的指令顺序不变;
- 处理器对原子操作的汇编指令的执行顺序不变;
部分平台会阻止编译器优化,加入内存栅栏来保证atomic的顺序一致性,将会大大影响性能;
【如何解决不阻止编译器优化】:让程序为原子操作指定内存顺序(memory_order);
能够让编译器重排序或处理器乱序执行;
memory_order_relaxed :不对执行顺序做任何保证;
memory_order_acquire :本线程中,所有后续的读操作必须再本条原子操作完成后执行;
memory_order_release :本线程中,所有之前的写操作完成后才能执行本条原子操作;
memory_order_acq_rel :同时包含memory_order_acquire、memory_order_release;
memory_order_consume :本线程中,所有后续的有关原子类型操作,后再本条原子操作完成后执行;
memory_order_seq_cst :全部存取都按顺序执行(可能会导致性能损失);
- 上述中,可以使用memory_order_release和memory_order_consume产生;
atomic<int> a;
atomic<int> b;
int func1(int) {
int t = 1;
a.store(t, std::memory_order_relaxed);
b.store(2, std::memory_order_release); // 本原子操作前,所有的写原子操作必须完成
}
int func2(int) {
while (b.load(std::memory_order_acquire) != 2); // 本原子操作必须完成才能执行后续的读原子操作
cout << a.load(std::memory_order_relaxed) << endl;
}
void test() {
thread t1(func1, 0);
thread t2(func2, 0);
t1.join();
t2.join();
}
4、线程局部存储
即拥有线程生命周期及线程可见性的变量(单线程中的全局/静态变量被用到多线程中被线程共享);
当一个全局变量被用于多线程中时,该变量在线程间共享,总是会导致只能记录其中一个线程的数据,故为每个线程指派一个全局的变量(TLS);
C++11通过thread_local修饰符变量,在线程开始时被初始化,结束时即失效;
5、quick_exit
在C++11之前提供terminate和abort、exit等退出函数;
【terminate】:
- 当异常被抛出时,即会调用terminate,而其实际是调用abort;abort不会调用任何的析构函数,它将给系统抛出一个悉尼号,系统将会默认释放进程所有资源,该方式有时候会导致一些交互进程出现问题;
【exit】:
- 正常退出程序,会调用析构函数以及atexit做一些清理工作,调用析构将内存还给系统时将会很耗时;
C++11提供quick_exit:
- 该函数不执行析构,只是让程序终止,也会调用at_quick_exit做一些清理工作;