c++11新特性總結(轉)


1、類型與變量相關

1.1、nullptr:

取代了NULL,專用於空指針


1.2、constexpr:

近似const, 可以修飾變量,也可以修飾函數,

修飾變量如:

const int global = 100;

int main () {

int temp = 100;

constexpr int a = 1; //right

constexpr int b = global; //right

constexpr int c = temp; //wrong

}

既可以賦值字面常量也可以賦值以const變量


重點:constexpr修飾的函數,生效於編譯時而不是運行時, 重點應用於修飾函數使其在編譯期大幅度被解釋

被constexpr修飾的函數,無論是普通函數,還是類成員函數,必須是編譯器可計算得到結果,即字面常量,不可是運行時才能獲取的內容

例1:

constexpr int calc_in_compile_0 () {
    return 100;
}
constexpr int calc_in_compile_1 (int a) {
    return a * 100;
}
constexpr int calc_in_compile_2 (int b, int c) {
return c * calc_in_compile_1(b);
}

EXPECT_EQ(100, calc_in_compile_0());

constexpr int a = 1;

EXPECT_EQ(100, calc_in_compile_1(a));

EXPECT_EQ(10000, calc_in_compile_2(a, calc_in_compile_1(a)));


例2:

代替了"const _max = INT_MAX"

static constexpr int max () {
     return INT_MAX;
}
static constexpr int min () {

return INT_MIN;
}

constexpr int _max = max(), _min = min();


例3:

class Calc {
    double a_;
public:
    /*構造函數在這里,必須用constexpr修飾,因為類成員函數是用constexpr修飾的*/
    constexpr Calc(double a):a_(a) {}


    constexpr double GetFabs() const {
return std::fabs(a_);
}
    constexpr double GetAbs() const {
        return std::abs(a_);
    }
    constexpr double GetSquare() const {
        return a_ * a_;
    }
};

    constexpr Calc calc(5.1);
    constexpr double _fabs = calc.GetFabs();
    ///_fabs = 10.0;
    LOG(INFO) << "fabs: " << _fabs;
    double _abs = calc.GetAbs();
    LOG(INFO) << "abs: " << _abs;
    _abs = 10.0;
    LOG(INFO) << "abs: " << _abs;
    double _square = calc.GetSquare();
    LOG(INFO) << "square: " << _square;
    _square = 10.0;
    LOG(INFO) << "square: " << _square;

1.3、using取代typedef:

typedef double db; //c99

using db = double; //c++11

typedef void(*function)(int, int);//c99,函數指針類型定義

using function = void(*)(int, int);//c++11,函數指針類型定義

using kvpairs = std::map<std::string, std::string>; //c++11

using CompareOperator = std::function<int (kvpairs &, kvpairs &)>; //c++11

using query_record = std::tuple<time_t, std::string>; //c++11


template<class T> using twins = std::pair<T, T>; //更廣泛的還可以用於模板


1.4、auto & decltype:

auto讓編譯器通過初始值來推算變量的類型。當然,其定義的變量必須要有初始值

auto a = 1;

auto task = std::function<void ()>([this, a] {

................

});

decltype(變量)可以獲取變量的類型

auto a = 1;

decltype(a) b = 2;

decltype(b) c = add(a, b);

注意下,decltype((a) )的結果是引用,此時創建新的變量就將會報錯,或者說:

int &b = a;

decltype(b) c;//也報錯,因為b是a的引用,decltype(b)就會報錯,效果同decltype((a))

 

此外,auto在容器的迭代器的使用,大大降低了代碼開發量

對於vector、map、set等容器

for (auto i: V) {

......

}

1.5、字符串和數值類型的轉換

以前的atoi、itoa等等成為歷史

to_string:itoa成為歷史

stoi、stol、stoul、stoll、stoull、stof、stod、stold:atoX成為歷史

1.5、random_device

生成隨機數,免去了以前需要自行調用srand初始化種子的步驟,因為有時忘了初始化結果導致錯誤。用法:

std::random_device rd;

int randint = rd();

1.6、std::ref和std::cref

分別對應變量的引用和const引用,主要用於作為c++11函數式編程時傳遞的參數

1.7、std::chrono時間相關

比以前的時間方便了許多:

std::chrono::duration<double> duration //時間間隔

std::this_thread::sleep_for(duration); //sleep

LOG(INFO) << "duration is " << duration.count() << std::endl;

std::chrono::microseconds  //微秒

std::chrono::seconds //秒

end = std::chrono::system_clock::now(); //獲取當前時間

1.8、原子變量

std::atomic<XXX>

用於多線程資源互斥操作,屬c++11重大提升,多線程原子操作簡單了許多

