目录
带超时的互斥量std::timed_mutex和 std::recursive_timed_mutex
std::condition_variable::wait_for
std::condition_variable::wait_until
Operations supported by certain specializations (integral and/or pointer)
std::atomic::compare_exchange_weak
Wait-free ring buffer – 无锁环形队列
Lock-free multi-producer queue- 无锁多生产者队列
异步操作future & async & package_task & promise
std::function & std::bind & lambda表达式
多线程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_lock比lock_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 until notified (public member function ) |
|
Wait for timeout or until notified (public member function ) |
|
Wait until notified or time point (public member function ) |
|
Notify one (public member function ) |
|
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
- 先创建一个package_task 的对象
- 通过get_future 获取future
- 执行task
- 主线程运行other job
- 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);
其中,Args 与 args 分别代表模板与函数的变长参数集合, 称之为参数包 (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 的哪些问题
- 返回管理this的shared_ptr
-
- 上面介绍的shared_from_this()其实就是通过weak_ptr返回的this指针
- 解决循环引用问题
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版本源码分析图
分析
- shared_ptr类几乎什么都没有做,它是继承了__shared_ptr
- __shared_ptr内部有一个类型为__shared_count类型的成员_M_refcount
- __shared_count内部有类型为_Sp_counted_base*的_M_pi的成员, _Sp_counted_base才是整个shared_ptr功能的核心,通过_Sp_counted_base控制引用计数来管理托管的内存
- Sp_counted_base内部不持有托管内存的指针,__shared_count内部的成员其实是一个继承自_Sp_counted_base的_Sp_counted_ptr类型
- _Sp_counted_ptr类型内部持有托管内存的指针_M_ptr
- , _M_use_count表示托管对象的引用计数,控制托管对象什么时候析构和释放
- 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
关于元组的使用有三个核心的函数:
- std::make_tuple: 构造元组
- std::get: 获得元组某个位置的值
- 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; }