C++11新特性

C++11 是 C++的一個版本,由C++03改進而來,原來計劃是在2010年前發佈,所以經常可以看到C++0x。C++11添加了很多非常好用的特性,甚至被人們認爲是一門新的語言。C++11在GCC 4.8中支持基本完善。以下內容爲轉載,添加了一些筆記。

auto,decltype,for,nullptr


如果編譯器在定義一個變量的時候可以推斷出變量的類型,不用寫變量的類型,你只需寫auto即可

auto str = "sissie";
assert(typeid(str) == typeid(const char *));

auto處理引用時默認是值類型,可以指定&作爲修飾符強制它作爲引用,auto自動獲取指針類型,也可以顯示地指定指針類型

int& foo();
auto i = foo();     // int
auto& ri = foo();   // int&

int* bar();
auto pi = bar();    // int*
auto* pi = bar();   // int*

在迭代器中使用auto,簡化代碼

std::vector<std::string> vs{{"sissie", "robin", "playjokes", "sky", "hatlonely"}};
for (auto it = vs.begin(); it != vs.end(); ++it) {
    std::cout << *it << ", ";
}

在模板中使用auto

template <typename BuiltType, typename Builder>
void makeAndProcessObject(const Builder& builder)
{
    BuiltType val = builder.makeObject();
    // do stuff with val
}
MyObjBuilder builder;
makeAndProcessObject<MyObj>(builder);

// 使用auto只需要一個模板參數,讓編譯器自動推導
template <typename Builder>
void makeAndProcessObject(const Builder& builder)
{
    auto val = builder.makeObject();
    // do stuff with val
}
MyObjBuilder builder;
makeAndProcessObject(builder);

decltype 返回操作數的類型,可以對基本上任何類型使用decltype,包括函數的返回值

int ia[10];
decltype(ia) ib;    // int ib[10];

新的函數返回值聲明語法,把返回類型放在函數聲明的後面,用auto代替前面的返回類型

// 這兩種函數聲明方式等效
int multiply(int x, int y);
auto multiply(int x, int y) -> int;

// 返回auto
template <typename Builder>
auto makeAndProcessObject(const Builder& builder) -> decltype(builder.makeObject())
{
    auto val = builder.makeObject();
    // do stuff with val
    return val;
}

區間迭代,for循環遍歷容器

std::vector<std::string> vs{{"sissie", "robin", "playjokes", "sky", "hatlonely"}};
for (const auto& name: vs) {
    std::cout << name << ", ";
}

nullptr 是C++11中新加的關鍵字,用來表示空指針,替代原來的NULL,nullptr不能轉換成int

lambda 表達式


C++11中得lambda表達式用來定義和創建匿名函數,lambda表達式語法形式如下:

[ capture ] ( params ) mutable exception attribute -> ret { body }      // (1)
[ capture ] ( params ) -> ret { body }                                  // (2)
[ capture ] ( params ) { body }                                         // (3)
[ capture ] { body }                                                    // (4)

(1) 是完整的 lambda 表達式形式
(2) const 類型的 lambda 表達式,該類型的表達式不能改捕獲(“capture”)列表中的值
(3) 省略了返回值類型的 lambda 表達式,但是該 lambda 表達式的返回類型可以按照下列規則推演出來:

  • 如果 lambda 代碼塊中包含了 return 語句,則該 lambda 表達式的返回類型由 return 語句的返回類型確定。
  • 如果沒有 return 語句,則類似 void f(…) 函數

(4) 省略了參數列表,類似於無參函數 f()

  1. capture:capture 爲捕獲的lambda所在作用域範圍內可見的局部變量列表

    • [a,&b] a變量以值的方式唄捕獲,b以引用的方式被捕獲
    • [this] 以值的方式捕獲 this 指針
    • [&] 以引用的方式捕獲所有的外部自動變量
    • [=] 以值的方式捕獲所有的外部自動變量
    • [] 不捕獲外部的任何變量
  2. params:參數列表

  3. mutable exception attribute:lambda是否可以更改捕獲變量以及是否有異常拋出

    • mutable 修飾符說明 lambda 表達式體內的代碼可以修改被捕獲的變量,並且可以訪問被捕獲對象的non-const 方法
    • exception 說明 lambda 表達式是否拋出異常(noexcept),以及拋出何種異常,類似於void f() throw(X, Y)
    • attribute 用來聲明屬性
  4. ret:返回類型

  5. body:函數體
