程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++11:提高性能及操作硬件的能力

C++11:提高性能及操作硬件的能力

編輯:C++入門知識

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 
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  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是一個元組。模板參數包如下:

template
class tuple;
tuple<1,2,3> one;//模板實例化
tuple twol

Elements就是一個模板參數包,有了這個模板包tuple模板類可以接受任意多個參數實例化模板,其本質就是將多個模板參數打包成Elements然後通過解包方式釋放模板參數。

函數參數包如下:

template
void f(T...args);
其中T是模板參數包,args是函數參數包。


3 原子類型

通常線程間同步通信都會想到mutex,condition_variable這樣的大殺器,但是通常一些簡單的線程同步可以利用原子類型來進行。原子操作:要麼不做,要麼一步完成。關於原子類型及其操作見:點擊打開鏈接,原子操作對於lock free編程大有好處。

這裡講一下內存序memory order,編譯器會對源代碼做一些優化,使得一些語句順序優化後可引起線程間產生錯誤,比如:

#include 
#include 
#include 
using namespace std;

atomic a;
atomic 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 
#include 
#include 
using namespace std;

atomic a;
atomic 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 
#include 
#include 
#include 
using namespace std;
 
atomic ptr;
atomic 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修飾符來定義一些類的線程局部變量,往往容易造成內存洩漏.


5 線程退出方式

teminate終止進程,它調用底層的abort,它們不會調用任何析構函數,向系統發送一個SIGABRT信號,若程序員定義了該信號的信號處理程序清理資源的話操作系統會釋放資源。它們屬於異常退出。

exit和main中的return一樣是正常退出,會執行析構操作,如果注冊了處理函數則還會執行清理函數。如:

#include 
#include 
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 
#include 
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);
}






  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved