C++ 11 新特性 汇总篇

目录

多线程Thread

Abstract

构造函数ProtoType

默认构造函数

初始化构造函数

拷贝构造函数

Move 构造函数

主要成员函数

get_id()

joinable()

join()

deteach()

简单线程的创建

线程封装

线程变量 - thread_local

ABSTRACT

C++ Storage Type

Demo

互斥量

分类

独占互斥量 std::mutex

成员函数

递归互斥量std::recursive_mutex

带超时的互斥量std::timed_mutex和 std::recursive_timed_mutex

lock_guard和unique_lock的使用和区别

condition_variable - 条件变量

ABSTRACT

Member Functions

std::condition_variable::wait

prototype

abstract

parameters

return value

demo

std::condition_variable::wait_for

prototype

abstract

parameters

return value

demo

std::condition_variable::wait_until

prototype

abstract

parameters

return value

demo

原子操作-atomic

ABSTRACT

内存顺序模型

Template parameters

T

Member Functions

General atomic operations

Operations supported by certain specializations (integral and/or pointer)

std::atomic::store & load

prototype

abstract

parameters

demo

prototype

abstract

return value

demo

std::atomic::compare_exchange_weak

prototype

abstract

return value

demo

Reference counting - 引用计数

abstract

Implementation

Usage

Spinlock - 自旋锁

abstract

Implementation

Usage

Wait-free ring buffer - 无锁环形队列

abstract

Implementation

Usage

Lock-free multi-producer queue- 无锁多生产者队列

abstract

Implementation

Usage

异步操作future & async & package_task & promise

std::future

What

How

Demo

std::async

What

std::package_task

What

How

Demo

std::promise

What

How

Demo

单次操作 - call_once

What

How

Demo

std::function & std::bind & lambda表达式

std::function

Abstract

How

Demo

std::bind

Abstract

How

Demo

lambda表达式

Abstract

How

Demo

模板

右尖括号

Abstract

模板的别名

Abstract

Demo

函数模板的默认模板参数

Abstract

Demo

变长参数模板

Abstract

How

Demo

右值与完美转发

左右值的理解

一般左值

一般右值

纯右值

将亡值

右值引用

移动构造函数

完美转发

abstract

Demo

C++ 返回值优化RVO

Abstract

How

Demo

智能指针

共享的智能指针shared_ptr

Abstract

How

Demo

独占的智能指针unique_ptr

Abstract

How

Demo

弱引用的智能指针weak_ptr

Abstract

How

Demo

shared_ptr 源码分析

gcc版本源码分析图

分析

内存对齐alignas、alignof关键字

为什么要做内存对齐

alignof和 std::alignment_of

Abstract

Demo

alignas

Abstract

Demo

std::aligned_storage

Abstract

max_align_t 和 std::align

Abstract

Demo

Enum Class

Abstract

chrono

Abstract

duration

Abstract

How

Demo

time_point

Abstract

How

Demo

Clock

Abstract

How

Demo

Template

新容器

forward_list

Abstract

How

Demo

array

Abstract

Comparewith vector

Compare with original array

How

Demo

tuple

Abstract

How

无序容器

Abstract

Demo

新算法

Abstract

Demo


多线程Thread

Abstract

Class to represent individual threads of execution.

A thread of execution is a sequence of instructions that can be executed concurrently with other such sequences in multithreading environments, while sharing a same address space.

An initialized thread object represents an active thread of execution; Such a thread object is joinable, and has a unique thread id.

A default-constructed (non-initialized) thread object is not joinable, and its thread id is common for all non-joinable threads.

A joinable thread becomes not joinable if moved from, or if either join or detach are called on them.

/

hread类来表示执行的各个线程。

执行线程是一个指令序列,它可以在多线程环境中与其他这样的序列并发执行,同时共享相同的地址空间。

一个初始化的线程对象表示一个活动的执行线程;这样的线程对象是可接合的,并且具有惟一的线程id。

默认构造(非初始化)的线程对象是不可join的,它的线程id对于所有不可接合的线程都是通用的。

如果将可join线程从,或者对它们调用join或detach,则可join线程将变得不可join。

std::thread 在 #include <Thread> 头文件中声明,因此使用 std::thread 时需要包含 #include<Thread>头文件。

构造函数ProtoType

默认构造函数

//创建一个空的 thread 执行对象。
thread() _NOEXCEPT 
{ 
    // construct with no thread 
    _Thr_set_null(_Thr); 
  }

初始化构造函数

//创建std::thread执行对象,该thread对象可被joinable,新产生的线程会调用threadFun函数,该函 数的参数由 args 给出
template<class Fn, class... Args> 
explicit thread(Fn&& fn, Args&&... args);

拷贝构造函数

// 拷贝构造函数(被禁用),意味着 thread 不可被拷贝构造。
thread(const thread&) = delete;

Move 构造函数

//move 构造函数,调用成功之后 x 不代表任何 thread 执行对象
thread(thread&& x)noexcept
#include<thread> 
using namespace std; 
void threadFun(int &a) // 引用传递
{
	cout << "this is thread fun !" <<endl; 
	cout <<" a = "<<(a+=10)<<endl;
}
int main() 
{ 
	int x = 10; 
	thread t1(threadFun, std::ref(x)); 
	thread t2(std::move(t1)); // t1 线程失去所有权 
	thread t3; 
	t3 = std::move(t2); // t2 线程失去所有权 
	//t1.join(); // ? 
	t3.join(); 
	cout<<"Main End "<<"x = "<<x<<endl;
	return 0; 
}

主要成员函数

get_id()

获取线程ID,返回类型std::thread::id对象。

joinable()

判断线程是否可以加入等待

join()

等待该线程执行完成之后才会返回

deteach()

detach调用之后,目标线程就成为了守护线程,驻留后台运行,与之关联的std::thread对象

失去对目标线程的关联,无法再通过std::thread对象取得该线程的控制权。当线程主函数执

行完之后,线程就结束了,运行时库负责清理与该线程相关的资源。

调用 detach 函数之后:

*this 不再代表任何的线程执行实例。

joinable() == false

get_id() == std::thread::id()

简单线程的创建

#include <iostream>
#include <thread> // head file



using namespace std;
// 1 传入0个值 
void func1() { cout << "func1 into" << endl; }
// 2 传入2个值
void func2(int a, int b) { cout << "func2 a + b = " << a+b << endl; }
//3 传入引用
void func3(int &c) { cout << "func3 c = " << &c << endl; c += 10; }
class A
{
	public: 
		 // 4. 传入类函数 
		 void func4(int a) 
		 { 
			 // std::this_thread::sleep_for(std::chrono::seconds(1));
			 cout << "thread:" << name_<< ", fun4 a = " << a << endl;
		 }
		 void setName(string name) { name_ = name; }
		 void displayName() { cout << "this:" << this << ", name:" << name_ << endl; }
		 void play() { std::cout<<"play call!"<<std::endl; } 
		 private: string name_; 
};
//5. detach 
void func5() 
{
	cout << "func5 into sleep " << endl; 
	std::this_thread::sleep_for(std::chrono::seconds(1)); 
	cout << "func5 leave " << endl; 
}
// 6. move 
void func6() { cout << "this is func6 !" <<endl; }
int main() {// 1. 传入0个值 
	cout << "\n\n main1--------------------------\n";
	std::thread t1(&func1); 
	// 只传递函数 
	t1.join(); 
	// 阻塞等待线程函数执行结束 
	// 2. 传入2个值
	cout << "\n\n main2--------------------------\n"; 
	int a =10; int b =20; 
	std::thread t2(func2, a, b); 
	// 加上参数传递,可以任意参数 
	t2.join(); 
	// 3. 传入引用 
	cout << "\n\n main3--------------------------\n"; 
	int c =10; 
	std::thread t3(func3, std::ref(c));
	// 加上参数传递,可以任意参数 
	t3.join(); 
	cout << "main3 c = " << &c << ", "<<c << endl; 
	// 4. 传入类函数 
	cout << "\n\n main4--------------------------\n"; 
	A * a4_ptr = new A(); 
	a4_ptr->setName("darren"); 
	std::thread t4(A::func4, a4_ptr, 10); 
	t4.join(); 
	delete a4_ptr; 
	// 5.detach 
	cout << "\n\n main5--------------------------\n"; 
	std::thread t5(&func5); 
	// 只传递函数 
	t5.detach(); 
	// 脱离 // 
	std::this_thread::sleep_for(std::chrono::seconds(2)); // 如果这里不休眠会怎么 样 
	cout << "\n main5 end\n"; 
	// 6.move 
	cout << "\n\n main6--------------------------\n"; 
	int x = 10; 
	thread t6_1(func6); 
	thread t6_2(std::move(t6_1)); 
	// t6_1 线程失去所有权 
	t6_1.join(); // 抛出异常 
	t6_2.join(); 
	return 0; }

线程封装

#ifndef ZERO_THREAD_H 
#define ZERO_THREAD_H
#include <thread> 
class ZERO_Thread
{
	public: 
	ZERO_Thread(); // 构造函数 
	virtual ~ZERO_Thread(); // 析构函数 
	bool start(); 
	void stop(); 
	bool isAlive() const; // 线程是否存活. 
	std::thread::id id() { return th_->get_id(); } 
	std::thread* getThread() { return th_; } 
	void join(); // 等待当前线程结束, 不能在当前线程上调用 
	void detach(); //能在当前线程上调用 
	static size_t CURRENT_THREADID(); 
	protected:
	void threadEntry(); 
	virtual void run() = 0; // 运行 
	protected: bool running_; //是否在运行 
	std::thread *th_;
};
#endif // ZERO_THREAD_H
#include "zero_thread.h" 
#include <sstream> 
#include <iostream> 
#include <exception> 
ZERO_Thread::ZERO_Thread(): running_(false), th_(NULL) {}
ZERO_Thread::~ZERO_Thread() { 
	if(th_ != NULL) 
	{ //如果到调用析构函数的时候,调用者还没有调用join则触发detach,此时是一个比较危险的动 作,用户必须知道他在做什么 
		if (th_->joinable()) 
		{ 
			std::cout << "~ZERO_Thread detach\n"; 
			th_->detach(); 
		}
		delete th_; 
		th_ = NULL; 
	}
	std::cout << "~ZERO_Thread()" << std::endl;
}
bool ZERO_Thread::start() 
{ 
	if (running_) {
		return false; 
	}try { 
		th_ = new std::thread(ZERO_Thread::threadEntry, this);
	}catch(...) { 
		throw "[ZERO_Thread::start] thread start error"; 
	}return true; 
}
void ZERO_Thread::stop() { running_ = false; }
bool ZERO_Thread::isAlive() const { return running_; }
void ZERO_Thread::join() { 
	if (th_->joinable()) { 
		th_->join(); // 不是detach才去join 
	} 
}
void ZERO_Thread::detach() 
{
	th_->detach(); 
}
size_t ZERO_Thread::CURRENT_THREADID() { 
	// 声明为thread_local的本地变量在线程中是持续存在的,不同于普通临时变量的生命周期, 
	// 它具有static变量一样的初始化特征和生命周期,即使它不被声明为static。
	static thread_local size_t threadId = 0; 
	if(threadId == 0 ) { 
		std::stringstream ss; 
		ss << std::this_thread::get_id(); 
		threadId = strtol(ss.str().c_str(), NULL, 0);
	}return threadId;
}
void ZERO_Thread::threadEntry() { 
	running_ = true; 
	try { 
		run(); // 函数运行所在 
	}
	catch (std::exception &ex) { 
		running_ = false; 
		throw ex; 
	}catch (...) { 
		running_ = false; 
		throw;
	}running_ = false;
}
#include <iostream> 
#include <chrono> 
#include "zero_thread.h" 
using namespace std; 
class A: public ZERO_Thread
{
	public: 
	void run() { 
		while (running_) { 
			cout << "print A " << endl; 
			std::this_thread::sleep_for(std::chrono::seconds(5)); 
			}
			cout << "----- leave A " << endl; 
	}
};
class B: public ZERO_Thread 
{
	public: 
	void run() { 
		while (running_) { 
		cout << "print B " << endl; 
		std::this_thread::sleep_for(std::chrono::seconds(2)); 
		}
		cout << "----- leave B " << endl; 
		} 
};
int main() { 
	{ 
		A a; a.start(); 
		B b; b.start(); 
		std::this_thread::sleep_for(std::chrono::seconds(5));
		a.stop(); a.join(); b.stop(); b.join(); // 需要我们自己join 
	}
	cout << "Hello World!" << endl; 
	return 0; 
}

线程变量 - thread_local

ABSTRACT

In C++, thread_local is defined as a specifier to define the thread-local data and this data is created when the thread is created and destroyed when the thread is also destroyed, hence this thread-local data is known as thread-local storage. This thread_local is one of the storage classes other than extern and static specifiers. Therefore a variable declared as thread_local. It copies its thread as each thread created the variable is also created and hence this thread_local specifier can be declared or defined only on variable and this cannot be applied to functions definition or declarations and the declaration can be done only during the static duration.

在c++中,thread_local被定义为一个定义线程本地数据的关键字,该数据在创建线程时创建,在销毁线程时销毁,因此这种线程本地数据被称为线程本地存储。这个thread_local是extern和静态说明符以外的存储类之一。

因此,一个变量声明为thread_local。每一个被创建的线程都会复制自己的thread_local变量,因此这个thread_local说明符只能在变量上声明或定义,而不能应用于函数定义或声明,声明只能在静态期间完成。

C++ Storage Type

automatic

Temporary variable,Block scope, automatic allocation and destruction

临时变量,作用域在一个所属代码块内,代码块结束释放