// 數組累加
std::vector<int> vi{{1, 2, 3, 4, 5, 6, 7, 8, 9}};
int total = 0;
std::for_each(vi.begin(), vi.end(), [&total](int i) {
  total += i;
});

// 數組絕對值
// 單一的return語句,編譯其可以推導出返回類型,多個return語句需要顯示指定返回類型
std::transform(vi.begin(), vi.end(), [](int i) -> int {
    if (i < 0) {
        return -i;
    } else {
        return i;
    }
});

// mutable
size_t i = 42;
auto f = [i]() mutable {
    return ++i;
};
i = 0;
std::cout << f() << std::endl;   // 43
std::cout << f() << std::endl;   // 44

初始化列表


C++11新添加初始化列表std::initializer_list<>類型,可以通過{}語法來構造初始化列表

// 容器初始化
// {1, 2, 3, 4, 5}實際上是一個std::initializer_list<int>類型
std::vector<int> vi = {1, 2, 3, 4, 5};
std::vector<std::string> vs{{"sissie", "robin", "playjokes", "sky", "hatlonely"}};
std::map<int, std::string> mis = {
    {1, "c"},
    {2, "java"},
    {3, "c++"}
};

// 初始化列表參數
void print_initializer_list(std::initializer_list<int> il) {
    for (auto i: il) {
        std::cout << i << ", ";
    }
    std::cout << endl;
}
print_initializer_list({1, 2, 3, 4, 5, 6});

// 返回初始化列表
std::vector<std::string> my_array() {
    return {"sissie", "robin", "playjokes", "sky", "hatlonely"};
}


加入兩個新的標識符:

  • override,表示函數應當重寫基類中的虛函數;
  • final,表示派生類不應當重寫這個虛函數
class B
{
public:
   virtual void f(int) {std::cout << "B::f" << std::endl;}
};

class D : public B
{
public:
   virtual void f(int) override final {std::cout << "D::f" << std::endl;}
};

默認或禁用函數,當我們定義了自己的帶參數的構造函數時,編譯器將不再生成默認的構造函數,如果此時想使用默認的構造函數,則必須顯式地聲明並定義不帶參數的構造函數。在C++11中,我們可以使用default關鍵字來表明我們希望使用默認的構造函數。類似的,當我們不想外部使用編譯器自動生成的構造函數或賦值函數時,我們一般需要將其聲明成protected或private的。在C++ 11中,我們可以使用delete關鍵字來表明我們不希望編譯器生成默認的構造函數或賦值函數。

class Person {
public:
    Person() = default;
    Person(const Person& person) = delete;
};

C++11允許成員變量就地初始化

class Person {
private:
    std::string _name = "sissie";
}

委託構造函數,一個委託構造函數使用它所屬類的其他構造函數執行它的初始化過程

class SaleData {
    SaleData(std::string booknum, uint32_t units_sold, double price) :
        _booknum(booknum), _units_sold(unit_sold), _price(price) {}
    SaleData() : SaleData("", 0, 0) {}
    SaleData(std::string booknum) : SaleData(booknum, 0, 0) {}
};

move語義與右值引用


左值和右值是針對表達式而言,表達式之後依然存在的對象是左值,表達式之後不再存在的臨時對象爲右值
左值可以對其取地址,右值不能

int i = 0;
std::string hello = "hello";
std::string world = "world";
const int& ri = 1;
// 左值:i, ++i, hello, world
// 右值:i++, hello + world, ri

拷貝臨時對象性能問題,考慮如下字符串初始化

std::string str1 = "a";
// str1 + "b" 生成一個臨時對象,再用這個臨時對象去構造str2
// 而這個臨時對象在構造完str2後就被釋放,這個對象並沒有用到,卻需要調用一次構造函數
std::string str2 = str1 + "b";

如果我們能確定某個值是一個非常量右值(或者是一個以後不會再使用的左值),則我們在進行臨時對象的拷貝時,可以不用拷貝實際的數據,而只是竊取實際數據的指針,C++11中引入的右值引用正好可用於標識一個非常量右值。C++ 11中用&表示左值引用,用&&表示右值引用

