深入理解C++11 读书笔记(六) 提高性能及操作硬件能力

8 篇文章 0 订阅
本文深入探讨C++11的新特性,包括利用constexpr实现编译期计算,理解变长模板的使用,以及原子类型和线程局部存储在多线程编程中的应用。同时介绍了quick_exit和at_quick_exit在优化程序退出时的角色。
摘要由CSDN通过智能技术生成

常量表达式

  • constexpr关键字,修饰函数,数据,构造函数等,是的编译器在编译器进行计算,编译时期常量。
  • constexpr修饰函数时,要求:
    1. 只有单一的return命令,这里指运行时命令,编译器运行的命名(猜测)可以多个,比如static_assert。
    2. 必须有返回值,非void
    3. 使用前必须有定义(不是声明,编译时会进行计算,因此必须先有定义)
    4. return 表达式中不能使用非常量表达式的函数,全局数据,且必须是常量表达式
  • 用constexpr 修饰值,称为常量表达式值(constant-expression value)。要求:
    1. 使用前初始化。
    2. 标准要求编译时浮点常量表达式值的精度大于等于运行时浮点常量的精度
    3. 自定义的类要使用常量表达式修饰,需要自定义常量构造函数(函数体空,初始化列表只能常量赋值,常量表达式不能作用于virtual成员函数,不需要单独定义非常量表达式版本的构造函数)
  • 常量表达式可以用于模板,如果修饰模板函数后,实例化后不满足常量,constexpr会自动被忽略。

变长模板

  • C++11支持C99的变长宏。printf使用了C语言的函数变长参数特性,使用变长函数variadic function。通常一个变长函数如:
#include <stdio.h>
#include <stdarg.h>
double SumOfFloat(int count,...){
va_list ap;
double sum=0;
va_start(ap,count);//获得变长列表的句柄ap
for(int i=0;i<count;i++)
sum+=va_arg(ap,double);//每次获得一个参数
va_end(ap);
return sum;
}
int main(){
printf("%f\n",SumOfFloat(3,1.2f,3.4,5.6));//10.200000
}

代码中需要使用va_arg,va_list,va_start,va_end来辅助。
变长函数可能的实现方式
这里例子中,在使用va_arg(ap,double)的时候去变长参数列表获得指定参数,而打印函数完全依赖%s,%d这样的转译字。对于没有定义转译字的非POD类型,使用变长函数就会出未知问题。
这种实现方式在函数本身无法知晓参数数量或类型

  • C++引入了更加现代化的变长参数的实现方式,将类型和变量同时传给变长参数的函数。C++98规定模板必须固定长度的参数个数,而C++11打破了这个限制,可以是变长个数。variadic template,变长模板。
  • 声明tuple是一个变长类模板。
template<typename ...Elements>class tuple.

Elements称为参数模板包。”template parameter pack”。参数模板包也可以是非类型的。

template<int ...A> Multiply.

使用参数模板包时用到解包(pack expansion)

template<typename ...Elements>
class A:public B<Elements...>

实现一些变长模板定义时可以使用递归式的定义方法,比如实现tuple

template<typename ...Elements> class tuple; //边长模板声明
template<typename Head,typename ...Tail>    //递归的片特化定义
class tuple:private tuple<Tail...>{
    Head h;
}

template<>class tuple<>{}   //递归边界

非类型模板例子

template<int ...A>struct Multiply;
template<int first, int ...last>
struct Multiply{
    //编译期进行计算
    static const int val = first * Multiply<int ...last>::val;
}
template<>struct Multiply<>{static const int val = 1;};

void main(){
    int v = Multiply<11,12,32>::val;
}
  • 变长模板函数也可以使用变长参数包,要求函数参数包必须唯一且是最后一个
template<typename T1, typename ...T2>
void Print(T1 t, T2...t2){
    cout<<t;
    Print(t2...);
}
  • 变长模板进阶。C++11标准定义了参数包可以在7个位置展开
    1. 表达式
    2. 初始化列表
    3. 基类描述列表
    4. 类成员初始化列表
    5. 模板参数列表
    6. 通用属性列表
    7. lambda函数捕捉列表
  • C++11引入新的操作符 sizeof… 用来计算参数包中参数的个数