All local objects that are not declared static, extern, or thread_local have this storage period.

未声明为 static、extern 或 thread_local 的所有局部对象均拥有此存储期。

static

The storage of such objects is allocated at the beginning of the program and unallocated at the end of the program. There is only one instance of such an object. All objects declared in the scope of namespaces (including global namespaces), plus objects declared static or extern, have this storage period. For details on initialization of objects with this storage period, see nonlocal and static local variables

这类对象的存储在程序开始时分配,并在程序结束时解分配。这类对象只存在一个实例。所有在命名空间(包含全局命名空间)作用域声明的对象,加上声明带有 static 或 extern 的对象均拥有此存储期。有关拥有此存储期的对象的初始化的细节与非局部变量与静态局部变量一致

thread

The storage of such objects is allocated at the start of the thread and unallocated at the end of the thread. Each thread has its own object instance. Only objects declared thread_local have this storage period. Thread_local can appear with static or extern, which are used to adjust links. Details about the initialization of objects with this storage period are consistent with non-local and static local variables.

这类对象的存储在线程开始时分配,并在线程结束时解分配。每个线程拥有它自身的对象实例。只有声明为 thread_local 的对象拥有此存储期。thread_local 能与 static 或 extern 一同出现,它们用于调整链接。关于具有此存储期的对象的初始化的细节与与非局部变量和静态局部变量一致。

dynamic

The storage of these objects is allocated and unallocated on request using dynamic memory allocation functions (new, malloc).

这类对象的存储是通过使用动态内存分配函数(new、malloc)来按请求进行分配和解分配的.

在c++中,变量被声明为线程局部数据,使用下划线(_)后跟线程关键字,如__thread int a, __thread char s等,这些变量可以作为任何变量访问,如全局变量或文件范围或函数范围,而自动变量总是线程局部变量,因此线程局部说明符可以与静态说明符或extern说明符结合使用。

这种变量的初始化需要一个静态构造函数,如果这个带有命名空间或类作用域的thread_local变量可以作为线程启动的一部分进行初始化,并且只有当类的一个成员只能是线程局部的,因此每个变量在每个线程中都可以有一个副本时,它才是静态的。

而这些初始化的线程局部变量被分配在。tdata部分,未初始化的被存储为用“COMMON”符号定义的变量,对于每个创建或初始化的新线程,线程在线程局部存储中分配一个新的块,每个线程都有一个指向线程控制块的线程指针,并有当前执行线程的线程指针的指针的值。因此,线程本地存储只能在创建任何新线程或在加载共享对象后或在程序启动本身第一次引用任何线程本地存储块时创建。

Demo

#include <iostream>   // std::cout
#include <thread>     // std::thread thread_local

thread_local int n=2;

void thread_integer(int n_val) {
    n=n_val;
}

void thread_cnt() {
    std::cout<<n;
}

void thread_func(int td) {
    thread_integer(td);
    ++n;
    thread_cnt();
}

int main(){
    n=4;
    std::thread it1(thread_func,1);
    std::thread it2(thread_func,2);
    std::thread it3(thread_func,3);
    
    it1.join();
    it2.join();
    it3.join();
    std::cout<<"\nmajor thread num = "<<n<<std::endl;
}

互斥量

在C++11中需要包含<mutex>模块。而在该文件中还有其他和mutex协作的类和函数,使得多线程编程时非常方便。

分类

  • std::mutex,独占的互斥量,不能递归使用。
  • std::time_mutex,带超时的独占互斥量,不能递归使用。
  • std::recursive_mutex,递归互斥量,不带超时功能。
  • std::recursive_timed_mutex,带超时的递归互斥量。

独占互斥量 std::mutex

std::mutex 是C++11 中最基本的互斥量,std::mutex 对象提供了独占所有权的特性——即不支持递归地

对 std::mutex 对象上锁,而 std::recursive_lock 则可以递归地对互斥量对象上锁.

成员函数

构造函数

std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于

unlocked 状态的。

lock()

调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况:

(1). 如果该互斥量当前没 有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。

(2). 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。

(3). 如果当前互斥量被当前调用线程锁 住,则会产生死锁(deadlock)。

unlock()

解锁,释放对互斥量的所有权。

try_lock()

尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。线程调用该 函数也会出现下面 3 种情况:

(1). 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量。

(2). 如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉。