移動構造函數

// MemoryBlock.h
#pragma once
#include <iostream>
#include <algorithm>

class MemoryBlock {
public:
    // Simple constructor that initializes the resource.
    explicit MemoryBlock(size_t length) : _length(length), _data(new int[length]) {
        std::cout << "In MemoryBlock(size_t). length = " << _length << "." << std::endl;
    }

    // Destructor.
    ~MemoryBlock() {
        std::cout << "In ~MemoryBlock(). length = " << _length << ".";

        if (_data != NULL) {
            std::cout << " Deleting resource.";
            // Delete the resource.
            delete[] _data;
        }

        std::cout << std::endl;
    }

    // Copy constructor.
    MemoryBlock(const MemoryBlock& other) : _length(other._length), _data(new int[other._length]) {
        std::cout << "In MemoryBlock(const MemoryBlock&). length = "
            << other._length << ". Copying resource." << std::endl;

        std::copy(other._data, other._data + _length, _data);
    }

    // Copy assignment operator.
    MemoryBlock& operator=(const MemoryBlock& other) {
        std::cout << "In operator=(const MemoryBlock&). length = "
            << other._length << ". Copying resource." << std::endl;

        if (this != &other)
        {
            // Free the existing resource.
            delete[] _data;

            _length = other._length;
            _data = new int[_length];
            std::copy(other._data, other._data + _length, _data);
        }
        return *this;
    }

    // Move constructor.
    MemoryBlock(MemoryBlock&& other) : _length(0), _data(NULL) {
        std::cout << "In MemoryBlock(MemoryBlock&&). length = "
            << other._length << ". Moving resource." << std::endl;

        // Copy the data pointer and its length from the
        // source object.
        _data = other._data;
        _length = other._length;

        // Release the data pointer from the source object so that
        // the destructor does not free the memory multiple times.
        other._data = NULL;
        other._length = 0;
    }

    // Move assignment operator.
    MemoryBlock& operator=(MemoryBlock&& other) {
        std::cout << "In operator=(MemoryBlock&&). length = "
            << other._length << "." << std::endl;

        if (this != &other)
        {
            // Free the existing resource.
            delete[] _data;

            // Copy the data pointer and its length from the
            // source object.
            _data = other._data;
            _length = other._length;

            // Release the data pointer from the source object so that
            // the destructor does not free the memory multiple times.
            other._data = NULL;
            other._length = 0;
        }
        return *this;
    }

    // Retrieves the length of the data resource.
    size_t Length() const {
        return _length;
    }

private:
    size_t _length; // The length of the resource.
    int* _data;     // The resource.
};

// rvalue-references-move-semantics.cpp
// compile with: /EHsc
#include "MemoryBlock.h"
#include <vector>

int main() {
    // Create a vector object and add a few elements to it.
    std::vector<MemoryBlock> v;
    v.push_back(MemoryBlock(25));
    std::cout << "======================" << std::endl;
    v.push_back(MemoryBlock(75));
    std::cout << "======================" << std::endl;

    // Insert a new element into the second position of the vector.
    v.insert(v.begin() + 1, MemoryBlock(50));
    std::cout << "======================" << std::endl;
}

// 可以看到下面的輸出在push_back的時候,由於參數是一個非常量右值,自動調用了move構造函數
// 下面還有拷貝構造函數是在vector長度增長時拷貝數組產生的,這次拷貝構造也可以優化成move
// 具體實現與編譯器有關
// In MemoryBlock(size_t). length = 25.
// In MemoryBlock(MemoryBlock&&). length = 25. Moving resource.
// In ~MemoryBlock(). length = 0.
// ======================
// In MemoryBlock(size_t). length = 75.
// In MemoryBlock(MemoryBlock&&). length = 75. Moving resource.
// In MemoryBlock(const MemoryBlock&). length = 25. Copying resource.
// In ~MemoryBlock(). length = 25. Deleting resource.
// In ~MemoryBlock(). length = 0.
// ======================
// In MemoryBlock(size_t). length = 50.
// In MemoryBlock(MemoryBlock&&). length = 50. Moving resource.
// In MemoryBlock(const MemoryBlock&). length = 25. Copying resource.
// In MemoryBlock(const MemoryBlock&). length = 75. Copying resource.
// In ~MemoryBlock(). length = 75. Deleting resource.
// In ~MemoryBlock(). length = 25. Deleting resource.
// In ~MemoryBlock(). length = 0.
// ======================
// In ~MemoryBlock(). length = 75. Deleting resource.
// In ~MemoryBlock(). length = 50. Deleting resource.
// In ~MemoryBlock(). length = 25. Deleting resource.