原子类型和原子操作

  • c++11之前,并行线程和多线程主要使用 POSIX线程(Pthread)和OpenMP编译器指令两种编程模型来完成程序的线程化.POSIX标准中的线程部分(Pthread)API完成线程的创建、共享、同步等,主要用于C语言,在类Unix系统FreeBSD、NetBSD、OpenBSD、GNU/Linux、Mac OS X以及windows上都有实现。不过OpenMP使用更加便利,OpenMP将大部分线程化的工作交给了编译器,需要识别线程化区域的工作交给程序员,这样使用更加简单。OpenMP得到了业界主流软硬件厂商的支持,现行最有影响力的线程优化编程模型。
  • C++11中引入对多线程的支持,使得进行c/c++编程使用多线程时不必依赖第三方库。其中最重要的部分是引入原子类型的概念。
  • 通常的原子操作通过互斥锁实现,c++11将原子操作抽象为对原子对象的操作,编译器保证原子对象在线程之间互斥访问。c++11内置原子类型定义在里。
    原子类型和内置类型对应表
TypedefNamen
Fullspecialization
std::atomic_charstd::atomic<char>
std::atomic_scharstd::atomic<signed char>
std::atomic_ucharstd::atomic<unsigned char>
std::atomic_shortstd::atomic<short>
std::atomic_ushortstd::atomic<unsigned short>
std::atomic_intstd::atomic<int>
std::atomic_uintstd::atomic<unsigned int>
std::atomic_longstd::atomic<long>
std::atomic_ulongstd::atomic<unsigned long>
std::atomic_llongstd::atomic<long long>
std::atomic_ullongstd::atomic<unsigned long long>
std::atomic_char16_tstd::atomic<char16_t>
std::atomic_char32_tstd::atomic<char32_t>
std::atomic_wchar_tstd::atomic<wchar_t>

自定义类型可使用原子模板
std::atomic<T> t

  • 顺序一致性:线程中运行的顺序和程序员看到的代码的顺序一致。
  • 通常情况内存模型是一种硬件上的概念,表示机器指令是以什么样的顺序被处理器执行的。
  • 强顺序:完全按照指令输入顺序执行。弱顺序:不相关的指令被打乱顺序。
  • 强顺序模型 x86,sparc. 弱顺序模型 PowerPC Alpha ArmV7。在弱内存模型如果要保证指令的执行顺序,通常汇编指令中加入一条内存栅栏的命令(memory barrier),比如PowerPC的sync,不过sync对PowerPC处理器性能影响很大
  • 要保证顺序一致性,需要编译器编译不改变指令顺序。还需要处理器对原子操作执行顺序不变,这对x86这样的强顺序体系结构而言没有问题,powerPC这样的弱顺序体系结构需要在每次原子操作后加入内存栅栏

线程局部存储

  • 线程局部存储(TLS thread local storage) :线程生命周期和线程可见变量
  • 各个编译器有自己的TLS标准,g++/clang++/xlc++使用关键字 __thread
__thread int errCode;

在全局变量和静态变量加上关键字__thread,每个线程有独立的变量拷贝。
* C++11对TLS标准做了统一规定,使用thread_local 修饰符

int thread_local errCode;

thread_local修饰的变量在线程开始时初始化,线程结束时无效,&取地址也只能取当前线程的地址。


quick_exit和 at_quick_exit

  • terminatec++异常退出:异常没有被捕捉或noexcept修饰的函数抛出异常。默认情况下调用更底层的abort(c异常退出)
  • exit正常退出,自动调用析构函数和atexit注册的函数。注册函数调用顺序和注册顺序相反,和析构类似。
  • 由于exit需要调用析构函数,将内存块逐个交还给操作系统。而进程结束后,操作系统会统一回收,因此有时不需要exit耗费时间调用析构函数,为此,c++11引入quick_exit函数,也有注册函数at_quick_exit。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值