事實上基於c++11實現的無鎖隊列,讓boost::lockfree無鎖隊列也將成為歷史

1.9、正則表達式std::regex

惡心的C正則(regex.h)和boost正則成為歷史

1.10、編譯期斷言static_assert

static_assert是用於涉及模板的assert,編譯期就能發現不滿足的情況,無需等到運行時出現core

如下最后一個被注掉的static_assert如果放開,則無法通過編譯。

  1. template<class T> class C {  
  2.     T data1_;  
  3.     int data2_;  
  4. public:  
  5.     C(T data1, int data2):data1_(data1), data2_(data2) {  
  6.         /*if the condition is not satisfiedm, would be errored by compiler in compling*/  
  7.         //static_assert(sizeof(T) > 4, "sizeof(T) is not larger than 4");  
  8.         static_assert(sizeof(T) >= 4, "sizeof(T) is not larger than 4");  
  9.         //static_assert(data2_ >= 10, "could not use static_assert here! condition must could be calced in compling!");  
  10.     }  
  11. };  
  12.   
  13. TEST(test_static_assert, test) {  
  14.     C<double> c(1.1, 1);  
  15. }  



2、容器

2.1、tuple & 花括號初始化

元組的出現,和python拉齊了,c++也實現了函數可以多個返回值

using  result = std::tuple<int, char, double>;

result res {1,'a',1.0};

return res;

return {2, 'b',100.0};

std::vector<int> v{1, 2, 3, 4, 5};


using res_tp = std::tuple<bool, char, int, float, double>;

res_tp res(true, 'b', 11, 1.1, 100.1);

LOG(INFO) << "res.bool: " << std::get<0>(res);
    LOG(INFO) << "res.char: " << std::get<1>(res);
    LOG(INFO) << "res.int: " << std::get<2>(res);
    LOG(INFO) << "res.float: " << std::get<3>(res);
    LOG(INFO) << "res.double: " << std::get<4>(res);

以上都是合法的,尤其對vector,簡單的測試程序再不需要一行行的push_back了!

2.2、hash正式進入stl

unordered_map、unordered_set、unordered_multimap、unordered_multiset。extstl擴展方式的使用hash成為歷史。

2.3、emplace:

作用於容器,區別於push、insert等,如push_back是在容器尾部追加一個容器類型對象,emplace_back是構造1個新對象並追加在容器尾部

對於標准類型沒有變化,如std:;vector<int>,push_back和emplace_back效果一樣

如自定義類型class A,A的構造函數接收一個int型參數,

那么對於push_back需要是:

std::vector<A> vec;

A a(10);

vec.push_back(a);

對於emplace_back則是:

std::vector<A> vec;

vec.emplace_back(10);

改進點是什么?改進點:避免無用臨時變量。比如上面例子中的那個a變量

2.4、int> v{1, 2, 3, 4, 5};  

  • v.push_back(1);  
  • std::cout << "before shrink_to_fit: " << v.capacity() << std::endl;  
  • v.shrink_to_fit();  
  • std::cout << "after shrink_to_fit: " << v.capacity() << std::endl;  

可以試一試,減少了很多,有一定價值


3、對於類

3.1、構造函數

3.1.1、控制構造函數

1、default關鍵字生成默認構造函數和析構函數

default的默認構造方式可生成:默認無參數的構造函數、拷貝構造函數、賦值構造函數

析構函數同樣可以由default默認構造

 

  1. class A11 {  
  2.     int data;  
  3. public:  
  4.     A11() = default;  
  1.       ~A11() = default;  
  2.     A11 (int _data):data(_data) {}  
  3. };  
  4.   
  5.   
  6. void c11_construct () {  
  7.     A11 a;  
  8.     A11 b(a);  
  9.     A11 c;  
  10.     c = b;  
  11.     A11 d(1);  
  12. }  

2、delete關鍵字禁止拷貝構造、禁止賦值構造、禁止自定義參數的構造函數

注意析構函數不可由delete修飾

c++11以前的方式,是把需要禁止的構造函數,放在private里使外部無法調用;

c++11風格的禁止構造的noncopyable基類實現如下,禁止了拷貝構造和賦值構造:

 

  1. class noncopyable {  
  2. protected:  
  3.     constexpr noncopyable() = default;  
  4.     ~noncopyable() = default;  
  5.     noncopyable(const noncopyable &) = delete;  
  6.     noncopyable &operator= (const noncopyable &) = delete;  
  7. };  


3、委托構造函數

一個構造函數,使用自己的參數,傳遞給其他構造函數去構造,作為自己的構造函數實現,