std::move顯示調用move構造函數,如果類沒有move構造函數,會調用copy構造函數。對一個左值使用std::move之後,不應該再使用該對象,對該對象的任何操作都是未定義的。

std::vector<MemoryBlock> v;
MemoryBlock mb(66);
v.push_back(std::move(mb));

閱讀筆記
move的使用,首先要實現類的move構造函數或者重載賦值操作符,然後傳入一個右值,或者使用move將一個左值轉換成右值。
std::forward的:由於聲明爲f(T&& t)的模板函數的形參t會失去右值引用性質,所以在將t傳給更深層函數前,可能會需要回復t的正確引用。在實現模板類時可能會用到。

容器


std::vector

  • size():記錄了當前元素的個數
  • capacity():不重新分配內存的話,可以保存多少個元素
  • reserve(n):分配至少能容納n個元素的內存空間
  • shrink_to_fit():將capacity減少爲於size()相同的大小
// vector的無參構造函數初始化vector時,size和capacity都是0
// 之後當capacity不足時,capacity會成倍增加,可以用reverse指定capacity的值
std::vector<int> vi;
assert(vi.size() == 0);
assert(vi.capacity() == 0);
vi.reserve(1024);
assert(vi.size() == 0);
assert(vi.capacity() == 1024);

// vector的構造函數可以傳入一個參數指定當前vector的size
// 此構造函數會調用元素的無參構造函數,初始化元素
// 所以元素的類型必須實現無參構造函數,才能調用此構造函數
std::vector<std::string> vs(5);
assert(vs.size() == 5);
assert(vs.capacity() == 5);
vs.push_back("sissie");
assert(vs[5] == "sissie");
assert(vs.size() == 6);
assert(vs.capacity() == 10);
vs.shrink_to_fit();
assert(vs.capacity() == 6);

std::array

array是C++11新引入的數組類型,和std::vector不同的是array的長度是固定的,不能動態拓展

template <class T, std::size_t N> struct array

std::array<int, 3> a1{{1, 2, 3}};
std::sort(a1.begin(), a1.end());

std::forward_list

C++11引入的新類型,forward_list是單鏈表(std::list是雙鏈表),只需要順序遍歷的場合,forward_list能更加節省內存,插入和刪除的性能高於list

std::forward_list<int> fli{{1, 2, 3, 4}};

unordered

std::set std::multiset std::map std::multimap

用平衡樹實現的有序的容器,插入、刪除和查找的時間複雜度都是O(nlogn)

std::unordered_set std::unordered_multiset std::unordered_map std::unordered_multimap

C++11引入的新類型,用hash實現的無序的容器,插入、刪除和查找的時間複雜度都是O(1),在不關注容器內元素順序的場合,使用unordered的容器能獲得更高的性能

std::unordered_set<int> usi = {{11, 22, 33, 44, 55, 66, 77, 88, 99, 0}};
assert(usi.size() == 10);
usi.insert(66);
assert(usi.size() == 10);

// unordered_set是無序的
// 0,99,88,77,66,55,44,33,22,11,
for (const auto& i: usi) {
    std::cout << i << ",";
}
std::cout << std::endl;
// set是有序的
// 0,11,22,33,44,55,66,77,88,99,
std::set<int> si = {{11, 22, 33, 44, 55, 66, 77, 88, 99, 0}};
for (const auto& i: si) {
    std::cout << i << ",";
}
std::cout << std::endl;

// multiset中可以插入相同的值
std::unordered_multiset<int> umsi = {{11, 22, 33, 44, 55, 66, 77, 88, 99, 0}};
assert(umsi.size() == 10);
assert(umsi.count(66) == 1);
umsi.insert(66);
assert(umsi.size() == 11);
assert(umsi.count(66) == 2);

