1 constexpr编译时期常量
constexpr用于函数:
constexpr int get(){return 10};
int array[get()];//get()返回一个编译期常量可以用于声明数组大小
constexpr int a=get();//a是一个编译期常量
int b=get();//此时get当一个普通函数使用,b不再是常量
常量表达式函数必须满足:
1) 函数体只能有一句return
constexpr int get(){
int a=10;//int a=10不可以,但是像static_assert这样编译时确定的还是可以的,assert是运行时断言也不可以
return 10;
}
2) 函数必须有返回值,所以constexpr void get();是不可以的
3) 使用前必须定义,即在用函数初始化一个常量时必须先定义
4) return不能返回非常量的表达式的函数、全局数据,如:constexpr int get(){ return fun();}返回若fun不是constexpr函数是不可以的
C++11规定浮点数常量表达式是不允许的,因为浮点环境可能改变
常量表达式值只能被常量表达式赋值,即:constexpr int a=1;是可以的,但是constexpr int a=i;是错误的。
C++11中constexpr是不能修饰自定义类型的,但是可以定义常量构造函数:
#include <iostream>
using namespace std;
struct Date {
constexpr Date(int y, int m, int d):year(y), month(m), day(d) {}//常量构造函数
constexpr int GetYear() { return year; }
constexpr int GetMonth() { return month; }
constexpr int GetDay() { return day; }
private:
int year;
int month;
int day;
};
constexpr Date PRCfound {1949, 10, 1};//常量类型由常量构造函数
Date PRCfound(1949,10,1)//调用constexpr Date等于调用普通构造函数,也就是说此时constexpr忽略
constexpr int foundmonth = PRCfound.GetMonth();//成员函数也需要constexpr int Getmonth声明为constexpr
int main() { cout << foundmonth << endl; } // 10
常量表达式用于模板函数
struct NotLiteral{
NotLiteral(){ i = 5; }
int i;
};
template <typename T> constexpr T ConstExp(T t) {//该模板函数用于返回一个constexpr型的NotLiteral
return t;
}
void g() {
NotLiteral nl;
NotLiteral nl1 = ConstExp(nl);//此时当普通函数使用
constexpr NotLiteral nl2 = ConstExp(nl); // 无法通过编译,nl不是常量表达式
constexpr int a = ConstExp(1);
}
2 变长模板
一个变长的宏__VA_ARGS__可以输出变长参数。C++11中提出了变长模板参数包和函数参数包。c++11中std::tuple就是个变长参数模板类,它是pair类型的泛化,可以指定多个元组,如:tuple<int,float,double>是一个元组。模板参数包如下:
template<typename...Elements>
class tuple;
tuple<1,2,3> one;//模板实例化
tuple<int,double,float> twol
Elements就是一个模板参数包,有了这个模板包tuple模板类可以接受任意多个参数实例化模板,其本质就是将多个模板参数打包成Elements然后通过解包方式释放模板参数。
函数参数包如下:
template<typename...T>
void f(T...args);
其中T是模板参数包,args是函数参数包。
3 原子类型
通常线程间同步通信都会想到mutex,condition_variable这样的大杀器,但是通常一些简单的线程同步可以利用原子类型来进行。原子操作:要么不做,要么一步完成。关于原子类型及其操作见:点击打开链接,原子操作对于lock free编程大有好处。
这里讲一下内存序memory order,编译器会对源代码做一些优化,使得一些语句顺序优化后可引起线程间产生错误,比如:
#include <thread>
#include <atomic>
#include <iostream>
using namespace std;
atomic<int> a;
atomic<int> b;
int Thread1(int) {
int t = 1;
a = t;
b = 2;
}
int Thread2(int) {
while(b != 2)
; // 自旋等待
cout << a << endl; // 总是期待a的值为1,但是由于编译器优化,可能使得b=2在a=t之前执行
}
int main() {
thread t1(Thread1, 0);
thread t2(Thread2, 0);
t1.join();
t2.join();
return 0;
}
在C++11前linux内核采用了一个叫做内存栅memory barrier来强制汇编代码执行顺序。C++11允许供程序员使处理器以指定的顺序执行机器指令的机制memory order。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_acquire的弱化 |
memory_order_seq_cst | 全部存取指令按照顺序执行,最强顺序,称为顺序一致性,是C++11中所有atomic原子操作的默认值 |
memory_order_acquire/release的应用实例:
#include <thread>
#include <atomic>
#include <iostream>
using namespace std;
atomic<int> a;
atomic<int> b;
int Thread1(int) {
int t = 1;
a.store(t, memory_order_relaxed);
b.store(2, memory_order_release); // 本原子操作前所有的写原子操作必须完成
}
int Thread2(int) {
while(b.load(memory_order_acquire) != 2); // 本原子操作必须完成才能执行之后所有的读原子操作
cout << a.load(memory_order_relaxed) << endl; // 1
}
int main() {
thread t1(Thread1, 0);
thread t2(Thread2, 0);
t1.join();
t2.join();
return 0;
}
memory_order_release/consume的应用实例:
#include <thread>
#include <atomic>
#include <cassert>
#include <string>
using namespace std;
atomic<string*> ptr;
atomic<int> data;
void Producer() {
string* p = new string("Hello");
data.store(42, memory_order_relaxed);
ptr.store(p, memory_order_release);
}
void Consumer() {
string* p2;
while (!(p2 = ptr.load(memory_order_consume)))//#1#所有关于后序关于ptr的操作都必须在本原子操作之后
;
assert(*p2 == "Hello"); // 总是相等
assert(data.load(memory_order_relaxed) == 42); // 可能断言失败,因为#1#处只保证了ptr的顺序,而data.load可能先于ptr.load
}
int main() {
thread t1(Producer);
thread t2(Consumer);
t1.join();
t2.join();
}
4 线程私有数据
在POSIX中在变量前加一个__thread就声明该变量为线程私有数据,
基本类型(如(unsigned) int,long, char,指针,c类型的结构体等 )可以采用用 __thread修饰符来定义线程局部变量.
示例如下:
__thread int i;
extern __thread struct state s;
static __thread char *p;
像 string 等类是不能直接用 __thread 修符的,只能用其指针类型的.如下面的示例是错误的.
thread std::string thread_name;
下面是正确的:
thread std::string * p_thread_name;
使用 __thread修饰符来定义一些类的线程局部变量,往往容易造成内存泄漏.
在C++11中,只需要使用thread_local修饰变量即可变成线程局部数据。如:
int thread_local var;
5 线程退出方式
teminate终止进程,它调用底层的abort,它们不会调用任何析构函数,向系统发送一个SIGABRT信号,若程序员定义了该信号的信号处理程序清理资源的话操作系统会释放资源。它们属于异常退出。
exit和main中的return一样是正常退出,会执行析构操作,如果注册了处理函数则还会执行清理函数。如:
#include <cstdlib>
#include <iostream>
using namespace std;
void openDevice() { cout << "device is opened." << endl; }
void resetDeviceStat() { cout << "device stat is reset." << endl; }
void closeDevice() { cout << "device is closed." << endl; }
int main() {
atexit(closeDevice);
atexit(resetDeviceStat);//注册清理函数,清理函数的执行和注册时的顺序相反
openDevice();
exit(0);
}
但是exit执行析构可能耗时,因此C++11引入了quick_exit,该函数不执行析构只是使程序终止。如:
#include <cstdlib>
#include <iostream>
using namespace std;
struct A { ~A() { cout << "Destruct A. " << endl; } };
void closeDevice() { cout << "device is closed." << endl; }
int main() {
A a;
at_quick_exit(closeDevice);//与quick_exit配套的清除函数的注册
quick_exit(0);
}