(3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。

递归互斥量std::recursive_mutex

递归锁允许同一个线程多次获取该互斥锁,可以用来解决同一线程需要多次获取互斥量时死锁的问题

#include <iostream> 
#include <thread> 
#include <mutex> 
struct Complex { 
    std::mutex mutex; 
    int i; 
    Complex() : i(1){} 
    void mul(int x) { 
        std::lock_guard<std::mutex> lock(mutex); 
        i *= x; 
    } //lock_guard析构时会自动释放锁
    void div(int x) { 
        std::lock_guard<std::mutex> lock(mutex); 
        i /= x; 
    }
    void both(int x, int y) { 
        std::lock_guard<std::mutex> lock(mutex); 
        mul(x); 
        div(y); 
    } 

};
int main(void) 
{ 
    Complex complex; 
    std::cout<<"start demo" <<std::endl;
    std::thread th(&Complex::both,&complex,32,23);
    th.join();
    std::cout<<"finish demo "<<complex.i <<std::endl;
    return 0; 
}
#include <iostream> 
#include <thread> 
#include <mutex> 
struct Complex { 
    std::recursive_mutex mutex; 
    int i; 
    Complex() : i(1){} 
    void mul(int x) { 
        std::lock_guard<std::recursive_mutex> lock(mutex); 
        i *= x; 
    } //lock_guard析构时会自动释放锁
    void div(int x) { 
        std::lock_guard<std::recursive_mutex> lock(mutex); 
        i /= x; 
    }
    void both(int x, int y) { 
        std::lock_guard<std::recursive_mutex> lock(mutex); 
        mul(x); 
        div(y); 
    } 

};
int main(void) 
{ 
    Complex complex; 
    std::cout<<"start demo" <<std::endl;
    std::thread th(&Complex::both,&complex,32,23);
    th.join();
    std::cout<<"finish demo "<<complex.i <<std::endl;
    return 0; 
}

带超时的互斥量std::timed_mutex和 std::recursive_timed_mutex

std::timed_mutex比std::mutex多了两个超时获取锁的接口:try_lock_for和try_lock_until

//1-2-timed_mutex 
#include <iostream> 
#include <thread> 
#include <mutex> 
#include <chrono> 
std::timed_mutex mutex; 
void work() { 
    std::chrono::milliseconds timeout(100); 
    while (true) { 
        if (mutex.try_lock_for(timeout)) { 
            std::cout << std::this_thread::get_id() << ": do work with the mutex" << std::endl; 
            std::chrono::milliseconds sleepDuration(250); 
            std::this_thread::sleep_for(sleepDuration); 
            mutex.unlock(); 
            std::this_thread::sleep_for(sleepDuration); 
            }else { 
                std::cout << std::this_thread::get_id() << ": do work without the mutex" << std::endl; 
                std::chrono::milliseconds sleepDuration(100); 
                std::this_thread::sleep_for(sleepDuration); 
            } 
    } 
}
int main(void) { 
    std::thread t1(work); 
    std::thread t2(work); 
    t1.join(); t2.join(); 
    std::cout << "main finish\n"; 
    return 0; 
    }

lock_guard和unique_lock的使用和区别

unique_lock与lock_guard都能实现自动加锁和解锁,但是前者更加灵活,能实现更多的功能。

unique_lock可以进行临时解锁和再上锁,如在构造对象之后使用lck.unlock()就可以进行解锁,

lck.lock()进行上锁,而不必等到析构时自动解锁。

必须使用unique_lock 的场景:需要结合notify+wait的场景使用unique_lock;

1. unique_lock 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与

条件变量一同使用。

2. unique_locklock_guard使用更加灵活,功能更加强大。

3. 使用unique_lock需要付出更多的时间、性能成本。

#include <iostream> 
#include <deque> 
#include <thread> 
#include <mutex> 
#include <condition_variable> 
#include <unistd.h> 
std::deque<int> q; 
std::mutex mu; 
std::condition_variable cond; 
int count = 0; 
void fun1() { 
    while (true) { 
       std::unique_lock<std::mutex> locker(mu);
       q.push_front(count++); 
       locker.unlock(); // 这里是不是必须的? 
       cond.notify_one(); // } 
       sleep(1); 
       } 
}
void fun2() { 
    while (true) { 
        std::unique_lock<std::mutex> locker(mu); 
        cond.wait(locker, [](){return !q.empty();}); 
        auto data = q.back(); 
        q.pop_back(); 
        // locker.unlock(); // 这里是不是必须的? 
        std::cout << "thread2 get value form thread1: " << data << std::endl; 
        } 
}
int main() { 
    std::thread t1(fun1); 
    std::thread t2(fun2); 
    t1.join(); t2.join(); 
    return 0; 
}

condition_variable - 条件变量

ABSTRACT

A condition variable is an object able to block the calling thread until notified to resume. It uses a unique_lock (over a mutex) to lock the thread when one of its wait functions is called. The thread remains blocked until woken up by another thread that calls a notification function on the same condition_variable object. Objects of type condition_variable always use unique_lock<mutex> to wait: for an alternative that works with any kind of lockable type, see condition_variable_any.

条件变量是一个对象,它能够阻塞调用线程,直到通知它恢复。

当线程的一个等待函数被调用时,它使用unique_lock(通过互斥锁)来锁定线程。线程保持阻塞状态,直到另一个线程调用同一个condition_variable对象上的通知函数时才被唤醒。

condition_variable类型的对象总是使用unique_lock<mutex>等待:等待可用于任何类型的可锁定类型的替代方法,参见condition_variable_any.

Member Functions

wait

Wait until notified (public member function )

wait_for

Wait for timeout or until notified (public member function )

wait_until

Wait until notified or time point (public member function )

notify_one

Notify one (public member function )

notify_all

Notify all (public member function )

std::condition_variable::wait

prototype

void wait (unique_lock<mutex>& lck);

template <class Predicate> void wait (unique_lock<mutex>& lck, Predicate pred);

abstract

Wait until notified

The execution of the current thread (which shall have locked lck's mutex) is blocked until notified.

At the moment of blocking the thread, the function automatically calls lck.unlock(), allowing other locked threads to continue.

Once notified (explicitly, by some other thread), the function unblocks and calls lck.lock(), leaving lck in the same state as when the function was called. Then the function returns (notice that this last mutex locking may block again the thread before returning).

Generally, the function is notified to wake up by a call in another thread either to member notify_one or to member notify_all. But certain implementations may produce spurious wake-up calls without any of these functions being called. Therefore, users of this function shall ensure their condition for resumption is met.

If pred is specified (2), the function only blocks if pred returns false, and notifications can only unblock the thread when it becomes true (which is specially useful to check against spurious wake-up calls). This version (2) behaves as if implemented as:

while (!pred()) wait(lck);

等到通知

当前线程(应该已经锁定了llock的互斥锁)的执行被阻塞,直到通知。

在阻塞线程时,函数会自动调用lck.unlock(),从而允许其他被锁定的线程继续执行。

一旦得到通知(被其他线程显式地通知),该函数将解除阻塞并调用lck.lock(),使lck处于与调用该函数时相同的状态。然后函数返回(注意,最后一个互斥锁可能会在返回之前再次阻塞线程)。

通常,在另一个线程中调用notify_one成员或notify_all成员会通知该函数被唤醒。但是某些实现可能会在没有调用这些函数的情况下产生虚假的唤醒调用。因此,使用该功能的用户必须确保满足恢复使用的条件。

如果指定了pred参数,函数只会在pred返回false时阻塞,而通知只有在它变为true时才能解除阻塞(这对于检查虚假唤醒调用特别有用)。这个行为就像实现了:

while (!pred()) wait(lck);

parameters

lck

A unique_lock object whose mutex object is currently locked by this thread.
All concurrent calls to wait member functions of this object shall use the same underlying mutex object (as returned by lck.mutex()).

pred

A callable object or function that takes no arguments and returns a value that can be evaluated as a bool.
This is called repeatedly until it evaluates to true.


return value

None

demo

// condition_variable::wait (with predicate) #include <iostream> // std::cout #include <thread> // std::thread, std::this_thread::yield #include <mutex> // std::mutex, std::unique_lock #include <condition_variable> // std::condition_variable std::mutex mtx; std::condition_variable cv; int cargo = 0; bool shipment_available() {return cargo!=0;} void consume (int n) { for (int i=0; i<n; ++i) { std::unique_lock<std::mutex> lck(mtx); cv.wait(lck,shipment_available); // consume: std::cout << cargo << '\n'; cargo=0; } } int main () { std::thread consumer_thread (consume,10); // produce 10 items when needed: for (int i=0; i<10; ++i) { while (shipment_available()) std::this_thread::yield(); std::unique_lock<std::mutex> lck(mtx); cargo = i+1; cv.notify_one(); } consumer_thread.join(); return 0; }

std::condition_variable::wait_for

prototype

template <class Rep, class Period> cv_status wait_for (unique_lock<mutex>& lck, const chrono::duration<Rep,Period>& rel_time);

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

abstract

The execution of the current thread (which shall have locked lck's mutex) is blocked during rel_time, or until notified (if the latter happens first).

At the moment of blocking the thread, the function automatically calls lck.unlock(), allowing other locked threads to continue.

Once notified or once rel_time has passed, the function unblocks and calls lck.lock(), leaving lck in the same state as when the function was called. Then the function returns (notice that this last mutex locking may block again the thread before returning). Generally, the function is notified to wake up by a call in another thread either to member notify_one or to member notify_all. But certain implementations may produce spurious wake-up calls without any of these functions being called. Therefore, users of this function shall ensure their condition for resumption is met. If pred is specified (2), the function only blocks if pred returns false, and notifications can only unblock the thread when it becomes true (which is especially useful to check against spurious wake-up calls). It behaves as if implemented as:

return wait_until (lck, chrono::steady_clock::now() + rel_time, std::move(pred));

当前线程的执行(已经锁定了lock的互斥锁)在rel_time期间被阻塞,或者直到通知(如果后者先发生)之前。

在阻塞线程时,函数会自动调用lck.unlock(),从而允许其他被锁定的线程继续执行。

一旦收到通知或rel_time已通过,该函数就会解除阻塞并调用lck.lock(),使lck处于与调用该函数时相同的状态。然后函数返回(注意,最后一个互斥锁可能会在返回之前再次阻塞线程)。

通常,在另一个线程中调用notify_one成员或notify_all成员会通知该函数被唤醒。但是某些实现可能会在没有调用这些函数的情况下产生虚假的唤醒调用。因此,使用该功能的用户必须确保满足恢复使用的条件。

如果指定了pred参数,函数只会在pred返回false时阻塞,而通知只有在它变为true时才能解除阻塞(这对于检查虚假唤醒调用特别有用)。它的行为就像实现了:

返回wait_until (lck, chrono::steady_clock::now() + rel_time, std::move(pred));

parameters

lck

A unique_lock object whose mutex object is currently locked by this thread.
All concurrent calls to wait member functions of this object shall use the same underlying mutex object (as returned by lck.mutex()).

rel_time

The maximum time span during which the thread will block waiting to be notified.
duration is an object that represents a specific relative time.

pred

A callable object or function that takes no arguments and returns a value that can be evaluated as a bool.
This is called repeatedly until it evaluates to true.


return value

The unconditional version (1) returns cv_status::timeout if the function returns because rel_time has passed, or cv_status::no_timeout otherwise.
The predicate version (2) returns pred(), regardless of whether the timeout was triggered (although it can only be false if triggered).

demo

// condition_variable::wait_for example #include <iostream> // std::cout #include <thread> // std::thread #include <chrono> // std::chrono::seconds #include <mutex> // std::mutex, std::unique_lock #include <condition_variable> // std::condition_variable, std::cv_status std::condition_variable cv; int value; void read_value() { std::cin >> value; cv.notify_one(); } int main () { std::cout << "Please, enter an integer (I'll be printing dots): \n"; std::thread th (read_value); std::mutex mtx; std::unique_lock<std::mutex> lck(mtx); while (cv.wait_for(lck,std::chrono::seconds(1))==std::cv_status::timeout) { std::cout << '.' << std::endl; } std::cout << "You entered: " << value << '\n'; th.join(); return 0;

std::condition_variable::wait_until

prototype

template <class Clock, class Duration> cv_status wait_until (unique_lock<mutex>& lck, const chrono::time_point<Clock,Duration>& abs_time);

template <class Clock, class Duration, class Predicate> bool wait_until (unique_lock<mutex>& lck, const chrono::time_point<Clock,Duration>& abs_time, Predicate pred);

abstract

Wait until notified or time point

The execution of the current thread (which shall have locked lck's mutex) is blocked either until notified or until abs_time, whichever happens first.

At the moment of blocking the thread, the function automatically calls lck.unlock(), allowing other locked threads to continue.

Once notified or once it is abs_time, the function unblocks and calls lck.lock(), leaving lck in the same state as when the function was called. Then the function returns (notice that this last mutex locking may block again the thread before returning).

Generally, the function is notified to wake up by a call in another thread either to member notify_one or to member notify_all. But certain implementations may produce spurious wake-up calls without any of these functions being called. Therefore, users of this function shall ensure their condition for resumption is met.

If pred is specified (2), the function only blocks if pred returns false, and notifications can only unblock the thread when it becomes true (which is especially useful to check against spurious wake-up calls). It behaves as if implemented as:

while (!pred()) if ( wait_until(lck,abs_time) == cv_status::timeout) return pred(); return true;

当前线程(应该已经锁定了llock的互斥锁)的执行被阻塞,直到接到通知或者直到abs_time,无论哪个先发生。

在阻塞线程时,函数会自动调用lck.unlock(),从而允许其他被锁定的线程继续执行。

一旦收到通知或者是abs_time,函数就会解除阻塞并调用lck.lock(),使lck处于与调用函数时相同的状态。然后函数返回(注意,最后一个互斥锁可能会在返回之前再次阻塞线程)。

通常,在另一个线程中调用notify_one成员或notify_all成员会通知该函数被唤醒。但是某些实现可能会在没有调用这些函数的情况下产生虚假的唤醒调用。因此,使用该功能的用户必须确保满足恢复使用的条件。

如果指定了pred参数,函数只会在pred返回false时阻塞,而通知只有在它变为true时才能解除阻塞(这对于检查虚假唤醒调用特别有用)。它的行为就像实现了:

while (!pred()) if ( wait_until(lck,abs_time) == cv_status::timeout) return pred(); return true;

parameters

lck

A unique_lock object whose mutex object is currently locked by this thread.
All concurrent calls to wait member functions of this object shall use the same underlying mutex object (as returned by lck.mutex()).

abs_time

A point in time at which the thread will stop blocking, allowing the function to return.
time_point is an object that represents a specific absolute time.

pred

A callable object or function that takes no arguments and returns a value that can be evaluated as a bool.
This is called repeatedly until it evaluates to true.


return value

The unconditional version (1) returns cv_status::timeout if the function returns because abs_time has been reached, or cv_status::no_timeout otherwise.
The predicate version (2) returns pred(), regardless of whether the timeout was triggered (although it can only be false if triggered).

demo

// condition_variable example
#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);
  // ...
  std::cout << "thread " << id << '\n';
}

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

int main ()
{
  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();                       // go!

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

  return 0;
}

原子操作-atomic

ABSTRACT

Objects of atomic types contain a value of a particular type (T).

The main characteristic of atomic objects is that access to this contained value from different threads cannot cause data races (i.e., doing that is well-defined behavior, with accesses properly sequenced). Generally, for all other objects, the possibility of causing a data race for accessing the same object concurrently qualifies the operation as undefined behavior.

Additionally, atomic objects have the ability to synchronize access to other non-atomic objects in their threads by specifying different memory orders.

原子类型的对象包含一个特定类型(T)的值。

原子对象的主要特征是,从不同的线程访问这个包含的值不会导致数据竞争(也就是说,这样做是定义良好的行为,访问顺序正确)。一般来说,对于所有其他对象,由于并发访问同一对象而导致数据竞争的可能性,因此将该操作限定为未定义的行为。

此外,通过指定不同的内存顺序,原子对象能够同步访问其线程中的其他非原子对象。

内存顺序模型

内存顺序模型有下面四种,默认的序列为一致顺序

宽松顺序(Relaxed ordering):原子操作带上memory_order_relaxed参数,仅保证操作是原子性的,不提供任何顺序约束。

释放获得顺序(Release-Acquire ordering):对于同一个atomic,在线程A中使用memory_order_release调用store(),在线程B中使用memory_order_acquire调用load()。这种模型保证在store()之前发生的所有读写操作(A线程)不会在store()后调用,在load()之后发生的所有读写操作(B线程)不会在load()的前调用,A线程的所有写入操作对B线程可见。

释放消费顺序(Release-Consume ordering):释放获得顺序的弱化版,对于同一个atomic,在线程A中使用memory_order_release调用store(),在线程B中使用memory_order_consume调用load()。这种模型保证在store()之前发生的所有读写操作(A线程)不会在store()后调用,在load()之后发生的依赖于该atomic的读写操作(B线程)不会在load()的前面调用,A线程对该atomic的带依赖写入操作对B线程可见。

序列一致顺序(Sequential consistency):原子操作带上memory_order_seq_cst参数,这也是C++标准库的默认顺序,也是执行代价最大的,它是memory_order_acq_rel的加强版,如果是读取就是acquire语义,如果是写入就是 release 语义,且全部读写操作顺序均一致。

Template parameters

T

Type of the contained value.

This shall be a trivially copyable type.

包含值的类型。 这应该是一个可简单复制的类型。


Member Functions

General atomic operations

(constructor)

Construct atomic (public member function )

构造函数

operator=

Assign contained value (public member function )

传递包含的值(公共成员函数)

is_lock_free

Is lock-free (public member function )

判断在 *this 的基本操作是否存在任意的锁。(公共成员函数)

store

Modify contained value (public member function )

设置*this 的值(公共成员函数)

load

Read contained value (public member function )

获取*this 的值(公共成员函数)

operator T

Access contained value (public member function )

读取并返回该存储的值(公共成员函数)

exchange

Access and modify contained value (public member function )

访问和修改所包含的值(公共成员函数)

compare_exchange_weak

Compare and exchange contained value (weak) (public member function )

比较并改变包含的值(公共成员函数)

比较原子对象所包含值的内容与预期值:

-如果为真,它会用val替换包含的值(像store一样)。

-如果为false,则用包含的值替换expected。

这个函数可能在满足真的情况下仍然返回false,所以可以在循环里使用

compare_exchange_strong

Compare and exchange contained value (strong) (public member function )

比较并改变包含的值(公共成员函数)

比较原子对象所包含值的内容与预期值:

-如果为真,它会用val替换包含的值(像store一样)。

-如果为false,则用包含的值替换expected。

Operations supported by certain specializations (integral and/or pointer)

Do not use floating point types here

fetch_add

Add to contained value (public member function )

fetch_sub

Subtract from contained value (public member function )

fetch_and

Apply bitwise AND to contained value (public member function )

fetch_or

Apply bitwise OR to contained value (public member function )

fetch_xor

Apply bitwise XOR to contained value (public member function )

operator++

Increment container value (public member function )

operator--

Decrement container value (public member function )

std::atomic::store & load

prototype

void store (T val, memory_order sync = memory_order_seq_cst) volatile noexcept; void store (T val, memory_order sync = memory_order_seq_cst) noexcept;

abstract

Replaces the contained value with val.

The operation is atomic and follows the memory ordering specified by sync.

parameters

val

Value to copy to the contained object. T is atomic's template parameter (the type of the contained value).

sync

Synchronization mode for the operation. This shall be one of these possible values of the enum type memory_order:

There are six types of memory_orders defined by the C++ standard library, of which memory_order_acq_rel can be regarded as a combination of memory_order_acquire and memory_order_release.

typedef enum memory_order { memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_release, memory_order_acq_rel, memory_order_seq_cst } memory_order;

The memory order model is in appendix

demo

// atomic::load/store example
#include <iostream>       // std::cout
#include <atomic>         // std::atomic, std::memory_order_relaxed
#include <thread>         // std::thread

std::atomic<int> foo (0);

void set_foo(int x) {
  foo.store(x,std::memory_order_relaxed);     // set value atomically
}

void print_foo() {
  int x;
  do {
    x = foo.load(std::memory_order_relaxed);  // get value atomically
  } while (x==0);
  std::cout << "foo: " << x << '\n';
}

int main ()
{
  std::thread first (print_foo);
  std::thread second (set_foo,10);
  first.join();
  second.join();
  return 0;
}

prototype

T exchange (T val, memory_order sync = memory_order_seq_cst) volatile noexcept; T exchange (T val, memory_order sync = memory_order_seq_cst) noexcept;

abstract

Replaces the contained value by val and returns the value it had immediately before.

The entire operation is atomic (an atomic read-modify-write operation): the value is not affected by other threads between the instant its value is read (to be returned) and the moment it is modified by this function.

将包含的值替换为val并返回它之前的值。

整个操作是原子(原子的读-修改-写操作):价值不受其他线程之间的即时影响它的值是读取(返回),现在是修改这个函数。

return value

The contained value before the call. T is atomic's template parameter (the type of the contained value).

demo

// atomic::exchange example
#include <iostream>       // std::cout
#include <atomic>         // std::atomic
#include <thread>         // std::thread
#include <vector>         // std::vector

std::atomic<bool> ready (false);
std::atomic<bool> winner (false);

void count1m (int id) {
  while (!ready) {}                  // wait for the ready signal
  for (int i=0; i<1000000; ++i) {}   // go!, count to 1 million
  if (!winner.exchange(true)) { std::cout << "thread #" << id << " won!\n"; }
};

int main ()
{
  std::vector<std::thread> threads;
  std::cout << "spawning 10 threads that count to 1 million...\n";
  for (int i=1; i<=10; ++i) threads.push_back(std::thread(count1m,i));
  ready = true;
  for (auto& th : threads) th.join();

  return 0;
}

std::atomic::compare_exchange_weak

prototype

bool compare_exchange_weak (T& expected, T val, memory_order sync = memory_order_seq_cst) volatile noexcept; bool compare_exchange_weak (T& expected, T val, memory_order sync = memory_order_seq_cst) noexcept; bool compare_exchange_weak (T& expected, T val, memory_order success, memory_order failure) volatile noexcept; bool compare_exchange_weak (T& expected, T val, memory_order success, memory_order failure) noexcept;

abstract

Compares the contents of the atomic object's contained value with expected:

  • if true, it replaces the contained value with val (like store).
  • if false, it replaces expected with the contained value .

The function always accesses the contained value to read it, and -if the comparison is true- it then also replaces it. But the entire operation is atomic: the value cannot be modified by other threads between the instant its value is read and the moment it is replaced.

比较原子对象所包含值的内容与预期值:

-如果为真,它会用val替换包含的值(像store一样)。

-如果为false,则用包含的值替换expected。

该函数总是访问所包含的值来读取它,如果比较为真,那么它也会替换它。

这个函数可能在满足真的情况下仍然返回false,所以可以在循环里使用.

return value

true if expected compares equal to the contained value (and does not fail spuriously). false otherwise.

如果预期的值与所包含的值相等(没有因为代码错误而导致失败),则为True。

否则错误。

demo

// atomic::compare_exchange_weak example:
#include <iostream>       // std::cout
#include <atomic>         // std::atomic
#include <thread>         // std::thread
#include <vector>         // std::vector

// a simple global linked list:
struct Node { int value; Node* next; };
std::atomic<Node*> list_head (nullptr);

void append (int val) {     // append an element to the list
  Node* oldHead = list_head;
  Node* newNode = new Node {val,oldHead};

  // what follows is equivalent to: list_head = newNode, but in a thread-safe way:
  while (!list_head.compare_exchange_weak(oldHead,newNode))
    newNode->next = oldHead;
}

int main ()
{
  // spawn 10 threads to fill the linked list:
  std::vector<std::thread> threads;
  for (int i=0; i<10; ++i) threads.push_back(std::thread(append,i));
  for (auto& th : threads) th.join();

  // print contents:
  for (Node* it = list_head; it!=nullptr; it=it->next)
    std::cout << ' ' << it->value;
  std::cout << '\n';

  // cleanup:
  Node* it; while (it=list_head) {list_head=it->next; delete it;}

  return 0;
}

Reference counting - 引用计数

abstract

The purpose of a reference counter is to count the number of pointers to an object. The object can be destroyed as soon as the reference counter reaches zero.

Implementation

#include <boost/intrusive_ptr.hpp>
#include <boost/atomic.hpp>

class X {
public:
  typedef boost::intrusive_ptr<X> pointer;
  X() : refcount_(0) {}

private:
  mutable boost::atomic<int> refcount_;
  friend void intrusive_ptr_add_ref(const X * x)
  {
    x->refcount_.fetch_add(1, boost::memory_order_relaxed);
  }
  friend void intrusive_ptr_release(const X * x)
  {
    if (x->refcount_.fetch_sub(1, boost::memory_order_release) == 1) {
      boost::atomic_thread_fence(boost::memory_order_acquire);
      delete x;
    }
  }
};

Usage

X::pointer x = new X;

Spinlock - 自旋锁

abstract

The purpose of a spin lock is to prevent multiple threads from concurrently accessing a shared data structure. In contrast to a mutex, threads will busy-wait and waste CPU cycles instead of yielding the CPU to another thread. Do not use spinlocks unless you are certain that you understand the consequences.

旋转锁的目的是防止多个线程并发地访问共享数据结构。与互斥锁相反,线程将忙等待并浪费CPU周期,而不是将CPU让给另一个线程。不要使用自旋锁,除非你确定你明白其后果,可能的使用场景是:在现有系统中的锁操作是短时锁的情况下,要求线程强制一定顺序去执行。

Implementation

#include <boost/atomic.hpp>

class spinlock {
private:
  typedef enum {Locked, Unlocked} LockState;
  boost::atomic<LockState> state_;

public:
  spinlock() : state_(Unlocked) {}

  void lock()
  {
    while (state_.exchange(Locked, boost::memory_order_acquire) == Locked) {
      /* busy-wait */
    }
  }
  void unlock()
  {
    state_.store(Unlocked, boost::memory_order_release);
  }
};

Usage

spinlock s;

s.lock(); // access data structure here

s.unlock();

Wait-free ring buffer - 无锁环形队列

abstract

A wait-free ring buffer provides a mechanism for relaying objects from one single "producer" thread to one single "consumer" thread without any locks. The operations on this data structure are "wait-free" which means that each operation finishes within a constant number of steps. This makes this data structure suitable for use in hard real-time systems or for communication with interrupt/signal handlers.

CAS无锁循环队列提供了一种机制,可以在没有任何锁的情况下将对象从一个“生产者”线程中继到一个“消费者”线程。该数据结构上的操作是“无等待”的,这意味着每个操作在固定数量的步骤内完成。这使得该数据结构适用于硬实时系统或与中断/信号处理程序通信。

Implementation

#include <iostream>       // std::cout
#include <atomic>         // std::atomic, std::memory_order_relaxed
using namespace std;
template<typename T, size_t Size>
class ringbuffer {
public:
  ringbuffer() : head_(0), tail_(0) {}

  bool push(const T & value)
  {
    size_t head = head_.load(memory_order_relaxed);
    size_t next_head = next(head);
    if (next_head == tail_.load(memory_order_acquire))
      return false;
    ring_[head] = value;
    head_.store(next_head, memory_order_release);
    return true;
  }
  bool pop(T & value)
  {
    size_t tail = tail_.load(memory_order_relaxed);
    if (tail == head_.load(memory_order_acquire))
      return false;
    value = ring_[tail];
    tail_.store(next(tail), memory_order_release);
    return true;
  }
private:
  size_t next(size_t current)
  {
    return (current + 1) % Size;
  }
  T ring_[Size];
  atomic<size_t> head_, tail_;
};

Usage

ringbuffer<int, 32> r;

// try to insert an element
if (r.push(42)) { /* succeeded */ }
else { /* buffer full */ }

// try to retrieve an element
int value;
if (r.pop(value)) { /* succeeded */ }
else { /* buffer empty */ }

Lock-free multi-producer queue- 无锁多生产者队列

abstract

The purpose of the lock-free multi-producer queue is to allow an arbitrary number of producers to enqueue objects which are retrieved and processed in FIFO order by a single consumer.

无锁多生产者队列的目的是允许任意数量的生产者排队对象,这些对象由单个消费者按照FIFO顺序检索和处理。

Implementation

ringbuffer<int, 32> r;

// try to insert an element
if (r.push(42)) { /* succeeded */ }
else { /* buffer full */ }

// try to retrieve an element
int value;
if (r.pop(value)) { /* succeeded */ }
else { /* buffer empty */ }

Lock-free multi-producer queue- 无锁多生产者队列

abstract

The purpose of the lock-free multi-producer queue is to allow an arbitrary number of producers to enqueue objects which are retrieved and processed in FIFO order by a single consumer.

无锁多生产者队列的目的是允许任意数量的生产者排队对象,这些对象由单个消费者按照FIFO顺序检索和处理。

Implementation

template<typename T>
class lockfree_queue {
public:
  struct node {
    T data;
    node * next;
  };
  void push(const T &data)
  {
    node * n = new node;
    n->data = data;
    node * stale_head = head_.load(boost::memory_order_relaxed);
    do {
      n->next = stale_head;
    } while (!head_.compare_exchange_weak(stale_head, n, boost::memory_order_release));
  }

  node * pop_all(void)
  {
    T * last = pop_all_reverse(), * first = 0;
    while(last) {
      T * tmp = last;
      last = last->next;
      tmp->next = first;
      first = tmp;
    }
    return first;
  }

  lockfree_queue() : head_(0) {}

  // alternative interface if ordering is of no importance
  node * pop_all_reverse(void)
  {
    return head_.exchange(0, boost::memory_order_consume);
  }
private:
  boost::atomic<node *> head_;
};

Usage

lockfree_queue<int> q;

// insert elements
q.push(42);
q.push(2);

// pop elements
lockfree_queue<int>::node * x = q.pop_all()
while(x) {
  X * tmp = x;
  x = x->next;
  // process tmp->data, probably delete it afterwards
  delete tmp;
}

异步操作future & async & package_task & promise

std::future

What

std::future期待一个返回,从一个异步调用的角度来说,future更像是执行函数的返回值,C++标准库

使用std::future为一次性事件建模,如果一个事件需要等待特定的一次性事件,那么这线程可以获取一

个future对象来代表这个事件。

在库的头文件中声明了两种future,唯一future(std::future)和共享future(std::shared_future)这

两个是参照std::unique_ptr和std::shared_ptr设立的,前者的实例是仅有的一个指向其关联事件的实例,而后者可以有多个实例指向同一个关联事件,当事件就绪时,所有指向同一事件的std::shared_future实例会变成就绪。

线程可以周期性的在这个future上等待一小段时间,检查future是否已经ready,如果没有,该线程可以

先去做另一个任务,一旦future就绪,future就无法复位(无法再次使用这个future等待这个事

件),所以future代表的是一次性事件

std::future提供了一种访问异步操作结果的机制。从字面意思来理解, 它表示未来,我觉得这个名字非常贴切,因为一个异步操作我们是不可能马上就获取操作结果的,只能在未来某个时候获取,但是我们可以以同步等待的方式来获取 结果,可以通过查询future的状态(future_status)来获取异步操作的结果。future_status有三种状态:

  • deferred:异步操作还没开始
  • ready:异步操作已经完成
  • timeout:异步操作超时

可以通过future_status去查询future的三种状态,future提供了一些函数比如get(),wait(),wait_for()。

一般用get()来获取future所得到的结果,如果异步操作还没有结束,那么会在此等待异步操作的结束,并获取返回的结果。

wait()只是在此等待异步操作的结束,并不能获得返回结果。

wait_for()超时等待返回结果。

How

std::future是一个模板,模板参数就是期待返回的类型,虽然future被用于线程间通信,但其本身却并不提供同步访问,必须通过互斥元或其他同步机制来保护访问。

future使用的时机是当你不需要立刻得到一个结果的时候,你可以开启一个线程帮你去做一项任务,并

期待这个任务的返回,但是std::thread并没有提供这样的机制,这就需要用到std::async和std::future

(都在头文件中声明)

std::async返回一个std::future对象,而不是给你一个确定的值(所以当你不需要立刻使用此值的时候才

需要用到这个机制)。当你需要使用这个值的时候,对future使用get(),线程就会阻塞直到future就

绪,然后返回该值。

Demo

#include <iostream> 
#include <future> 
#include <thread> 
using namespace std; 
int find_result_to_add() {
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 用来测试异步延迟的影 响 
    std::cout << "find_result_to_add" << std::endl; return 1 + 1; 
}
int find_result_to_add2(int a, int b) {
    // std::this_thread::sleep_for(std::chrono::seconds(5)); // 用来测试异步延迟的影 响 
    return a + b; 
}
void do_other_things() { 
    std::cout << "do_other_things" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(5));
}

int main() { 
    std::future<int> result = std::async(launch::async,find_result_to_add); 
    // std::future<decltype (find_result_to_add())> result = std::async(find_result_to_add); 
    // auto result = std::async(find_result_to_add); // 推荐的写法 
    do_other_things();
    std::cout << "result: " << result.get() << std::endl; // 延迟是否有影响? 
    // std::future<decltype (find_result_to_add2(int, int))> result2 = std::async(find_result_to_add2, 10, 20); //错误 
    // std::future<decltype (find_result_to_add2(0, 0))> result2 = std::async(find_result_to_add2, 10, 20); 
    // std::cout << "result2: " << result2.get() << std::endl; // 延迟是否有影响? 
    // std::cout << "main finish" << endl; 
    return 0; 
}

std::async

What

跟thread类似,async允许你通过将额外的参数添加到调用中,来将附加参数传递给函数。如果传入的

函数指针是某个类的成员函数,则还需要将类对象指针传入(直接传入,传入指针,或者是std::ref封

装)。

默认情况下,std::async是否启动一个新线程,或者在等待future时,任务是否同步运行都取决于你给的

参数。这个参数为std::launch类型

std::launch::defered表明该函数会被延迟调用,直到在future上调用get()或者wait()为止

std::launch::async,表明函数会在自己创建的线程上运行

std::launch::any = std::launch::defered | std::launch::async

std::launch::sync = std::launch::defered

默认选项参数被设置为std::launch::any。如果函数被延迟运行可能永远都不会运行。

std::package_task

What

The class template std::packaged_task wraps any Callable target (function, lambda expression,

bind expression, or another function object) so that it can be invoked asynchronously. Its return

value or exception thrown is stored in a shared state which can be accessed through std::future

objects.

可以通过std::packaged_task对象获取任务相关联的feature,调用get_future()方法可以获得

std::packaged_task对象绑定的函数的返回值类型的future。std::packaged_task的模板参数是函数签

PS:(例如int add(int a, intb)的函数签名就是int(int, int)

How

  1. 先创建一个package_task 的对象
  2. 通过get_future 获取future
  3. 执行task
  4. 主线程运行other job
  5. future get返回值

Demo

#include<iostream>
#include<future>
#include<thread>

int TripleX_func(int x){
	x = x*3;
	std::cout<<"3X thread id:"<<std::endl;
	std::cout<<std::this_thread::get_id()<<std::endl;
	std::this_thread::sleep_for(std::chrono::seconds(5));
	return x; 
} 
int main(){
	// 将函数(某种操作后的值)打包起来
	// std::packaged_task<函数返回类型(参数类型)> 变量名(函数名)
	std::packaged_task<int(int)> pt{TripleX_func};
	//并将结果返回给future,类型是int 
	std::future<int> fu = pt.get_future();
	//future提供了一些函数比如get(),wait(),wait_for()。
	//一般用get()来获取future所得到的结果
	//如果异步操作还没有结束,那么会在此等待异步操作的结束,并获取返回的结果。
	std::thread t(std::ref(pt), 5);
	std::cout<<fu.get()<<std::endl;
	//输出3X线程和main线程的id,可以发现是两个不同的ID。 
	std::cout<<"main thread id:"<<std::endl;
	std::cout<<std::this_thread::get_id()<<std::endl;
	t.join();
	return 0;
	
}

std::promise

What

std::promise提供了一种设置值的方式,它可以在这之后通过相关联的std::future对象进行读取。换种

说法,之前已经说过std::future可以读取一个异步函数的返回值了,那么这个std::promise就提供一种

方式手动让future就绪

How

线程在创建promise的同时会获得一个future,然后将promise传递给设置他的线程,当前线程则持有

future,以便随时检查是否可以取值。

future的表现为期望,当前线程持有future时,期望从future获取到想要的结果和返回,可以把future当

做异步函数的返回值。而

promise是一个承诺,当线程创建了promise对象后,这个promise对象向线程承诺他必定会被人设置一

个值,和promise相关联的future就是获取其返回的手段。

Demo

#include <future> 
#include <string> 
#include <thread> 
#include <iostream> 
using namespace std; 
void print(std::promise<std::string>& p) { p.set_value("There is the result whitch you want."); }
void do_some_other_things() { std::cout << "Hello World" << std::endl; }
int main() { 
    std::promise<std::string> promise; 
    std::future<std::string> result = promise.get_future(); 
    std::thread t(print, std::ref(promise)); 
    do_some_other_things(); 
    std::cout << result.get() << std::endl; 
    t.join();
    return 0;
}

单次操作 - call_once

What

std:call_once是C++11引入的新特性,如需使用,只需要#include <mutex>即可,简单来说std:call_once的作用,确保函数或代码片段在多线程环境下,只需要执行一次,常用的场景如Init()操作或一些系统参数的获取等。

How

std::call_once用法比较简单,配合std::once_flag即可实现

Demo

#include <iostream>
#include <thread>
#include <mutex>

std::once_flag flag;

void Initialize()
{
	std::cout << "Run into Initialize.." << std::endl;
}

void Init()
{
	std::call_once(flag, Initialize);
}

int main()
{
	std::thread t1(Init);
	std::thread t2(Init);
	std::thread t3(Init);
	std::thread t4(Init);
	t1.join();
	t2.join();
	t3.join();
	t4.join();
}

std::function & std::bind & lambda表达式

std::function

Abstract

满足以下条件之一就可称为可调用对象:

  • 是一个函数指针
  • 是一个具有operator()成员函数的类对象(传说中的仿函数),lambda表达式
  • 是一个可被转换为函数指针的类对象
  • 是一个类成员(函数)指针
  • bind表达式或其它函数对象

std::function就是上面这种可调用对象的封装器,可以把std::function看做一个函数对象,用于表示函数这个抽象概念。std::function的实例可以存储、复制和调用任何可调用对象,存储的可调用对象称为std::function的目标,若std::function不含目标,则称它为空,调用空的std::function的目标会抛出std::bad_function_call异常。

How

std::function 的模板参数是函数签名

需要引入头文件 :<functional>

Demo

#include <functional>
#include <iostream>

struct Foo {
    Foo(int num) : num_(num) {}
    void print_add(int i) const { std::cout << num_ + i << '\n'; }
    int num_;
};

void print_num(int i) { std::cout << i << '\n'; }

struct PrintNum {
    void operator()(int i) const { std::cout << i << '\n'; }
};

int main() {
    // 存储自由函数
    std::function<void(int)> f_display = print_num;
    f_display(-9);

    // 存储 lambda
    std::function<void()> f_display_42 = []() { print_num(42); };
    f_display_42();

    // 存储到 std::bind 调用的结果
    std::function<void()> f_display_31337 = std::bind(print_num, 31337);
    f_display_31337();

    // 存储到成员函数的调用
    std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
    const Foo foo(314159);
    f_add_display(foo, 1);
    f_add_display(314159, 1);

    // 存储到数据成员访问器的调用
    std::function<int(Foo const&)> f_num = &Foo::num_;
    std::cout << "num_: " << f_num(foo) << '\n';

    // 存储到成员函数及对象的调用
    using std::placeholders::_1;
    std::function<void(int)> f_add_display2 = std::bind(&Foo::print_add, foo, _1);
    f_add_display2(2);

    // 存储到成员函数和对象指针的调用
    std::function<void(int)> f_add_display3 = std::bind(&Foo::print_add, &foo, _1);
    f_add_display3(3);

    // 存储到函数对象的调用
    std::function<void(int)> f_display_obj = PrintNum();
    f_display_obj(18);
}

std::bind

Abstract

使用std::bind可以将可调用对象和参数一起绑定,绑定后的结果使用std::function进行保存,并延迟调用到任何我们需要的时候。

std::bind通常有两大作用:

  • 将可调用对象与参数一起绑定为另一个std::function供调用
  • 将n元可调用对象转成m(m < n)元可调用对象,绑定一部分参数,这里需要使用std::placeholders

How

using namespace std::placeholders; // 针对 _1, _2, _3...

auto a = bind(f,_1,_2,_3) // 这里的_1,_2指的是后来实际传递的值得位置

 auto f2 = std::bind(f, _3, std::bind(g, _3), _3, 4, 5);
 f2(10, 11, 12);  // 进行到 f(12, g(12), 12, 4, 5); 的调用

Demo

#include <functional>
#include <iostream>
#include <memory>

void f(int n1, int n2, int n3, const int& n4, int n5) {
    std::cout << n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << std::endl;
}

int g(int n1) { return n1; }

struct Foo {
    void print_sum(int n1, int n2) { std::cout << n1 + n2 << std::endl; }
    int data = 10;
};

int main() {
    using namespace std::placeholders;  // 针对 _1, _2, _3...

    // 演示参数重排序和按引用传递
    int n = 7;
    // ( _1 与 _2 来自 std::placeholders ,并表示将来会传递给 f1 的参数)
    auto f1 = std::bind(f, _2, 42, _1, std::cref(n), n);
    n = 10;
    f1(1, 2, 1001);  // 1 为 _1 所绑定, 2 为 _2 所绑定,不使用 1001
                     // 进行到 f(2, 42, 1, n, 7) 的调用

    // 嵌套 bind 子表达式共享占位符
    auto f2 = std::bind(f, _3, std::bind(g, _3), _3, 4, 5);
    f2(10, 11, 12);  // 进行到 f(12, g(12), 12, 4, 5); 的调用

    // 绑定指向成员函数指针
    Foo foo;
    auto f3 = std::bind(&Foo::print_sum, &foo, 95, _1);
    f3(5);

    // 绑定指向数据成员指针
    auto f4 = std::bind(&Foo::data, _1);
    std::cout << f4(foo) << std::endl;

    // 智能指针亦能用于调用被引用对象的成员
    std::cout << f4(std::make_shared<Foo>(foo)) << std::endl;
}

lambda表达式

Abstract

它定义了一个匿名函数,可以捕获一定范围的变量在函数内部使用

How

auto func = [capture] (params) opt -> ret { func_body; };

其中func是可以当作lambda表达式的名字,作为一个函数使用,capture是捕获列表,params是参数表,opt是函数选项(mutable之类), ret是返回值类型,func_body是函数体。

auto func1 = [](int a) -> int { return a + 1; };
auto func2 = [](int a) { return a + 2; };
cout << func1(1) << " " << func2(2) << endl;

lambda表达式允许捕获一定范围内的变量:

  • []不捕获任何变量
  • [&]引用捕获,捕获外部作用域所有变量,在函数体内当作引用使用
  • [=]值捕获,捕获外部作用域所有变量,在函数内内有个副本使用
  • [=, &a]值捕获外部作用域所有变量,按引用捕获a变量
  • [a]只值捕获a变量,不捕获其它变量
  • [this]捕获当前类中的this指针

Demo

struct A {
    int a;
    int b;
};

int main() {
    vector<A> vec;
    std::sort(vec.begin(), vec.end(), [](const A &left, const A &right) { return left.a < right.a; });
}

模板

右尖括号

Abstract

C++11之前是不允许两个右尖括号出现的,会被认为是右移操作符,所以需要中间加个空格进行分割,避免发生编译错误。

模板的别名

Abstract

C++11引入了using,可以轻松的定义别名,而不是使用繁琐的typedef。

Demo

typedef std::vector<std::vector<int>> vvi; // before c++11
using vvi = std::vector<std::vector<int>>; // c++11

template<class T>
struct Alloc { };
template<class T>
using Vec = vector<T, Alloc<T>>; // 类型标识为 vector<T, Alloc<T>>
Vec<int> v; // Vec<int> 同 vector<int, Alloc<int>>

函数模板的默认模板参数

Abstract

C++11之前只有类模板支持默认模板参数,函数模板是不支持默认模板参数的,C++11后都支持。

类模板的默认模板参数必须从右往左定义,而函数模板则没有这个限制。

对于函数模板,参数的填充顺序是从左到右的,而通常情况下c/c++默认入栈方式:__cdel,也就是以右到左将参数压入堆栈

Demo

template <typename T, typename U=int>
class A {
    T value;  
};

template <typename T=int, typename U> // error
class A {
    T value;  
};

emplate <typename R, typename U=int>
R func1(U val) {
    return val;
}

template <typename R=int, typename U>
R func2(U val) {
    return val;
}

int main() {
    cout << func1<int, double>(99.9) << endl; // 99
    cout << func1<double, double>(99.9) << endl; // 99.9
    cout << func1<double>(99.9) << endl; // 99.9
    cout << func1<int>(99.9) << endl; // 99
    cout << func2<int, double>(99.9) << endl; // 99
    cout << func1<double, double>(99.9) << endl; // 99.9
    cout << func2<double>(99.9) << endl; // 99.9
    cout << func2<int>(99.9) << endl; // 99
    return 0;
}

变长参数模板

Abstract

在C++11之后,加入了新的表示方 法,允许任意个数、任意类别的模板参数,同时也不需要在定义时将参数的个数固定。

参数包

template<typename... Args> 
void printf(const std::string &str, Args... args);

其中,Argsargs 分别代表模板与函数的变长参数集合, 称之为参数包 (parameter pack)。参数包必须要和运算符"…"搭配使用。

sizeof 参数包

template<typename... Ts>
void magic(Ts... args) 
{
	std::cout << sizeof...(args) << std::endl;
}

How

递归解包

template <typename T>
void fun(const T& t){
	cout << t << '\n';
}
 
template <typename T, typename ... Args>
void fun(const T& t, Args ... args){
	cout << t << ',';
	fun(args...);//递归解决,利用模板推导机制,每次取出第一个,缩短参数包的大小。
}

fold expression

template <typename ... T>
void DummyWrapper(T... t){}
 
template <class T>
T unpacker(const T& t){
	cout<<','<<t;
	return t;
}
template <typename T, typename... Args>
void write_line(const T& t, const Args& ... data){
    cout<<','<<t;
    (unpacker(data), ...);//展开成(((unpacker(data_1), unpacker(data_2)), unpacker(data_3), ... ),unpacker(data_n)
    cout<<'\n';
 
    //如果不需要输出间隔符,后两行还可以使用下面的简单形式
    //(cout<< ... <<args)<<'\n';
}

外置递归

template <typename T>
void _write(const T& t){
	cout << t << '\n';
}
 
template <typename T, typename ... Args>
void _write(const T& t, Args ... args){
	cout << t << ',';
	_write(args...);//递归解决,利用模板推导机制,每次取出第一个,缩短参数包的大小。
}
 
template <typename T, typename... Args>
inline void write_line(const T& t, const Args& ... data){
	_write(t, data...);
}

Demo

template <typename T>
void unpack(T&& t)
{
    std::cout << std::forward<T>(t) << ' ';
}

template <typename ... Args>
void debugLogImpl(Args&& ... args)
{
    int dummy[] = {0 , (unpack(std::forward<Args>(args)), 0)...};
    MOOS_UNUSE(dummy);
    std::cout << '\n';
}


template <typename ... Args>
void debugLog(Args&& ... args)
{
    debugLogImpl(std::forward<Args>(args)...);
}

右值与完美转发

左右值的理解

顾名思义:可以放到等号左边的东西叫左值,不可以放到等号左边的东西就叫右值。

从程序的角度讲:可以取地址并且有名字的东西就是左值,不能取地址的没有名字的东西就是右值。

int a = b + c; 

a是左值,a有变量名,也可以取地址,可以放到等号左边, 表达式b+c的返回值是右值,没有名字且不能取地址,&(b+c)不能通过编译,而且也不能放到等号左边。

一般左值

  • 函数名和变量名
  • 返回左值引用的函数调用
  • 前置自增自减表达式++i、--i
  • 由赋值表达式或赋值运算符连接的表达式(a=b, a += b等)
  • 解引用表达式*p
  • 字符串字面值"abcd"

一般右值

纯右值和将亡值都属于右值。

纯右值

运算表达式产生的临时变量、不和对象关联的原始字面量、非引用返回的临时变量、lambda表达式等都是纯右值。

举例:

  • 除字符串字面值外的字面值
  • 返回非引用类型的函数调用
  • 后置自增自减表达式i++、i--
  • 算术表达式(a+b, a*b, a&&b, a==b等)
  • 取地址表达式等(&a)

将亡值

将亡值是指C++11新增的和右值引用相关的表达式,通常指将要被移动的对象、T&&函数的返回值、std::move函数的返回值、转换为T&&类型转换函数的返回值,将亡值可以理解为即将要销毁的值,通过“盗取”其它变量内存空间方式获取的值,在确保其它变量不再被使用或者即将被销毁时,可以避免内存空间的释放和分配,延长变量值的生命周期,常用来完成移动构造或者移动赋值的特殊任务。

右值引用

int a = 4;
int &&b = a; // error, a是左值
int &&c = std::move(a); // ok

如果使用右值引用,那表达式等号右边的值需要时右值,可以使用std::move函数强制把左值转换为右值。

移动构造函数

如果不使用移动构造函数,拷贝本身代表着复制,代表着内存和时间的开销,但是如果我们不需要复制,只是希望这个内存换一个地方继续工作,那就可以使用转移 std::move

class A {
public:
    A(int size) : size_(size) {
        data_ = new int[size];
    }
    A(){}
    A(const A& a) {
        size_ = a.size_;
        data_ = new int[size_];
        cout << "copy " << endl;
    }
    A(A&& a) {
        this->data_ = a.data_;
        a.data_ = nullptr;
        cout << "move " << endl;
    }
    ~A() {
        if (data_ != nullptr) {
            delete[] data_;
        }
    }
    int *data_;
    int size_;
};
int main() {
    A a(10);
    A b = a;
    A c = std::move(a); // 调用移动构造函数
    std::vector<string> vecs;
    std::vector<string> vecm = std::move(vecs); // 免去很多拷贝
    return 0;
}

完美转发

abstract

完美转发指可以写一个接受任意实参的函数模板,并转发到其它函数,目标函数会收到与转发函数完全相同的实参,转发函数实参是左值那目标函数实参也是左值,转发函数实参是右值那目标函数实参也是右值。那如何实现完美转发呢,答案是使用std::forward()。

Demo

void PrintV(int &t) {
    cout << "lvalue" << endl;
}

void PrintV(int &&t) {
    cout << "rvalue" << endl;
}

template<typename T>
void Test(T &&t) {
    PrintV(t);
    PrintV(std::forward<T>(t));

    PrintV(std::move(t));
}

int main() {
    Test(1); // lvalue rvalue rvalue
    int a = 1;
    Test(a); // lvalue lvalue rvalue
    Test(std::forward<int>(a)); // lvalue rvalue rvalue
    Test(std::forward<int&>(a)); // lvalue lvalue rvalue
    Test(std::forward<int&&>(a)); // lvalue rvalue rvalue
    return 0;
}

C++ 返回值优化RVO

Abstract

返回值优化(RVO)是一种C++编译优化技术,当函数需要返回一个对象实例时候,就会创建一个临时对象并通过复制构造函数将目标对象复制到临时对象,这里有复制构造函数和析构函数会被多余的调用到,有代价,而通过返回值优化,C++标准允许省略调用这些复制构造函数。

How

  • return的值类型与函数的返回值类型相同
  • return的是一个局部对象

Demo

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return tmp;
}

std::vector<int> rval_ref = return_vector();
//这段代码会触发RVO,不拷贝也不移动,不生成临时对象。

智能指针

在C++中没有垃圾回收机制,必须自己释放分配的内存,否则就会造成内存泄露。解决这个问题最有效的方法是使用智能指针(smart pointer)。智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。

C++11中提供了三种智能指针,使用这些智能指针时需要引用头文件<memory>

std::shared_ptr:共享的智能指针

std::unique_ptr:独占的智能指针

std::weak_ptr:弱引用的智能指针,它不共享指针,不能操作资源,是用来监视shared_ptr的。

共享的智能指针shared_ptr

Abstract

shared_ptr使用了引用计数,每一个shared_ptr的拷贝都指向相同的内存,每次拷贝都会触发引用计数+1,每次生命周期结束析构的时候引用计数-1,在最后一个shared_ptr析构的时候,内存才会释放。

How

1. shared_ptr的初始化

共享智能指针是指多个智能指针可以同时管理同一块有效的内存,共享智能指针shared_ptr 是一个模板类,如果要进行初始化有三种方式:

通过构造函数;

std::make_shared辅助函数;

reset方法。

共享智能指针对象初始化完毕之后就指向了要管理的那块堆内存,如果想要查看当前有多少个智能指针同时管理着这块内存可以使用共享智能指针提供的一个成员函数use_count

2.获取原始指针

对应基础数据类型来说,通过操作智能指针和操作智能指针管理的内存效果是一样的,可以直接完成数据的读写。但是如果共享智能指针管理的是一个对象,那么就需要取出原始内存的地址再操作,可以调用共享智能指针类提供的get()方法得到原始地址

3. 指定删除器

当智能指针管理的内存对应的引用计数变为0的时候,这块内存就会被智能指针析构掉了。另外,我们在初始化智能指针的时候也可以自己指定删除动作,这个删除操作对应的函数被称之为删除器,这个删除器函数本质是一个回调函数,我们只需要进行实现,其调用是由智能指针完成的。

4. 注意事项

  • 不要用一个裸指针初始化多个shared_ptr,会出现double_free导致程序崩溃
  • 通过shared_from_this()返回this指针,不要把this指针作为shared_ptr返回出来,因为this指针本质就是裸指针,通过this返回可能 会导致重复析构,不能把this指针交给智能指针管理。
  • 尽量使用make_shared,少用new。
  • 不要delete get()返回来的裸指针。
  • 不是new出来的空间要自定义删除器。
  • 要避免循环引用,循环引用导致内存永远不会被释放,造成内存泄漏。

Demo

使用

#include <iostream>
using namespace std;
#include <string>
#include <memory>
 
class Test
{
public:
    Test() : m_num(0)
    {
        cout << "construct Test..." << endl;
    }
 
    Test(int x) : m_num(0)
    {
        cout << "construct Test, x = " << x << endl;
    }
 
    Test(string str) : m_num(0)
    {
        cout << "construct Test, str = " << str << endl;
    }
 
    ~Test()
    {
        cout << "destruct Test..." << endl;
    }
 
    void setValue(int v)
    {
        this->m_num = v;
    }
 
    void print()
    {
        cout << "m_num: " << this->m_num << endl;
    }
 
private:
    int m_num;
};
 
int main()
{
    /*--------------------------  一,初始化智能指针shared_ptr  ------------------------------*/
    //1.通过构造函数初始化
    shared_ptr<int> ptr1(new int(3));
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
 
    //2.通过移动和拷贝构造函数初始化
    shared_ptr<int> ptr2 = move(ptr1);
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
    cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
 
    shared_ptr<int> ptr3 = ptr2;
    cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
    cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
 
    //3.通过 std::make_shared初始化
    shared_ptr<int> ptr4 = make_shared<int>(8);
    shared_ptr<Test> ptr5 = make_shared<Test>(7);
    shared_ptr<Test> ptr6 = make_shared<Test>("GOOD LUCKLY!");
 
    //4.通过reset初始化
    ptr6.reset(); //重置ptr6, ptr6的引用基数为0
    cout << "ptr6管理的内存引用计数: " << ptr6.use_count() << endl;
 
    ptr5.reset(new Test("hello"));
    cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;
 
    cout << endl;
    cout << endl;
 
    /*--------------------------  二,共享智能指针shared_ptr的使用  ------------------------------*/
    //1.方法一
    Test* t = ptr5.get();
    t->setValue(1000);
    t->print();
 
    //2.方法二
    ptr5->setValue(7777);
    ptr5->print();
 
    printf("\n\n");
    /*------------------------------------  三,指定删除器  -----------------------------------*/
     //1.简单举例
    shared_ptr<Test> ppp(new Test(100), [](Test* t) {
        //释放内存
        cout << "Test对象的内存被释放了......." << endl;
        delete t;
        });
    printf("----------------------------------------------------------------------\n");
 
    2.如果是数组类型的地址,就需要自己写指定删除器,否则内存无法全部释放
    //shared_ptr<Test> p1(new Test[5], [](Test* t) {
    //    delete[]t;
    //    });
 
    //3.也可以使用c++给我们提供的 默认删除器函数(函数模板)
    shared_ptr<Test> p2(new Test[3], default_delete<Test[]>());
 
    //4.c++11以后可以这样写 也可以自动释放内存
    shared_ptr<Test[]> p3(new Test[3]);
 
    return 0;
}

数组模板

#include <iostream>
#include <memory>
#include <string>
using namespace std;
 
//有了这个函数模板,我们就不用自己去释放数组类型的地址了
template <typename T>
shared_ptr<T> make_share_array(size_t size) 
{
	//返回匿名对象
	return shared_ptr<T>(new T[size], default_delete<T[]>());
}
 
int main()
{
	shared_ptr<int> ptr1 = make_share_array<int>(10);
	cout << ptr1.use_count() << endl;
 
	shared_ptr<string> ptr2 = make_share_array<string>(7);
	cout << ptr2.use_count() << endl;
		
	return 0;
}

独占的智能指针unique_ptr

Abstract

std::unique_ptr是一个独占型的智能指针,它不允许其它智能指针共享其内部指针,也不允许unique_ptr的拷贝和赋值。使用方法和shared_ptr类似,区别是不可以拷贝。

从哲学的角度讲,unique 与share 实现内存安全的思路完全相反,一个是通过不允许多份内存指针,一个是对内存指针的使用进行计数。

How

1. 初始化

std::unique_ptr是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,可以通过它的构造函数初始化一个独占智能指针对象,但是不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。

2. 删除器

unique_ptr指定删除器和shared_ptr指定删除器是有区别的,unique_ptr指定删除器的时候需要确定删除器的类型,所以不能像shared_ptr那样直接指定删除器

Demo

using namespace std;

struct A {
    ~A() {
        cout << "A delete" << endl;
    }
    void Print() {
        cout << "A" << endl;
    }
};


int main() {
    auto ptr = std::unique_ptr<A>(new A);
    auto tptr = std::make_unique<A>(); // error, c++11还不行,需要c++14
    std::unique_ptr<A> tem = ptr; // error, unique_ptr不允许移动
    ptr->Print();
    return 0;
}

弱引用的智能指针weak_ptr

Abstract

weak_ptr的思路就是观察者模式,是用来监视shared_ptr的生命周期,std::weak_ptr没有重载操作符*和->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视shared_ptr中管理的资源是否存在,可以用来返回this指针和解决循环引用问题。

How

1. 初始化

#include <iostream>
#include <memory>
using namespace std;
 
int main() 
{
    shared_ptr<int> sp(new int);
 
    weak_ptr<int> wp1;
    weak_ptr<int> wp2(wp1);
    weak_ptr<int> wp3(sp);
    weak_ptr<int> wp4;
    wp4 = sp;
    weak_ptr<int> wp5;
    wp5 = wp3;
    
    return 0;
}

weak_ptr<int> wp1;构造了一个空weak_ptr对象

weak_ptr<int> wp2(wp1);通过一个空weak_ptr对象构造了另一个空weak_ptr对象

weak_ptr<int> wp3(sp);通过一个shared_ptr对象构造了一个可用的weak_ptr实例对象

wp4 = sp;通过一个shared_ptr对象构造了一个可用的weak_ptr实例对象(这是一个隐式类型转换)

wp5 = wp3;通过一个weak_ptr对象构造了一个可用的weak_ptr实例对象

通过调用std::weak_ptr类提供的use_count()方法可以获得当前所观测资源的引用计数

2. 常用函数

通过调用std::weak_ptr类提供的expired()方法来判断观测的资源是否已经被释放

通过调用std::weak_ptr类提供的lock()方法来获取管理所监测资源的shared_ptr对象

通过调用std::weak_ptr类提供的reset()方法来清空对象,使其不监测任何资源

3. 解决了share_ptr 的哪些问题

  1. 返回管理this的shared_ptr
    1. 上面介绍的shared_from_this()其实就是通过weak_ptr返回的this指针
  1. 解决循环引用问题

Demo

struct A;
struct B;

struct A {
    std::shared_ptr<B> bptr;
    ~A() {
        cout << "A delete" << endl;
    }
    void Print() {
        cout << "A" << endl;
    }
};

struct B {
    std::weak_ptr<A> aptr; // 这里改成weak_ptr
    ~B() {
        cout << "B delete" << endl;
    }
    void PrintA() {
        if (!aptr.expired()) { // 监视shared_ptr的生命周期
            auto ptr = aptr.lock();
            ptr->Print();
        }
    }
};

int main() {
    auto aaptr = std::make_shared<A>();
    auto bbptr = std::make_shared<B>();
    aaptr->bptr = bbptr;
    bbptr->aptr = aaptr;
    bbptr->PrintA();
    return 0;
}
输出:
A
A delete
B delete

shared_ptr 源码分析

gcc版本源码分析图

分析

  1. shared_ptr类几乎什么都没有做,它是继承了__shared_ptr
  2. __shared_ptr内部有一个类型为__shared_count类型的成员_M_refcount
  3. __shared_count内部有类型为_Sp_counted_base*的_M_pi的成员, _Sp_counted_base才是整个shared_ptr功能的核心,通过_Sp_counted_base控制引用计数来管理托管的内存
  4. Sp_counted_base内部不持有托管内存的指针,__shared_count内部的成员其实是一个继承自_Sp_counted_base的_Sp_counted_ptr类型
  5. _Sp_counted_ptr类型内部持有托管内存的指针_M_ptr
  6. , _M_use_count表示托管对象的引用计数,控制托管对象什么时候析构和释放
  7. M_weak_count表示管理对象的引用计数,管理对象也是一个内存指针,这块指针是初始化第一个shared_ptr时new出来的,到最后也需要delete

_M_use_count是如何加减的

template <typename _Yp>
__shared_ptr(const __shared_ptr<_Yp, _Lp>& __r,
                element_type* __p) noexcept
    : _M_ptr(__p), _M_refcount(__r._M_refcount)  // never throws
{
}

__shared_count(const __shared_count& __r) noexcept : _M_pi(__r._M_pi)
{
    if (_M_pi != 0) _M_pi->_M_add_ref_copy();
}

template <>
inline void _Sp_counted_base<_S_single>::_M_add_ref_copy()
{
    ++_M_use_count;
}

template <typename _Yp>
_Assignable<const shared_ptr<_Yp>&> operator=(
    const shared_ptr<_Yp>& __r) noexcept
{
    this->__shared_ptr<_Tp>::operator=(__r);
    return *this;
}

template <typename _Yp>
_Assignable<_Yp> operator=(const __shared_ptr<_Yp, _Lp>& __r) noexcept
{
    _M_ptr = __r._M_ptr;
    _M_refcount = __r._M_refcount;  // __shared_count::op= doesn't throw
    return *this;
}

__shared_count& operator=(const __shared_count& __r) noexcept
{
    _Sp_counted_base<_Lp>* __tmp = __r._M_pi;
    if (__tmp != _M_pi) {
        if (__tmp != 0) __tmp->_M_add_ref_copy();
        if (_M_pi != 0) _M_pi->_M_release();
        _M_pi = __tmp;
    }
    return *this;
}

~__shared_count() noexcept
{
    if (_M_pi != nullptr) _M_pi->_M_release();
}

template <>
inline void _Sp_counted_base<_S_single>::_M_release() noexcept
{
    if (--_M_use_count == 0) {
        _M_dispose();
        if (--_M_weak_count == 0) _M_destroy();
    }
}

virtual void _M_dispose() noexcept { delete _M_ptr; }

内存对齐alignas、alignof关键字

为什么要做内存对齐

Although memory is measured in bytes, most processors do not access memory in blocks of bytes. It accesses memory in units of double, four, eight,16, or even 32 bytes, which we refer to as memory access granularity.

Now consider a 4-byte access granularity processor taking variables of type INT (32-bit system), which can only start reading data from memory with address multiples of 4.

Without memory alignment, data can be stored anywhere. Now an int variable is stored in a sequence of four bytes starting at address 1. When the processor fetches the data, The first 4-byte block is read from address 0, eliminating unwanted bytes (address 0), then the next 4-byte block is read from address 4, also eliminating unwanted data (address 5,6,7), and finally the remaining two pieces of data are merged into the register. It takes a lot of work.

Simply put, memory alignment can improve the speed at which the CPU reads data and reduce errors in accessing data (some cpus must have memory alignment, otherwise pointer access errors).

尽管内存是以字节为单位,但是大部分处理器并不是按字节块来存取内存的.它一般会以双字节,四字节,8字节,16字节甚至32字节为单位来存取内存,我们将上述这些存取单位称为内存存取粒度.

现在考虑4字节存取粒度的处理器取int类型变量(32位系统),该处理器只能从地址为4的倍数的内存开始读取数据。

假如没有内存对齐机制,数据可以任意存放,现在一个int变量存放在从地址1开始的连续四个字节地址中,该处理器去取数据时,要先从0地址开始读取第一个4字节块,剔除不想要的字节(0地址),然后从地址4开始读取下一个4字节块,同样剔除不要的数据(5,6,7地址),最后留下的两块数据合并放入寄存器.这需要做很多工作.

简单的说内存对齐能够提高 cpu 读取数据的速度,减少 cpu 访问数据的出错性(有些 cpu 必须内存对齐,否则指针访问会出错)。

alignof和 std::alignment_of

Abstract

alignof用来获取内存对齐大小,alignof只能返回一个size_t而std::alignment_of继承自std::integral_constant,拥有value_type,type,value成员

Demo

  A a;
  cout << alignof(a) << endl;

  cout << std::alignment_of<A>::value << endl;   >>>> 1
  cout << std::alignment_of<B>::value << endl;   >>>> 2

alignas

Abstract

alignas可在定义变量时,改变当前变量的对齐值

Demo

_declspec(align(16)) class TEST {
    double a;
    double b;
    alignas(32) char c;
};
int main() {
    std::cout << alignof(TEST);//output: 32
    system("pause");
}

std::aligned_storage

Abstract

std::aligned_storage可以看成一个内存对齐的缓冲区

  template<std::size_t Len, std::size_t Align = /*default-alignment*/>
 
  struct aligned_storage;

max_align_t 和 std::align

Abstract

std::max_align_t用来返回当前平台的最大默认内存对齐类型,对于malloc返回的内存,其对齐和max_align_t类型的对齐大小应当是一致的

std::align用来在一大块内存中获取一个符合指定内存要求的地址

Demo

std::cout << alignof(std::max_align_t) << std::endl;

char buffer[] = "......";
void *ptr = buffer;
std::size_t space = sizeof(buffer) - 1;
std::align(alignof(int),sizeof(char),pt,space);

Enum Class

Abstract

c++11新增有作用域的枚举类型

不带作用域的枚举代码:

enum AColor {
    kRed,
    kGreen,
    kBlue
};

enum BColor {
    kWhite,
    kBlack,
    kYellow
};

int main() {
    if (kRed == kWhite) {
        cout << "red == white" << endl;
    }
    return 0;
}

不带作用域的枚举类型可以自动转换成整形,且不同的枚举可以相互比较,代码中的红色居然可以和白色比较,这都是潜在的难以调试的bug,而这种完全可以通过有作用域的枚举来规避。

有作用域的枚举代码:

enum class AColor {
    kRed,
    kGreen,
    kBlue
};

enum class BColor {
    kWhite,
    kBlack,
    kYellow
};

int main() {
    if (AColor::kRed == BColor::kWhite) { // 编译失败
        cout << "red == white" << endl;
    }
    return 0;
}

使用带有作用域的枚举类型后,对不同的枚举进行比较会导致编译失败,消除潜在bug,同时带作用域的枚举类型可以选择底层类型,默认是int,可以改成char等别的类型。

enum class AColor : char {
    kRed,
    kGreen,
    kBlue
};

chrono

Abstract

c++11关于时间引入了chrono库,源于boost,功能强大,chrono主要有三个点:

  • duration
  • time_point
  • clocks

duration

Abstract

duration表示一段时间间隔

template<class Rep, class Period = std::ratio<1>> 
 class duration

Rep是一个数值类型,表示时钟个数,Period表示每个时钟周期的秒数。Period的默认模板参数是std::ratio

template<std::intmax_t Num, std::intmax_t Denom = 1> 
class ratio

Num代表分子,Denom代表分母。ratio<1>代表一个时钟周期是一秒,ratio<60>代表了一分钟,ratio<60*60>代表一个小时,ratio<1, 1000>代表一毫秒,ratio<1, 1000000>代表一微秒,ratio<1, 1000000000>代表一纳秒。

How

typedef duration <Rep, ratio<3600,1>> hours; 
typedef duration <Rep, ratio<60,1>> minutes;
typedef duration <Rep, ratio<1,1>> seconds;
typedef duration <Rep, ratio<1,1000>> milliseconds;          //毫秒
typedef duration <Rep, ratio<1,1000000>> microseconds;       //微秒
typedef duration <Rep, ratio<1,1000000000>> nanoseconds;     //纳秒

Demo

#include <iostream>
#include <chrono>
#include <thread>
using namespace std;
int main()
{
    this_thread::sleep_for(chrono::seconds(3));        //休眠3秒
    this_thread::sleep_for(chrono::milliseconds(100)); //休眠100毫秒
    cout << "Hello" << endl;
    getchar();
    return 0;
}

time_point

Abstract

描述的是一个具体的时间点

原型是:

template<class _Clock, class _Duration = typename _Clock::duration>
class time_point

这里的duration 就是上面提到的,Clock之后会介绍。

How

 time_point<system_clock, hours> h = time_point_cast<hours>(system_clock::now());

Demo

#include <iostream>
#include <chrono>
using namespace std;
 
int main()
{
    using namespace std::chrono;
    time_point<system_clock, hours> h = time_point_cast<hours>(system_clock::now());
    cout << h.time_since_epoch().count() << " hours since epoch" << endl;
    getchar();
    return 0;
}

Clock

Abstract

clocks表示当前的系统时钟,内部有time_point, duration, Rep, Period等信息。

clocks包含三种时钟:

steady_clock 是单调的时钟,相当于教练手中的秒表;只会增长,适合用于记录程序耗时;

system_clock 是系统的时钟;因为系统的时钟可以修改;甚至可以网络对时; 所以用系统时间计算时间差可能不准。

high_resolution_clock 是当前系统能够提供的最高精度的时钟;它也是不可以修改的。相当于 steady_clock 的高精度版本。

How

steady_clock

稳定的时间间隔,表示相对时间,相对于系统开机启动的时间,无论系统时间如何被更改,后一次调用now()肯定比前一次调用now()的数值大,可用于计时。

相当于教练手中的秒表;只会增长,适合用于记录程序耗时.

system_clock

表示当前的系统时钟,可以用于获取当前时间

high_resolution_clock

high_resolution_clock表示系统可用的最高精度的时钟,实际上就是system_clock或者steady_clock其中一种的定义,官方没有说明具体是哪个,不同系统可能不一样,我之前看gcc chrono源码中high_resolution_clock是steady_clock的typedef。

Demo

// copied from http://www.informit.com/articles/article.aspx?p=1881386&seqNum=2;
 
// Author: Nicolai M. Josuttis
 
 
#include <chrono>
 
#include <iostream>
 
#include <iomanip>
 
 
template <typename C>
 
void printClockData ()
 
{
 
using namespace std;
 
 
cout << "- precision: ";
 
// if time unit is less or equal one millisecond
 
typedef typename C::period P;// type of time unit
 
if (ratio_less_equal<P,milli>::value) {
 
// convert to and print as milliseconds
 
typedef typename ratio_multiply<P,kilo>::type TT;
 
cout << fixed << double(TT::num)/TT::den
 
<< " milliseconds" << endl;
 
}
 
else {
 
// print as seconds
 
cout << fixed << double(P::num)/P::den << " seconds" << endl;
 
}
 
cout << "- is_steady: " << boolalpha << C::is_steady << endl;
 
}
 
 
int main()
 
{
 
std::cout << "system_clock: " << std::endl;
 
printClockData<std::chrono::system_clock>();
 
std::cout << "\nhigh_resolution_clock: " << std::endl;
 
printClockData<std::chrono::high_resolution_clock>();
 
std::cout << "\nsteady_clock: " << std::endl;
 
printClockData<std::chrono::steady_clock>();
 
 
#ifdef _WIN32
 
system("pause");
 
#endif
 
return 0;
 
}
 
 
system_clock:
 
- precision: 0.000100 milliseconds
 
- is_steady: false
 
 
high_resolution_clock:
 
- precision: 0.000001 milliseconds
 
- is_steady: true
 
 
steady_clock:
 
- precision: 0.000001 milliseconds
 
- is_steady: true

Template

#include <chrono>

#define TIMERSTART(tag)  auto tag##_start = std::chrono::steady_clock::now(),tag##_end = tag##_start
#define TIMEREND(tag)  tag##_end =  std::chrono::steady_clock::now()
#define DURATION_s(tag) printf("%s costs %d s\n",#tag,std::chrono::duration_cast<std::chrono::seconds>(tag##_end - tag##_start).count())
#define DURATION_ms(tag) printf("%s costs %d ms\n",#tag,std::chrono::duration_cast<std::chrono::milliseconds>(tag##_end - tag##_start).count());
#define DURATION_us(tag) printf("%s costs %d us\n",#tag,std::chrono::duration_cast<std::chrono::microseconds>(tag##_end - tag##_start).count());
#define DURATION_ns(tag) printf("%s costs %d ns\n",#tag,std::chrono::duration_cast<std::chrono::nanoseconds>(tag##_end - tag##_start).count());


// usage:
//   TIMERSTART(for_loop);
//   for (int i = 0; i < 100000; i++)
//   {
//       i*i;
//   }
//   TIMEREND(for_loop);
//   DURATION_ms(for_loop);

新容器

forward_list

Abstract

Forward lists are sequence containers that allow constant time insert and erase operations anywhere within the sequence.

Forward lists are implemented as singly-linked lists; Singly linked lists can store each of the elements they contain in different and unrelated storage locations. The ordering is kept by the association to each element of a link to the next element in the sequence.

前向列表是序列容器,允许在序列的任何地方进行常数时间的插入和删除操作。

转发列表实现为单链表;单链表可以将它们包含的每个元素存储在不同且不相关的存储位置。顺序是通过链接到序列中的下一个元素的每个元素的关联来保持的。

forward_list容器和list容器在设计上的主要区别是,前者在内部只保留到下一个元素的链接,而后者为每个元素保留两个链接:一个指向下一个元素,一个指向上一个元素,允许在两个方向上高效迭代,但每个元素消耗额外的存储空间,插入和删除元素的时间开销略高。因此,Forward_list对象比list对象更有效,尽管它们只能向前迭代。

How

head file

#include <forward_list>
using namespace std;

member funtion

before_begin() 返回一个前向迭代器,其指向容器中第一个元素之前的位置。

begin() 返回一个前向迭代器,其指向容器中第一个元素的位置。

end() 返回一个前向迭代器,其指向容器中最后一个元素之后的位置。

cbefore_begin() 和 before_begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。

cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。

cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。

empty() 判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。

max_size() 返回容器所能包含元素个数的最大值。这通常是一个很大的值,一般是 232-1,所以我们很少会用到这个函数。

front() 返回第一个元素的引用。

assign() 用新元素替换容器中原有内容。

push_front() 在容器头部插入一个元素。

emplace_front() 在容器头部生成一个元素。该函数和 push_front() 的功能相同,但效率更高。

pop_front() 删除容器头部的一个元素。

emplace_after() 在指定位置之后插入一个新元素,并返回一个指向新元素的迭代器。和 insert_after() 的功能相同,但效率更高。

insert_after() 在指定位置之后插入一个新元素,并返回一个指向新元素的迭代器。

erase_after() 删除容器中某个指定位置或区域内的所有元素。

swap() 交换两个容器中的元素,必须保证这两个容器中存储的元素类型是相同的。

resize() 调整容器的大小。

clear() 删除容器存储的所有元素。

splice_after() 将某个 forward_list 容器中指定位置或区域内的元素插入到另一个容器的指定位置之后。

remove(val) 删除容器中所有等于 val 的元素。

remove_if() 删除容器中满足条件的元素。

unique() 删除容器中相邻的重复元素,只保留一个。

merge() 合并两个事先已排好序的 forward_list 容器,并且合并之后的 forward_list 容器依然是有序的。

sort() 通过更改容器中元素的位置,将它们进行排序。

reverse() 反转容器中元素的顺序。

notice

forward_list 容器中是不提供 size() 函数的,但如果想要获取 forward_list 容器中存储元素的个数,可以使用头文件 中的 distance() 函数

#include<iostream>
#include<vector>
#include<cstdio>
#include<string>
#include <forward_list>
#include <iterator>

using namespace std;
int main() {
    forward_list<int> my_words{1, 2, 3, 5};
    int count = distance(my_words.begin(), my_words.end());
    cout << count << endl;    
  return 0;
}

forward_list 容器迭代器的移动除了使用 ++ 运算符单步移动,还能使用 advance() 函数

#include <iostream>
#include <forward_list>
using namespace std;
int main()
{
    std::forward_list<int> values{1,2,3,4};
    auto it = values.begin();
    advance(it, 2);
    while (it!=values.end())
    {
        cout << *it << " ";
        ++it;
    }
    return 0;
}

Demo

// forward_list::emplace_front
#include <iostream>
#include <forward_list>

int main ()
{
  std::forward_list< std::pair<int,char> > mylist;

  mylist.emplace_front(10,'a');
  mylist.emplace_front(20,'b');
  mylist.emplace_front(30,'c');

  std::cout << "mylist contains:";
  for (auto& x: mylist)
    std::cout << " (" << x.first << "," << x.second << ")";

  std::cout << std::endl;
  return 0;
}

array

Abstract

Arrays are fixed-size sequence containers: they hold a specific number of elements ordered in a strict linear sequence.

Internally, an array does not keep any data other than the elements it contains (not even its size, which is a template parameter, fixed on compile time). It is as efficient in terms of storage size as an ordinary array declared with the language's bracket syntax ([]). This class merely adds a layer of member and global functions to it, so that arrays can be used as standard containers.

Unlike the other standard containers, arrays have a fixed size and do not manage the allocation of its elements through an allocator: they are an aggregate type encapsulating a fixed-size array of elements. Therefore, they cannot be expanded or contracted dynamically (see vector for a similar container that can be expanded).

Zero-sized arrays are valid, but they should not be dereferenced (members front, back, and data).

Unlike with the other containers in the Standard Library, swapping two array containers is a linear operation that involves swapping all the elements in the ranges individually, which generally is a considerably less efficient operation. On the other side, this allows the iterators to elements in both containers to keep their original container association.

Another unique feature of array containers is that they can be treated as tuple objects: The <array> header overloads the get function to access the elements of the array as if it was a tuple, as well as specialized tuple_size and tuple_element types.


数组是固定大小的序列容器:它们以严格的线性序列保存特定数量的元素。

在内部,数组不保留它所包含的元素以外的任何数据(甚至不保留它的大小,这是一个模板参数,在编译时固定)。就存储大小而言,它与使用语言的方括号语法([])声明的普通数组一样有效。这个类只添加了一层成员和全局函数,这样数组就可以用作标准容器。

与其他标准容器不同,数组具有固定大小,不通过分配器管理其元素的分配:它们是封装了固定大小的元素数组的聚合类型。因此,它们不能动态地展开或收缩(参见vector获取可展开的类似容器)。

零大小的数组是有效的,但是不应该解除对它们的引用(成员前面、后面和数据)。

与标准库中的其他容器不同,交换两个数组容器是一种线性操作,需要分别交换范围内的所有元素,这种操作的效率通常要低得多。另一方面,这允许两个容器中的元素的迭代器保持它们原来的容器关联关系。

数组容器的另一个独特特性是它们可以被视为元组对象:Header重载get函数来访问数组的元素,就像它是一个元组一样,以及专门的tuple_size和tuple_element类型。

Comparewith vector

std::vector 不同,std::array 对象的大小是固定的,如果容器大小是固定的,那么可以优先考虑使用 std::array 容器。 另外由于 std::vector 是自动扩容的,当存入大量的数据后,并且对容器进行了删除操作, 容器并不会自动归还被删除元素相应的内存,这时候就需要手动运行 shrink_to_fit() 释放这部分内存。

std::vector<int> v;
std::cout << "size:" << v.size() << std::endl;         // 输出 0
std::cout << "capacity:" << v.capacity() << std::endl; // 输出 0

// 如下可看出 std::vector 的存储是自动管理的,按需自动扩张
// 但是如果空间不足,需要重新分配更多内存,而重分配内存通常是性能上有开销的操作
v.push_back(1);
v.push_back(2);
v.push_back(3);
std::cout << "size:" << v.size() << std::endl;         // 输出 3
std::cout << "capacity:" << v.capacity() << std::endl; // 输出 4

// 这里的自动扩张逻辑与 Golang 的 slice 很像
v.push_back(4);
v.push_back(5);
std::cout << "size:" << v.size() << std::endl;         // 输出 5
std::cout << "capacity:" << v.capacity() << std::endl; // 输出 8

// 如下可看出容器虽然清空了元素,但是被清空元素的内存并没有归还
v.clear();                                             
std::cout << "size:" << v.size() << std::endl;         // 输出 0
std::cout << "capacity:" << v.capacity() << std::endl; // 输出 8

// 额外内存可通过 shrink_to_fit() 调用返回给系统
v.shrink_to_fit();
std::cout << "size:" << v.size() << std::endl;         // 输出 0
std::cout << "capacity:" << v.capacity() << std::endl; // 输出 0

Compare with original array

使用 std::array 能够让代码变得更加“现代化”,而且封装了一些操作函数,比如获取数组大小以及检查是否非空,同时还能够友好的使用标准库中的容器算法,比如 std::sort。

How

使用 std::array 很简单,只需指定其类型和大小即可,下面主要讲如何适配原生的数组接口

std::array<int, 4> arr = {1, 2, 3, 4};

arr.empty(); // 检查容器是否为空
arr.size();  // 返回容纳的元素数

// 迭代器支持
for (auto &i : arr)
{
    // ...
}

// 用 lambda 表达式排序
std::sort(arr.begin(), arr.end(), [](int a, int b) {
    return b < a;
});

// 数组大小参数必须是常量表达式
constexpr int len = 4;
std::array<int, len> arr = {1, 2, 3, 4};
void foo(int *p, int len) {
    return;
}

std::array<int, 4> arr = {1,2,3,4};

// C 风格接口传参
// foo(arr, arr.size()); // 非法, 无法隐式转换
foo(&arr[0], arr.size());
foo(arr.data(), arr.size());

// 使用 `std::sort`
std::sort(arr.begin(), arr.end());

Demo

// arrays as tuples
#include <iostream>
#include <array>
#include <tuple>

int main ()
{
  std::array<int,3> myarray = {10, 20, 30};
  std::tuple<int,int,int> mytuple (10, 20, 30);

  std::tuple_element<0,decltype(myarray)>::type myelement;  // int myelement

  myelement = std::get<2>(myarray);
  std::get<2>(myarray) = std::get<0>(myarray);
  std::get<0>(myarray) = myelement;

  std::cout << "first element in myarray: " << std::get<0>(myarray) << "\n";
  std::cout << "first element in mytuple: " << std::get<0>(mytuple) << "\n";

  return 0;
}

tuple

Abstract

Class template std::tuple is a fixed-size collection of heterogeneous values. It is a generalization of std::pair.

类模板std::tuple是一个固定大小的异构值集合。它是std::pair的一种加强版本,std::pair 的缺陷是显而易见的,只能保存两个元素

How

关于元组的使用有三个核心的函数:

  1. std::make_tuple: 构造元组
  2. std::get: 获得元组某个位置的值
  3. std::tie: 元组拆包
#include <tuple>
#include <iostream>

auto get_student(int id)
{
    // 返回类型被推断为 std::tuple<double, char, std::string>

    if (id == 0)
        return std::make_tuple(3.8, 'A', "张三");
    if (id == 1)
        return std::make_tuple(2.9, 'C', "李四");
    if (id == 2)
        return std::make_tuple(1.7, 'D', "王五");
    return std::make_tuple(0.0, 'D', "null");
    // 如果只写 0 会出现推断错误, 编译失败
}

int main()
{
    auto student = get_student(0);
    std::cout << "ID: 0, "
    << "GPA: " << std::get<0>(student) << ", "
    << "成绩: " << std::get<1>(student) << ", "
    << "姓名: " << std::get<2>(student) << '\n';

    double gpa;
    char grade;
    std::string name;

    // 元组进行拆包
    std::tie(gpa, grade, name) = get_student(1);
    std::cout << "ID: 1, "
    << "GPA: " << gpa << ", "
    << "成绩: " << grade << ", "
    << "姓名: " << name << '\n';
}

std::get 除了使用常量获取元组对象外,C++14 增加了使用类型来获取元组中的对象

std::tuple<std::string, double, double, int> t("123", 4.5, 6.7, 8);
std::cout << std::get<std::string>(t) << std::endl;
std::cout << std::get<double>(t) << std::endl; // 非法, 引发编译期错误,弱国tuple中只有一个double就可以
std::cout << std::get<3>(t) << std::endl;

获取长度

template <typename T>
auto tuple_len(T &tpl) {
    return std::tuple_size<T>::value;
}

无序容器

Abstract

std::map/std::set,这些元素内部通过红黑树进行实现, 插入和搜索的平均复杂度均为 O(log(size))。在插入元素时候,会根据 < 操作符比较元素大小并判断元素是否相同, 并选择合适的位置插入到容器中。当对这个容器中的元素进行遍历时,输出结果会按照 < 操作符的顺序来逐个遍历。

而无序容器中的元素是不进行排序的,内部通过 Hash 表实现,插入和搜索元素的平均复杂度为 O(constant), 在不关心容器内部元素顺序时,能够获得显著的性能提升。

但是hash表的建立相对比较耗时(需要解决hash冲突),因此插入的效率稍显逊色;

C++11 引入了的两组无序容器分别是:std::unordered_map/std::unordered_multimap 和 std::unordered_set/std::unordered_multiset。

它们的用法和原有的 std::map/std::multimap/std::set/set::multiset 基本类似。

因此在容器中元素比较少,并且查询要求比较多的场景中,无序容器有更好的性能。

Demo

#include <iostream>
#include <string>
#include <unordered_map>
#include <map>

int main() {
    // 两组结构按同样的顺序初始化
    std::unordered_map<int, std::string> u = {
        {1, "1"},
        {3, "3"},
        {2, "2"}
    };
    std::map<int, std::string> v = {
        {1, "1"},
        {3, "3"},
        {2, "2"}
    };

    // 分别对两组结构进行遍历
    std::cout << "std::unordered_map" << std::endl;
    for( const auto & n : u)
        std::cout << "Key:[" << n.first << "] Value:[" << n.second << "]\n";

    std::cout << std::endl;
    std::cout << "std::map" << std::endl;
    for( const auto & n : v)
        std::cout << "Key:[" << n.first << "] Value:[" << n.second << "]\n";
}

新算法

Abstract

all_of:检测表达式是否对范围[first, last)中所有元素都返回true,如果都满足,则返回true

any_of:检测表达式是否对范围[first, last)中至少一个元素返回true,如果满足,则返回true

none_of:检测表达式是否对范围[first, last)中所有元素都不返回true,如果都不满足,则返回true,否则返回false,用法和上面一样

find_if_not:找到第一个不符合要求的元素迭代器,和find_if相反
copy_if:复制满足条件的元素

minmax_element:返回容器内最大元素和最小元素位置

is_sorted、is_sorted_until:返回容器内元素是否已经排好序。

itoa:对容器内的元素按序递增

Demo

std::vector<int> l(10);
std::iota(l.begin(), l.end(), 19); // 19为初始值
for (auto n : l) std::cout << n << ' ';
// 19 20 21 22 23 24 25 26 27 28

int main() {
    std::vector<int> v = {3, 9, 1, 4, 2, 5, 9};

    auto result = std::minmax_element(v.begin(), v.end());
    std::cout << "min element at: " << *(result.first) << '\n';
    std::cout << "max element at: " << *(result.second) << '\n';
    return 0;
}

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
C++11引入了许多新特性,其中包括线程池的实现。在引用中的代码中,ZERO_ThreadPool类封装了线程池的功能。线程池的原理是通过维护一个线程队列和一个任务队列来实现的。 在初始化阶段,通过调用init函数来初始化线程池。该函数会创建指定数量的线程,并将其保存在threads_队列中。如果线程池已经被初始化过,则直接返回false。 在启动线程池后,调用start函数。该函数会循环创建指定数量的线程,并将它们放入threads_队列中。每个线程都会调用run函数来执行任务。 当调用exec函数时,会将任务添加到tasks_队列中。其中,std::bind用于绑定一个成员函数和其参数,以及占位符std::placeholders::_1表示传入的参数。 在waitForAllDone函数中,会判断atomic_是否为0且tasks_是否为空。如果是,则表示所有任务已经执行完毕,线程池可以退出。 线程池的stop函数用于停止线程池的运行。它会遍历threads_队列,并调用每个线程的join函数,等待线程执行完毕后再返回。 以上就是C++11新特性线程池的基本原理。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [基于C++11新特性手写线程池实现](https://blog.csdn.net/m0_70418130/article/details/126805390)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ym影子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值