std::unordered_map<std::string, double> book_price_map{{
    {"C++ Primer", 128.00},
    {"UNIX 環境高級編程", 99.00},
    {"HBase 權威指南", 89.00},
    {"MapReduce 設計模式", 49.00}
}};
for (const auto& book_price_pair: book_price_map) {
    std::cout << book_price_pair.first << " => " << book_price_pair.second << std::endl;
}

智能指針


  • unique_ptr:作用域結束之後自動釋放資源,不可複製,可以移動
  • shared_ptr:通過引用計數共享資源,當引用計數爲0時,自動釋放資源
  • weak_ptr:一個shared_ptr的弱引用,不修改引用計數,爲了解決循環引用問題而引入
#include <cassert>
#include <memory>

int main() {
    {
        std::unique_ptr<int> upi(new int(6));
    }

    {
        // 用make_shared來初始化shared_ptr
        std::shared_ptr<int> spi = std::make_shared<int>(6);
        // use_count獲取引用計數
        assert(spi.use_count() == 1);
        {
            std::shared_ptr<int> spi_shared(spi);
            assert(spi.use_count() == 2);
        }
        assert(spi.use_count() == 1);
    }

    {
        std::shared_ptr<int> spi = std::make_shared<int>(6);
        assert(spi.use_count() == 1);

        // 通過shared_ptr來構造weak_ptr
        std::weak_ptr<int> wpi(spi);
        // weak_ptr不改變引用計數
        assert(spi.use_count() == 1);
        assert(wpi.use_count() == 1);

        // lock() 獲取weak_ptr引用的shared_ptr
        assert(*wpi.lock() == 6);
        // expired() 返回引用的對象是否已經釋放
        assert(!wpi.expired());
    }

    return 0;
}

shared_ptr提供get函數獲取對象的指針

正則表達式


regex

typedef basic_regex<char> regex
typedef basic_regex<wchar_t> wregex

typedef sub_match<const char*> csub_match
typedef sub_match<const wchar_t*> wcsub_match
typedef sub_match<std::string::const_iterator> ssub_match
typedef sub_match<std::wstring::const_iterator> wssub_match

typedef match_results<const char*> cmatch
typedef match_results<const wchar_t*> wcmatch
typedef match_results<std::string::const_iterator> smatch
typedef match_results<std::wstring::const_iterator> wsmatch
  • basic_regex:正則表達式
  • sub_match:正則表達式子匹配
  • match_result:正則匹配結果,由多個sub_match組成

match_result成員

  • ready():如果已經通過regex_serach或者regex_match設置則返回true,否則返回false;如果ready返回false,那所有對match_result的操作都是未定義的
  • size():匹配失敗返回0,否則返回最近一次匹配正則表達式中子表達式的數目
  • empty():返回size() == 0
  • prefix():返回一個sub_match對象,表示匹配之前的序列
  • suffix():返回一個sub_match對象,表示匹配之後的序列
  • format():將match_result格式化成一個字符串
  • length(n):第n個子表達式的長度
  • *position(n):第n個子表達式距序列開始的距離
  • str(n):第*個子表達式匹配的字符串
  • []:第n個子表達式的sub_match對象
  • begin(), end():sub_match的iterator
  • cbegin(), cend():sub_match的const_iterator

正則匹配

  • regex_match():完全匹配
  • regex_serach():部分匹配(匹配第一個)
  • regex_replace():正則替換
  • regex_iterator:迭代器適配器(value_type爲match_result),調用regex_search來遍歷一個string中所有的匹配子串
  • regex_token_iterator:迭代器適配器(value_type爲sub_match)
#include <iostream>
#include <regex>
#include <cassert>