如下例,后面兩個構造函數,均傳遞參數,委托給第一個構造函數去實現

 

  1. <span style="font-size:10px;">struct A {  
  2.         bool a_;  
  3.         char b_;  
  4.         int c_;  
  5.         float d_;  
  6.         double e_;  
  7.         A(bool a, char b, int c, float d, double e): a_(a), b_(b), c_(c), d_(d), e_(e) {}  
  8.         //construct reuse  
  9.         A (int c): A(true, 'b', c, 1.1, 1000.1) {}  
  10.         A (double e): A (false, 'a', 0, 0.1, e) {}  
  11.     };  
  12.   
  13.     A o1(10);  
  14.     LOG(INFO) << "a: " << o1.a_ << ", b: " << o1.b_ << ", c: " << o1.c_ << ", d: " << o1.d_ << ", e: " << o1.e_;  
  15.     A o2(5.5);  
  16.     LOG(INFO) << "a: " << o2.a_ << ", b: " << o2.b_ << ", c: " << o2.c_ << ", d: " << o2.d_ << ", e: " << o2.e_;</span>  

4、移動構造函數:

屬於c++11的右值引用的衍生效果之一,首先描述右值引用std::move

std::move主要能解決的拷貝性能問題

類似於python的深拷貝和淺拷貝, python中的對象賦值和copy.copy都是淺拷貝, 賦值的都是對象的引用, copy.deepcopy則是深拷貝

首先插一段python代碼幫助理解深淺拷貝,建議用pdb跟一下代碼感受更加深刻:

 

[python] view plain copy
  1. <span style="font-size:10px;">import copy                                                                                  
  2. import json                                                                                  
  3.                                                                                              
  4.                                                                                              
  5. a = [1, 2, 3, 4, 5, [99, 98]]                                                                
  6. #b全都是a的引用                                                                              
  7. b = a                                                                                        
  8. #c的非子對象都是a的復制構造, 但子對象還是引用                                                
  9. c = copy.copy(a)                                                                             
  10. #d全都是a的復制構造                                                                        </span>  
[python] view plain copy
  1. <span style="font-size:10px;">d = copy.deepcopy(a)                                                                         
  2.                                                                                              
  3. print "a append a new element 100"                                                           
  4. a.append(100)                                                                                
  5.                                                                                              
  6. print "a: %s" % json.dumps(a)                                                                
  7. print "b = a, b will change: %s" % json.dumps(b)                                             
  8. print "c = copy.copy(a): %s" % json.dumps(c)                                                 
  9. print "d = copy.deepcopy(a): %s" % json.dumps(d)                                             
  10.                                                                                              
  11. print "a's subobject append a new element 100"                                               
  12. a[5].append(100)                                                                             
  13.                                                                                              
  14. print "a: %s" % json.dumps(a)                                                                
  15. print "b = a, b will change: %s" % json.dumps(b)                                             
  16. print "c = copy.copy(a), will change: %s" % json.dumps(c)                                    
  17. print "d = copy.deepcopy(a): %s" % json.dumps(d) </span>  


直接定位到實際應用上(程序中盡量不要出現"int &&a = 1"這樣的東西,炫技容易搞出錯誤)
c++11的std::move, 解決的問題是一個復制效率的問題:

對臨時變量(如函數中的參數)的復制,通過更改對象的所有者(move),實現免內存搬遷或拷貝(去除深拷貝),

