3

C++11新特性

auto,decltype,for,nullptr

如果编译器在定义一个变量的时候可以推断出变量的类型,不用写变量的类型,你只需写auto即可

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

auto处理引用时默认是值类型,可以指定&作为修饰符强制它作为引用,auto自动获取指针类型,也可以显示地指定指针类型

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

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

在迭代器中使用auto,简化代码

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

在模板中使用auto

cpptemplate <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,包括函数的返回值

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

新的函数返回值声明语法,把返回类型放在函数声明的后面,用auto代替前面的返回类型

cpp// 这两种函数声明方式等效
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循环遍历容器

cppstd::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表达式语法形式如下:

cpp[ 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:函数体
cpp// 数组累加
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<>类型,可以通过{}语法来构造初始化列表

cpp// 容器初始化
// {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,表示派生类不应当重写这个虚函数
cppclass 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关键字来表明我们不希望编译器生成默认的构造函数或赋值函数。

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

C++11允许成员变量就地初始化

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

委托构造函数,一个委托构造函数使用它所属类的其他构造函数执行它的初始化过程

cppclass 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语义与右值引用

左值和右值是针对表达式而言,表达式之后依然存在的对象是左值,表达式之后不再存在的临时对象为右值
左值可以对其取地址,右值不能

cppint i = 0;
std::string hello = "hello";
std::string world = "world";
const int& ri = 1;

// 左值:i, ++i, hello, world
// 右值:i++, hello + world, ri

拷贝临时对象性能问题,考虑如下字符串初始化

cppstd::string str1 = "a";
// str1 + "b" 生成一个临时对象,再用这个临时对象去构造str2
// 而这个临时对象在构造完str2后就被释放,这个对象并没有用到,却需要调用一次构造函数
std::string str2 = str1 + "b";

如果我们能确定某个值是一个非常量右值(或者是一个以后不会再使用的左值),则我们在进行临时对象的拷贝时,可以不用拷贝实际的数据,而只是窃取实际数据的指针,C++11中引入的右值引用正好可用于标识一个非常量右值。C++ 11中用&表示左值引用,用&&表示右值引用

移动构造函数

cpp// 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之后,不应该再使用该对象,对该对象的任何操作都是未定义的。

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

容器

std::vector

  • size():记录了当前元素的个数
  • capacity():不重新分配内存的话,可以保存多少个元素
  • reserve(n):分配至少能容纳n个元素的内存空间
  • shrink_to_fit():将capacity减少为于size()相同的大小
cpp// 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的长度是固定的,不能动态拓展

cpptemplate <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

cppstd::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的容器能获得更高的性能

cppstd::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的弱引用,不修改引用计数,为了解决循环引用问题而引入
cpp#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

cpptypedef 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个子表达式匹配的字符串
  • []:第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)
cpp#include <iostream>
#include <regex>
#include <cassert>

int main()
{
    std::string context = ""
        "hatlonely (hatlonely@gmail.com) "
        "playjokes (playjokes@gmail.com)";

    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] == "hatlonely@gmail.com");
    // 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 (playjokes@gmail.com)");
    // $`  相当于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 (hatlonely@gmail.com) playjokes (playjokes@gmail.com)
        std::cout << context << std::endl;
        // hatlonely (hatlonely) playjokes (playjokes)
        std::cout << regex_replace(context, mail_regex, "$1") << std::endl;
        // hatlonely (hatlonely) playjokes (playjokes@gmail.com)
        std::cout << regex_replace(context, mail_regex, "$1",
            std::regex_constants::format_first_only) << std::endl;
    }

    return 0;
}

多线程

thread

构造函数

cppthread();
// 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():当前线程休眠到某个时间点
cpp#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相关,能自动上锁和解锁

cppvoid 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():阻塞等待被唤醒,或者到某个时间点
cpp// 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);
cpp#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) 行为发生。

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

    return 0;
}

随机数

标准把随机数抽象成随机数引擎和分布两部分.引擎用来产生随机数,分布产生特定分布的随机数(比如平均分布,正太分布等)

标准提供三种常用的引擎:linear_congruential_enginemersenne_twister_enginesubtract_with_carry_engine。第一种是线性同余算法,第二种是梅森旋转算法,第三种带进位的线性同余算法。第一种是最常用的,而且速度也是非常快的

随机数引擎接受一个整形参数当作种子,不提供的话,会使用默认值,推荐使用random_device来产生一个随机数当作种子

cpp#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;
        }
    }
}

链接


hatlonely
1.1k 声望24 粉丝

下一篇 »
vim