int main()
{
    std::string context = ""
        "hatlonely ([email protected]) "
        "playjokes ([email protected])";

    std::regex mail_regex("(\\w+)@(\\w+)\\.com");
    std::smatch mail_result;

    // 不能全詞匹配 regex_match返回false
    assert(!std::regex_match(context, mail_result, mail_regex));
    // 可以部分匹配 regex_search返回true
    assert(std::regex_search(context, mail_result, mail_regex));
    // mail_result被regex_search設置過 返回true
    assert(mail_result.ready());
    // mail_result中sub_match的個數,兩個子表達式加上整個表達式
    assert(mail_result.size() == 3);
    // mail_result[0]爲匹配到的整個字符串
    assert(mail_result[0] == "[email protected]");
    // mail_result[n]爲第n個子表達式匹配到得串(小括號內的串)
    assert(mail_result[1] == "hatlonely");
    assert(mail_result[2] == "gmail");
    // prefix未匹配到的之前的串
    assert(mail_result.prefix() == "hatlonely (");
    // suffix未匹配到的之後的串
    assert(mail_result.suffix() == ") playjokes ([email protected])");
    // $`  相當於prefix
    // $'  相當於suffix
    // $n  第n個子匹配
    std::cout << mail_result.format("$` $1 $2") << std::endl;

    {
        // 相當於循環調用regex_search,迭代器的value_type爲match_result
        std::sregex_iterator it(context.begin(), context.end(), mail_regex);
        std::sregex_iterator end;
        for (; it != end; ++it) {
            std::cout << (*it)[0] << std::endl;
        }
    }
    {
        // 相當於循環調用regex_search,迭代器的value_type爲sub_match,相當於match_result[0]
        std::sregex_token_iterator it(context.begin(), context.end(), mail_regex);
        std::sregex_token_iterator end;
        for (; it != end; ++it) {
            std::cout << *it << std::endl;
        }
    }

    {
        // regex_replace 默認會替換所有匹配到的串,指定format_first_only可以只替換第一個匹配到得串
        // hatlonely ([email protected]) playjokes ([email protected])
        std::cout << context << std::endl;
        // hatlonely (hatlonely) playjokes (playjokes)
        std::cout << regex_replace(context, mail_regex, "$1") << std::endl;
        // hatlonely (hatlonely) playjokes ([email protected])
        std::cout << regex_replace(context, mail_regex, "$1",
            std::regex_constants::format_first_only) << std::endl;
    }

    return 0;
}

多線程


thread

構造函數

thread();
// f 是線程執行的函數,可以是函數指針或者是仿函數對象
// args 是函數的參數,(C++11新特新可變模板參數)
template <class Function, class... Args> explicit thread(Function&& f, Args&&... args);
// 線程不可複製
thread(const thread&) = delete;
// 線程可以移動
thread(thread&& other);

thread成員函數

  • get_id(): 獲取線程id
  • joinable(): 線程是否是可以合併的
  • join(): 合併線程
  • detach(): 分離線程
    join和detach是指主線程是否需要等待子線程執行完成,主線程調用join後將等待子線程執行完成,detach表示和主線程分離,子線程單獨執行,一個線程在構造後必須調用join或者detach,編譯器無法自動選擇其中行爲

當前線程操作函數,這些函數都定義在std::this_thread命名空間內

  • yield():當前線程將CPU讓出,等待下次被調度
  • get_id():獲取當前線程的線程id
  • sleep_for():當前線程休眠一段時間
  • sleep_until():當前線程休眠到某個時間點
#include <iostream>
#include <thread>

// 無參數線程函數
void thread_func_with_no_param() {
    std::this_thread::sleep_for(std::chrono::milliseconds(20));
    std::cout << "thread_func_with_no_param" << std::endl;
}

// 帶參數線程函數
void thread_func_with_param(int a, int b, int& result) {
    std::this_thread::sleep_for(std::chrono::milliseconds(40));
    result = a + b;
    std::cout << "thread_func_with_param: " << a << " + " << b << " = " << result << std::endl;
}

// 線程仿函數
struct thread_func_struct {
    void operator()(int a, int b, int& result) {
        std::this_thread::sleep_for(std::chrono::milliseconds(60));
        result = a * b;
        std::cout << "thread_func_struct: " << a << " * " << b << " = " << result << std::endl;
    }
};

void thread_usage() {
    int a = 1, b = 2, result1, result2;

    std::thread thread1(thread_func_with_no_param);
    // 此處的必須使用std::ref傳入result1的引用,下面一樣
    std::thread thread2(thread_func_with_param, a, b, std::ref(result1));
    std::thread thread3(thread_func_struct(), a, b, std::ref(result2));

    thread1.join();
    thread2.join();
    thread3.join();
    // thread1.detach();
    // thread2.detach();
    // thread3.detach();

    std::cout << "result1: " << result1 << std::endl;
    std::cout << "result2: " << result2 << std::endl;
}