提高"復制"效率(其實不是復制,僅是更改了對象的所有者

例一:改變引用持有者(減少復制成本,移交引用權力給有用的變量,同時免除不再有用變量對引用的持有權)

 

  1. <span style="font-size:10px;">    std::string a = "123";    //或std::string &&a = "123";顯示的標識a是全局字符串"123"的右值引用  
  2.     LOG(INFO) << "at first, std::string a is: " << a;   //打印123  
  3.   
  4.     /*右值"123", 它的所有者將從原先的左值(變量std::string a), 轉移到新的左值(std::vector v) 
  5.      *所以, 使用std::move時一定保證, 以前的左值不再真需要了. 典型使用場合就是: (構造)函數的參數, 避免了再復制*/  
  6.     v.push_back(std::move(a));  
  7.     LOG(INFO) << "after std::move(a), now std::string a is: " << a; //打印空</span>  

最后的glog將無法打印出a最開始的拷貝構造獲取的值"123",因為全局字符串"123"的所有者,已經從最開始的變量a,轉移到了v

這在日常場合也是需要的,用途為:

1、減少內存復制成本

2、將不再需要的變量,取消它對原先持有變量(內存)的持有(修改)權限


例二:移動構造函數

 

  1. <span style="font-size:10px;">    class test {  
  2.     public:  
  3.         std::vector<std::string> t_;  
  4.         test(std::vector<std::string> &tmp) {  
  5.             for (auto& i: tmp) {  
  6.                 //not copy rvalue to t_, only add rvalue reference to t_ and update rvalue's lifecycle  
  7.                 t_.push_back(std::move(i));  
  8.             }  
  9.         }  
  10.     };  
  11.   
  12.     /*起初, 右值("123", "456", "789", "012", "345")都歸屬於左值temp*/  
  13.     std::vector<std::string> temp = {"123", "456", "789", "012", "345"};  
  14.     LOG(INFO) << "before move to object ot, t's size is: " << temp.size();  
  15.     for (auto& i: temp) {  
  16.         LOG(INFO) << " OLD LVALUE(object temp) element: " << i;  
  17.     }  
  18.   
  19.     /*由類test的構造函數, 更改右值的所有者為類test的對象ot*/  
  20.     test ot(temp);  
  21.     LOG(INFO) << "after move elements of temp to object ot, now ot's size is: " << ot.t_.size();  
  22.     for (auto& i: temp) {  
  23.         LOG(INFO) << " OLD LVALUE(object temp) element: " << i;  
  24.     }  
  25.     for (auto& i: ot.t_) {  
  26.         LOG(INFO) << " NEW LVALUE(object ot) element: " << i;  
  27.     }</span>  

第一輪glog, vector容器temp可以打印出其持有的全局字符串列表

第二輪glog: 因為全局字符串列表的每一個字符串的引用,均被move到test類對象ot的成員變量t_中,vector容器temp不再持有全局字符串列表中每一個字符串的引用權限
故無法打印
第三輪glog: 對象ot的成員t_持有全局字符串列表每一個字符串的引用,所以可以打印

移動構造函數,最大的用途避免同一份內存數據的不必要的變成兩份甚至多份、過程中的變量傳遞導致的內存復制,另外解除了棧變量對內存的引用
實際應用時往往如下這樣:
int main () {
var a = XXX;
var b = YYY;
.....
object obj(a,b,.....);//使用移動構造函數,免去a的復制構造成本(避免深拷貝造成XXX、YYY在main里有一份,obj里還有一份,而且obj構造時可能還得內存復制),另外以后臨時變量a再無權修改對應內存,完全消除全部隱患
}


例三:c++11風格的新老容器的數據移交:

如果一個老容器如vector容器oldv,需要將其內部數據復制給新容器如vector容器newv,且老容器后面無用,數據量很大;

那么c++11的std::"font-size:10px;"><span style="font-family:Courier New, sans-serif;">std::vector<std::string> oldv = {"123", "456", "789"};  

  •     std::vector<std::string> newv(oldv.size());  
  •   
  •     for (auto &i: oldv) {  
  •         std::cout << i << "\t";  
  •     }  
  •     std::cout << std::endl;  
  •     <strong><span style="color:#ff0000;">std::copy(std::make_move_iterator(oldv.begin()), std::make_move_iterator(oldv.end()), newv.begin());   //c++11做法,move引用</span></strong></span></span>  
  1. <span style="font-size:10px;"><span style="font-family:Courier New, sans-serif;"><strong><span style="color:#ff0000;">    //std::copy(oldv.begin(), oldv.end(), newv.begin());  //傳統做法,復制</span></strong>  
  2.     for (auto &i: oldv) {  
  3.         std::cout << i << "\t";  
  4.     }  
  5.     std::cout << std::endl;  
  6.     for (auto &i: newv) {  
  7.         std::cout << i << "\t";  
  8.     }  
  9.     std::cout << std::endl;</span></span>  

第一次打印:老容器正常打印

第二次打印:老容器無法打印了,因為每個
第三次打印:新容器正常打印

關於右值引用是c++11的一大重點,還有很多其他相關內容,個人認為理解和運用到這里基本可滿足了。

5、繼承構造函數

回到c++11的關於類的構造問題,近似於委托構造函數原理,如下:

  1. struct A {  
  2.     int a;  
  3.     A(int _a):a(_a + 100){}  
  4. };  
  5. struct B : public A {  
  6.     int b;  
  7.     B(int _b):A(_b), b(_b + 10000){}  
  8. };  
  9.   
  10. B obj(1);  
  11. std::cout << obj.a << ", " << obj.b << std::endl;  


3.2、override和final

作用於虛函數,更多的作用是:顯式的標識是否應該多態繼承或不應該

1、override:子類用override修飾其虛函數,表示要多態繼承基類的虛函數。不可以修飾非虛函數

舉一個rocksdb的merge運算符重載的例子:

 

  1. class ProcessMerge : public rocksdb::MergeOperator {  
  2. public:  
  3.     virtual bool FullMergeV2 (const MergeOperationInput &merge_in,  
  4.                               MergeOperationOutput *merge_out) const override {  
  5.         merge_out->new_value.clear();  
  6.         if (merge_in.existing_value != nullptr) {  
  7.             merge_out->new_value.assign(merge_in.existing_value->data(), merge_in.existing_value->size());  
  8.         }  
  9.   
  10.         for (const rocksdb::Slice& m : merge_in.operand_list) {  
  11.             merge_out->new_value.append("|");  
  12.             merge_out->new_value.append(m.data(), m.size());  
  13.         }  
  14.   
  15.         return true;  
  16.     }  
  17.   
  18.     const char* Name() const override { return "ProcessMerge"; }  
  19. };  


2、final:基類用final修飾其虛函數,意外其子類不可以多態繼承該虛函數

 

  1. class father {  
  2.     public:  
  3.         int a_;  
  4.         int GetA() {return a_;}  
  5.         virtual void SetA(int a) {  
  6.             a_ = a;  
  7.             LOG(INFO) << "father modify a to " << a_;  
  8.         }  
  9.         //add keyword final to avoid non-anticipated inherit in compling but not errored in running  
  10.         //virtual void SetA(int a) final {a_ = a;}  
  11.     public:  
  12.         father(int a):a_(a) {}  
  13.     };  
  14.   
  15.     class Son: public father {  
  16.         int b_;  
  17.     public:  
  18.         Son(int a, int b):father(a), b_(b) {}  
  19.         //add keyword override to avoid the error in compling but not errored in running.(eg. 'int SetA(double a){...} override' woule be errored by compiler)  
  20.         virtual void SetA(int a) override {  
  21.             a_ = a;  
  22.             LOG(INFO) << "son modify a to " << a_;  
  23.         }  
  24.         //virtual void SetA(double a) override {a_ = a;}  
  25.     };  

如father基類的SetA實現為"virtual void SetA(int a) final {a_ = a;}",則子類Son再多態繼承實現SetA方法就會報錯了。


3.3、建議:

構造與析構:全部的復制構造、賦值構造、所有權移動構造、自定義構造函數,以及全部的復制運算符、賦值運算符、所有權移動運算符,盡可能自行全部都實現

繼承:子類的虛函數多態實現要加override顯式的表明,不讓子類多態實現的虛函數也要記得加入final;

宗旨:讓c++11的編譯器更多的幫助發現問題


4、lambda、bind、function:

函數式編程是c++11重要亮點之一

4.1、直接lambda表達式

完全如同python

  1. int a = 1, b = 2;  
  2. auto multi = [](int a, int b){  
  3.     b = a + a + a;  
  4.     return a + b;  
  5. };  
  6.   
  7. LOG(INFO) << "by lambda: " << multi(a, b);  

函數multi


4.2、c++11風格的函數指針std::function & std::bind

 

  1. int func1 (int a, int b) {  
  2.     b = a + a + a;  
  3.     return a + b;  
  4. }  
  5.   
  6. auto a = 1, b = 2;  
  7. std::function<int (int, int)> modify_add0(func1);  
  8.        LOG(INFO) << "directly assign function: " << modify_add0(a, b);  

通過指定返回值、參數列表、綁定的函數和函數名,定義一個函數(指針)modify_add0


綁定的函數,可以是普通函數,也可以是類成員函數,同時指定:

  1. class ca {  
  2. public:  
  3.     bool func(int a) {  
  4.         LOG(INFO) << "aaa: " << a;  
  5.     }  
  6. };  
  7.   
  8. ca o;  
  9. std::function<bool (int)> f = std::bind(&ca::func, o, std::placeholders::_1);  
  10. f(1);  

原先只有在boost出現且極為受限的函數占位符,也加入到了標准庫,即std::placeholders,傳遞自定義參數

綁定類成員函數時,需要配合使用std:bind。

bind和placeholders,同樣可以用於普通函數:

  1. int func1 (int a, int b) {  
  2.     b = a + a + a;  
  3.     return a + b;  
  4. }  
  5.   
  6. auto a = 1, b = 2;  
  7. auto auto1 = std::bind(func1, std::placeholders::_1, std::placeholders::_2);  
  8. LOG(INFO) << "directly run auto: " << auto1(a, b);  

auto可以自動識別標准類型的變量的類型,同樣可以用於std:;function:

  1. int func1 (int a, int b) {  
  2.     b = a + a + a;  
  3.     return a + b;  
  4. }  
  5.   
  6. auto a = 1, b = 2;  
  7. auto auto1 = std::bind(func1, std::placeholders::_1, std::placeholders::_2);  
  8. LOG(INFO) << "directly run auto: " << auto1(a, b);  

std:;function作為函數指針,同樣可以作為參數傳遞並執行:

  1. int func1 (int a, int b) {  
  2.     b = a + a + a;  
  3.     return a + b;  
  4. }  
  5.   
  6. int func3 (auto f) {  
  7.     return f(1, 2);  
  8. }  
  9.   
  10. auto a = 1, b = 2;  
  11. auto auto1 = std::bind(func1, std::placeholders::_1, std::placeholders::_2);  
  12. LOG(INFO) << "run auto in function: " << func3(auto1);  

bind內不僅不再有boost占位符實現的1st、2nd的個數限制,還可以傳遞常量,並可以指定參數的順序:

  1. int func2 (int a, double b, std::string c) {  
  2.     b = a + a + a;  
  3.     return int(a + b);  
  4. }  
  5.   
  6. /*std::function內的定義了該function調用時的順序, 也是_1、_2、..._n的順序, bind內要整理符合綁定的函數參數順序*/  
  7. std::function<int (std::string, int)> modify_add2 = std::bind(func2, std::placeholders::_2, 2.0, std::placeholders::_1);  
  8. LOG(INFO) << "by bind with partly arg: " << modify_add2("aaa", 1);  

modify_add2函數執行時,第一個參數"aaa"第二個參數1,貌似和綁定的函數func2的順序不符,就是因為bind內指定了占位符標識,占位符2作為第一個參數,常量2.0作為第二個參數,占位符1作為第三個參數,即1、2.0、"aaa"

更廣泛的用法,直接定義函數體:

  1. std::function<int ()> modify_add3 = std::function<int ()>([=, &b]{  
  2.     b = a + a + a;  
  3.     return a + b;  
  4. });  
  5. LOG(INFO) << "directly in-function: " << modify_add3();  


這個做法是后面描述的std::thread的典型適配方法,讓static void thread_func(void *arg) {......}作為線程執行函數體的作法成為歷史

對於函數參數為引用、常引用、指針的方法:

  1. int func4 (const int &a, int &b) {  
  2.     b = 3;  
  3.     return a + b;  
  4. }  
  5.       
  6. int func5 (int *a) {  
  7.     return *a;  
  8. }  
  9.   
  10. std::function<int (const int&, int&)> modify_add4 = std::bind(func4, std::placeholders::_1, std::placeholders::_2);  
  11. LOG(INFO) << "args is const reference and reference: " << modify_add4(std::cref(a), std::ref(a));  
  12. std::function<int (int *)> modify_add5 = std::bind(func5, std::placeholders::_1);  
  13. LOG(INFO) << "args is const reference and reference: " << modify_add5(&a);  


在這里,std::ref、std::cref派上了用場

5、動態指針

這也是c++11一個重要亮點

如同函數式編程,動態指針同樣大量移植了原先boost里的東西

5.1、unique_ptr

功能基本對應boost的scoped_ptr,或之前stl的auto_ptr,生命周期隨構造者,reset自動析構再重新構造,get判斷是否有效、支持放在容器內;

真正意義智能指針。

不論是臨時變量指針、類成員指針變量.....90%的指針都應該用這個

5.2、shared_ptr

功能對於boost的shared_ptr,可以有多個持有者的共享指針,即所謂引用計數型指針,直到最后一個持有者delete釋放時,其指向的資源才會真正被釋放

典型應用案例:如對同一個全局無鎖隊列對象由shared_ptr封裝,多線程的多個持有者均持有對其的引用。直到全部線程都釋放掉對其的引用時,該無鎖隊列對象才會被最終銷毀。

也就是shared_ptr適合用於管理“全局動態資源”

 

6、多線程與互斥同步(互斥鎖,條件變量)

這也是c++11的一個重要亮點

c++11的多線程管理瞬間變得和boost甚至比boost的還要方便:

  1. static void *ThreadFunc(void *arg) {  
  2.     reinterpret_cast<ThreadPool *>(arg)->process();  
  3.     return 0;  
  4. }  
  5.   
  6. int a = new int;  
  7. std::thread th(&ThreadFunc, (void *)&a);  

一個線程池的構造:

  1. ThreadPool::ThreadPool (int thread_num): thread_num_(thread_num),  
  2.                                          pending_num_(0),  
  3.                                          running_num_(0),  
  4.                                          task_count_(0),  
  5.                                          stop_(true) {  
  6.     Start();  
  7. }  
  8.   
  9. ThreadPool::~ThreadPool () {  
  10.     Stop(false);  
  11. }  
  12.   
  13. bool ThreadPool::Start () {  
  14.     std::unique_lock<std::mutex> lock(mtx_);  
  15.     stop_ = false;  
  16.     for (auto i: common::Range(0, thread_num_)) {  
  17.         ths_.push_back(std::thread(&ThreadFunc, this));  
  18.     }  
  19. }  

就是這樣創建並運行

結合前邊的std::function,可以讓static void ThreadFunc(void *arg)成為歷史:

  1. std::unique_ptr<std::thread> agent_;  
  2.   
  3. agent_.reset(new std::thread([this] () {  
  4.     while (1) {  
  5.         std::unique_lock<std::mutex> lock(mtx_);  
  6.         if (!lockfreequeue_.get() || (lockfreequeue_->empty() && !stop_)) {  
  7.             std::cv_status cvsts = cond_.wait_for(lock, std::chrono::milliseconds(100));  
  8.             if (cvsts == std::cv_status::timeout) {  
  9.                 continue;  
  10.             }  
  11.         }  
  12.         if (stop_) {  
  13.             break;  
  14.         }  
  15.   
  16.         void *msg = nullptr;  
  17.         lockfreequeue_->pop(msg);  
  18.         if (msg) {  
  19.             Task task = std::bind(&DataPreProcess::PreProcess, this, msg);  
  20.             workers_->AddTask(task);  
  21.         }  
  22.     }  
  23.   
  24.     LOG(INFO) << "agent thread exit.";  
  25. }));  


即,直接定義函數體。在c++11直接定義函數體代替靜態函數是非常常用的方式。


提到多線程,不能不提到多線程互斥與同步,c++11在這方面同樣大量移植boost:

std:;mutex

std::unique_lock

std::condition_variable

它們讓多線程共用全局posix互斥鎖、條件變量的方式成為歷史

std::unique_lock和std::condition_variable,基本對應boost的scoped_lock和condition_variable,使用方法完全一樣


以線程池的部分實現為例:

1、首先聲明和定義線程池的執行實體:

 

  1. using Task = std::function<void ()>;  
  2. struct Timertask {  
  3.     bool flag_;  
  4.     Task task_;  
  5.     int64_t timeval_;  
  6.     int64_t exec_time_;  
  7.     bool operator< (const struct Timertask otherone) const {  
  8.         return exec_time_ > otherone.exec_time_;  
  9.     }  
  10.     Timertask(const Task &task, int64_t timeval, int64_t exec_time, bool flag = false):flag_(flag), task_(task), timeval_(timeval), exec_time_(exec_time) {}  
  11.     Timertask(const Task &task, int64_t timeval, bool flag = false):flag_(flag), task_(task), timeval_(timeval) {  
  12.         int64_t nowtime = common::getime_micros();  
  13.         exec_time_ = timeval_ + nowtime;  
  14.     }  
  15. };  

業務上包括任務Task、和定時任務Timertask兩類,執行實體都是Task

Timertask重載<是因為定時任務需要按時間臨的遠近排序,線程池的定時任務隊列的實現是一個堆,所以這里需要重載<;flag_意為是一次性定時任務還是例行定時任務。

這些非本部分關注點不影響閱讀即可。


2、線程池的聲明,重點關注多線程互斥鎖、條件變量成員

  1. class ThreadPool {  
  2. private:  
  3.     std::atomic<uint64_t> pending_num_;  
  4.     std::atomic<uint64_t> running_num_;  
  5.     uint64_t task_count_;  
  6.   
  7.     bool stop_;  
  8.     int thread_num_;  
  9.     std::vector<std::thread> ths_;  
  10.   
  11.     std::mutex mtx_;  
  12.     std::condition_variable cond_;  
  13.     std::deque<Task> queue_;  
  14.     std::priority_queue<Timertask> timer_queue_;  
  15.   
  16. public:  
  17.     ThreadPool(int thread_num);  
  18.     ~ThreadPool();  
  19.     bool Start();  
  20.     bool Stop(bool graceful);  
  21.   
  22.     void AddTask(const Task &task);  
  23.     void AddPriorityTask(const Task &task);  
  24.     void AddDelayTask(int timeval, const Task &task);  
  25.     void AddTimerTask(int timeval, const Task &task);  
  26.     bool IsEmpty() {return (running_num_ > 0)?false:true;}  
  27.     bool CancelTask();  
  28.   
  29.     static void *ThreadFunc(void *arg) {  
  30.         reinterpret_cast<ThreadPool *>(arg)->process();  
  31.         return 0;  
  32.     }  
  33.     void process();  
  34. };  


3、線程池構造與析構

重點關注析構,析構函數在"優雅模式"下,可以通過原子成員變量pending_num_獲知是否全部任務執行完畢

非優雅模式下,首先置stop_標志位為false意為即將析構,並通過條件變量cond_的notify_all喚醒全部線程,使其執行完當前任務后退出

  1. bool ThreadPool::Start () {  
  2.     std::unique_lock<std::mutex> lock(mtx_);  
  3.     stop_ = false;  
  4.     for (auto i: common::Range(0, thread_num_)) {  
  5.         ths_.push_back(std::thread(&ThreadFunc, this));  
  6.     }  
  7. }  
  8.   
  9. bool ThreadPool::Stop (bool graceful) {  
  10.     if (graceful) {  
  11.         while (pending_num_) {  
  12.             std::chrono::milliseconds duration(5000);  
  13.             std::this_thread::sleep_for(duration);  
  14.         }  
  15.     }  
  16.   
  17.     stop_ = true;  
  18.     cond_.notify_all();  
  19.     for (auto i: common::Range(0, thread_num_)) {  
  20.         ths_[i].join();  
  21.     }  
  22.   
  23.     pending_num_ = running_num_ = task_count_ = 0;  
  24. }  

線程池的線程的實際執行函數,在執行完當前任務后會發現stop_標志位已經為false了,會紛紛退出

每個線程被操作系統調度到后,首先霸占互斥鎖,注意c++11的互斥鎖使用方法;

然后從任務隊列中取出任務,然后釋放掉互斥鎖,自己去執行任務;如果沒有任務,釋放鎖並一直等待條件變量的被通知

 

  1. void ThreadPool::process () {  
  2.     while (1) {  
  3.         std::unique_lock<std::mutex> lock(mtx_);  
  4.         while (timer_queue_.empty() && queue_.empty() && !stop_) {  
  5.             cond_.wait(lock);  
  6.         }  
  7.   
  8.         if (stop_) {  
  9.             break;  
  10.         }  
  11.   
  12.         if (!timer_queue_.empty()) {  
  13.             int64_t nowtime = common::getime_micros();  
  14.             Timertask newestask = timer_queue_.top();  
  15.             if (newestask.exec_time_ <= nowtime) {  
  16.                 timer_queue_.pop();  
  17.                 Task task = newestask.task_;  
  18.                 bool flag = newestask.flag_;  
  19.                 int64_t timeval = newestask.timeval_;  
  20.                 if (flag) {  
  21.                     Timertask newtask(task, timeval, true);  
  22.                     timer_queue_.push(newtask);  
  23.                     ++task_count_;  
  24.                 }  
  25.   
  26.                 ++running_num_;  
  27.                 --pending_num_;  
  28.                 lock.unlock();  
  29.                 task();  
  30.                 lock.lock();  
  31.                 --running_num_;  
  32.             }  
  33.         }  
  34.   
  35.         if (!queue_.empty()) {  
  36.             Task task = queue_.front();  
  37.             queue_.pop_front();  
  38.   
  39.             --pending_num_;  
  40.             ++running_num_;  
  41.             lock.unlock();  
  42.             task();  
  43.             lock.lock();  
  44.             --running_num_;  
  45.         }  
  46.     }  



當給線程池加入新的要執行的任務,也會先霸占鎖並向任務隊列里加入新的任務,然后通知某一個正在等待條件變量同步的sleeping的線程(notify_one):

普通任務以雙向數組std::deque管理,按是否重要選擇前插還是后插

 

    1. void ThreadPool::AddTask (const Task &task) {  
    2.     std::unique_lock<std::mutex> lock(mtx_);  
    3.     queue_.push_back(task);  
    4.     ++pending_num_;  
    5.     ++task_count_;  
    6.     cond_.notify_one();  
    7. }  
    8. void ThreadPool::AddPriorityTask (const Task &task) {  
    9.     std::unique_lock<std::mutex> lock(mtx_);  
    10.     queue_.push_front(task);  
    11.     ++pending_num_;  
    12.     ++task_count_;  
    13.     cond_.notify_one();  
    14. }  
    15. void ThreadPool::AddDelayTask (int timeval, const Task &task) {  
    16.     std::unique_lock<std::mutex> lock(mtx_);  
    17.     Timertask newtask(task, timeval);  
    18.     timer_queue_.push(newtask);  
    19.     ++task_count_;  
    20.     cond_.notify_one();  
    21. }  
    22. void ThreadPool::AddTimerTask (int timeval, const Task &task) {  
    23.     std::unique_lock<std::mutex> lock(mtx_);  
    24.     Timertask newtask(task, timeval, true);  
    25.     timer_queue_.push(newtask);  
    26.     ++task_count_;  
    27.     cond_.notify_one();  
    28. }

http://blog.csdn.net/u010246947/article/details/77822972

http://blog.csdn.net/u012931582/article/details/61655729

http://blog.csdn.net/zhangyifei216/article/details/72868345


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM