zoukankan      html  css  js  c++  java
  • 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 << " ";  
    •     }  
    •     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 << " ";  
    4.     }  
    5.     std::cout << std::endl;  
    6.     for (auto &i: newv) {  
    7.         std::cout << i << " ";  
    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

  • 相关阅读:
    MySQL优化十大技巧
    50个常用sql语句 网上流行的学生选课表的例子
    JDK常用命令
    linux中用date命令获取昨天、明天或多天前后的日期
    ***如何优雅的选择字体(font-family)
    将WordPress安装在网站子目录的相关问题
    PHP源码加密- php-beast
    如何安装ioncube扩展对PHP代码加密
    PHP防止表单重复提交的解决方法
    CI中SESSION的用法及其注意
  • 原文地址:https://www.cnblogs.com/wangbin/p/7494427.html
Copyright © 2011-2022 走看看