mutex

多個線程同時訪問共享資源的時候需要需要用到互斥量,當一個線程鎖住了互斥量後,其他線程必須等待這個互斥量解鎖後才能訪問它。thread提供了四種不同的互斥量:

  • mutex:最基本的Mutex類。
  • recursive_mutex:遞歸Mutex類。
  • timed_mutex:定時Mutex類。
  • recursive_timed_mutex:定時遞歸Mutex類。
    std::mutex加解鎖是成對的,同一個線程內mutex在沒有解鎖的情況下,再次對它進行加鎖這是不對的,會得到一個未定義行爲;std::recursive_mutex與獨mutex不同的是,同一個線程內在互斥量沒有解鎖的情況下可以再次進行加鎖,不過他們的加解鎖次數需要一致;

std::mutex有如下幾個成員函數

構造函數,std::mutex不允許拷貝構造,也不允許move拷貝,最初產生的mutex對象是處於unlocked狀態的。

  • lock(),調用線程將鎖住該互斥量。線程調用該函數會發生下面3種情況:

    1. 如果該互斥量當前沒有被鎖住,則調用線程將該互斥量鎖住,直到調用unlock之前,該線程一直擁有該鎖。
    2. 如果當前互斥量被其他線程鎖住,則當前的調用線程被阻塞住。
    3. 如果當前互斥量被當前調用線程鎖住,則會產生死鎖(deadlock)。
  • unlock(), 解鎖,釋放對互斥量的所有權。

  • try_lock(),嘗試鎖住互斥量,如果互斥量被其他線程佔有,則當前線程也不會被阻塞。線程調用該函數也會出現下面3種情況:

    1. 如果當前互斥量沒有被其他線程佔有,則該線程鎖住互斥量,直到該線程調用unlock釋放互斥量。
    2. 如果當前互斥量被其他線程鎖住,則當前調用線程返回false,而並不會被阻塞掉。
    3. 如果當前互斥量被當前調用線程鎖住,則會產生死鎖(deadlock)。

std::timed_mutex 比 std::mutex 多了兩個成員函數,try_lock_for(),try_lock_until()。

  • try_lock_for() 函數接受一個時間範圍,表示在這一段時間範圍之內線程如果沒有獲得鎖則被阻塞住(與std::mutex的 try_lock()不同,try_lock如果被調用時沒有獲得鎖則直接返回 false),如果在此期間其他線程釋放了鎖,則該線程可以獲得對互斥量的鎖,如果超時(即在指定時間內還是沒有獲得鎖),則返回 false。
  • try_lock_until() 函數則接受一個時間點作爲參數,在指定時間點未到來之前線程如果沒有獲得鎖則被阻塞住,如果在此期間其他線程釋放了鎖,則該線程可以獲得對互斥量的鎖,如果超時(即在指定時間內還是沒有獲得鎖),則返回 false。

  • std::lock_guardstd::unique_lock,與RAII相關,能自動上鎖和解鎖
void thread_func1(std::mutex& m) {
    for (int i = 0; i < 10; i++) {
        m.lock();       // 加鎖
        std::cout << "thread1 " << i << std::endl;
        m.unlock();     // 解鎖
        std::this_thread::sleep_for(std::chrono::milliseconds(20));
    }
}

void thread_func2(std::mutex& m) {
    for (int i = 0; i < 10; i++) {
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
        // lock_guard在構造後加鎖,在作用域解釋後自動釋放鎖
        std::lock_guard<std::mutex> lg(m);
        std::cout << "thread2 " << i << std::endl;
    }
}

void mutex_usage() {
    std::mutex m;

    std::thread thread1(thread_func1, std::ref(m));
    std::thread thread2(thread_func2, std::ref(m));

    thread1.join();
    thread2.join();
}

condition_variable

當std::condition_variable對象的某個wait函數被調用的時候,它使用std::unique_lock(通過std::mutex) 來鎖住當前線程。當前線程會一直被阻塞,直到另外一個線程在相同的std::condition_variable對象上調用了notification函數來喚醒當前線程。

std::condition_variable對象通常使用std::unique_lock來等待,如果需要使用另外的lockable類型,可以使用std::condition_variable_any類

condition_variable成員函數

  • condition_variable 不可拷貝不可賦值
  • notify_one():喚醒一個等待的線程
  • notify_all():喚醒所有等待的線程
  • wait():阻塞等待直到被喚醒
  • wait_for():阻塞等待被喚醒,或者超時
  • wait_until():阻塞等待被喚醒,或者到某個時間點
// wait
void wait(std::unique_lock<std::mutex>& lock);
template <class Predicate>
void wait(std::unique_lock<std::mutex>& lock, Predicate pred);

// wait_for
template <class Rep, class Period>
std::cv_status wait_for(
    std::unique_lock<std::mutex>& lock,
    const std::chrono::duration<Rep, Period>& rel_time);
template <class Rep, class Period, class Predicate>
bool wait_for(
    std::unique_lock<std::mutex>& lock,
    const std::chrono::duration<Rep, Period>& rel_time,
    Predicate pred);

// wait_until
template<class Clock, class Duration>
std::cv_status wait_until(
    std::unique_lock<std::mutex>& lock,
    const std::chrono::time_point<Clock, Duration>& timeout_time);
template<class Clock, class Duration, class Predicate>
bool wait_until(
    std::unique_lock<std::mutex>& lock,
    const std::chrono::time_point<Clock, Duration>& timeout_time,
    Predicate pred);
#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void print_id (int id) {
    std::unique_lock<std::mutex> lck(mtx);
    // 下面兩句話是一樣的
    // while (!ready) cv.wait(lck);
    cv.wait(lck, []{return ready;});
    std::cout << "thread " << id << '\n';
}

void go() {
    std::unique_lock<std::mutex> lck(mtx);
    ready = true;
    cv.notify_all();
}

void condition_variable_usage() {
    std::thread threads[10];
    // spawn 10 threads:
    for (int i=0; i<10; ++i) {
        threads[i] = std::thread(print_id, i);
    }

    std::cout << "10 threads ready to race...\n";
    go();

    for (auto& th : threads) {
        th.join();
    }
}

atomic

原子類型對象的主要特點就是從不同線程訪問不會導致數據競爭(data race)。因此從不同線程訪問某個原子對象是良性 (well-defined) 行爲,而通常對於非原子類型而言,併發訪問某個對象(如果不做任何同步操作)會導致未定義 (undifined) 行爲發生。

#include <atomic>
int main() {
    std::atomic<int> ai(5);
    ai++;
    ai += 100;

    return 0;
}

隨機數


標準把隨機數抽象成隨機數引擎和分佈兩部分.引擎用來產生隨機數,分佈產生特定分佈的隨機數(比如平均分佈,正太分佈等)

標準提供三種常用的引擎:

  • linear_congruential_engine
  • mersenne_twister_engine
  • subtract_with_carry_engine。

第一種是線性同餘算法,第二種是梅森旋轉算法,第三種帶進位的線性同餘算法。第一種是最常用的,而且速度也是非常快的.

隨機數引擎接受一個整形參數當作種子,不提供的話,會使用默認值,推薦使用random_device來產生一個隨機數當作種子

#include <iostream>
#include <random>

int main() {
    {
        // random_device是一個隨機數設備,不同的操作系統有不同的實現,linux下是讀取/dev/urandom設備
        std::random_device rd;
        for (int i = 0; i < 10; i++) {
            std::cout << rd() << std::endl;
        }
    }

    {
        std::random_device rd;
        // 用random_device來爲隨機數生成器設置種子
        std::mt19937_64 mt(rd());
        for (int i = 0; i < 10; i++) {
            std::cout << mt() << std::endl;
        }

        // 整數均勻分佈
        std::uniform_int_distribution<unsigned> dis(1, 100);
        for (int i = 0; i < 10; i++) {
            std::cout << dis(mt) << std::endl;
        }

        // 浮點數均勻分佈
        std::uniform_real_distribution<double> drs(0.0, 1.0);
        for (int i = 0; i < 10; i++) {
            std::cout << drs(mt) << std::endl;
        }
    }
}

轉載內容來自: https://segmentfault.com/a/1190000003004734

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章