C++11的一些特性
记录一下使用过的C++11的一些特性,大致分为三部分:并发相关,智能指针相关,chrono。
并发相关:
std::thread相关:
#include <iostream> #include <thread> using namespace std; int main() { auto func = []() { for (int i = 0; i < 10; ++i) { cout << i << " "; } cout << endl; }; std::thread t(func); if (t.joinable()) { t.detach(); } auto func1 = [](int k) { for (int i = 0; i < k; ++i) { cout << i << " "; } cout << endl; }; std::thread tt(func1, 20); if (tt.joinable()) { // 检查线程可否被join tt.join(); } return 0; }
上述代码中,函数func和func1运行在线程对象t和tt中,从刚创建对象开始就会新建一个线程用于执行函数,调用join函数将会阻塞主线程,直到线程函数执行结束,线程函数的返回值将会被忽略。如果不希望线程被阻塞执行,可以调用线程对象的detach函数,表示将线程和线程对象分离。
如果没有调用join或者detach函数,假如线程函数执行时间较长,此时线程对象的生命周期结束调用析构函数清理资源,这时可能会发生错误,这里有两种解决办法,一个是调用join(),保证线程函数的生命周期和线程对象的生命周期相同,另一个是调用detach(),将线程和线程对象分离,这里需要注意,如果线程已经和对象分离,那我们就再也无法控制线程什么时候结束了,不能再通过join来等待线程执行完。
c++11还提供了获取线程id,或者系统cpu个数,获取thread native_handle,使得线程休眠等功能,如下:
std::thread t(func); cout << "当前线程ID " << t.get_id() << endl; cout << "当前cpu个数 " << std::thread::hardware_concurrency() << endl; auto handle = t.native_handle();// handle可用于pthread相关操作 std::this_thread::sleep_for(std::chrono::seconds(1));
std::mutex相关:
std::mutex是一种线程同步的手段,用于保存多线程同时操作的共享数据。
mutex分为四种:
-
std::mutex:独占的互斥量,不能递归使用,不带超时功能
-
std::recursive_mutex:递归互斥量,可重入,不带超时功能
-
std::timed_mutex:带超时的互斥量,不能递归
-
std::recursive_timed_mutex:带超时的互斥量,可以递归使用
使用时,都是直接lock或者unique_lock,伪代码如下:
if (1) { LevelLog(DDRFramework::Log::NOTICE, "BaseServiceClient::CheckAndRecordNetStatus() 开始检测网络 ++++++ "); std::unique_lock<std::mutex> lck(m_checkNetStatus); // 方式1(推荐) m_NetStatusIsRunningIndex = true; } // 或者如下: if(1) { m_checkNetStatus.lock(); // 方式2 m_NetStatusIsRunningIndex = true; m_checkNetStatus.unlock(); }
方式1的主要优势在于若中间处理部分,抛异常了,能自动解锁。
std::lock相关:
c++11主要有std::lock_guard和std::unique_lock两种方式。
#include <iostream> #include <mutex> #include <thread> #include <chrono> using namespace std; std::mutex mutex_; int main() { auto func1 = [](int k) { // std::lock_guard<std::mutex> lock(mutex_); std::unique_lock<std::mutex> lock(mutex_); for (int i = 0; i < k; ++i) { cout << i << " "; } cout << endl; }; std::thread threads[5]; for (int i = 0; i < 5; ++i) { threads[i] = std::thread(func1, 200); } for (auto& th : threads) { th.join(); } return 0; }
std::lock_gurad相比于std::unique_lock更加轻量级,少了一些成员函数,std::unique_lock类有unlock函数,可以手动释放锁,所以条件变量都配合std::unique_lock使用,而不是std::lock_guard,因为条件变量在wait时需要有手动释放锁的能力,具体关于条件变量后面会讲到。
std::atomic相关:
c++11提供了原子类型std::atomic<T>,理论上这个T可以是任意类型,但是我平时只存放整形,别的还真的没用过,整形有这种原子变量已经足够方便,就不需要使用std::mutex来保护该变量啦。看一个计数器的代码:
struct OriginCounter { // 普通的计数器 int count; std::mutex mutex_; void add() { std::lock_guard<std::mutex> lock(mutex_); ++count; } void sub() { std::lock_guard<std::mutex> lock(mutex_); --count; } int get() { std::lock_guard<std::mutex> lock(mutex_); return count; } }; struct NewCounter { // 使用原子变量的计数器 std::atomic<int> count; void add() { ++count; // count.store(++count);这种方式也可以 } void sub() { --count; // count.store(--count); } int get() { return count.load(); } };
std::call_once相关:
c++11提供了std::call_once来保证某一函数在多线程环境中只调用一次,它需要配合std::once_flag使用,直接看使用代码吧:
std::once_flag onceflag; void CallOnce() { std::call_once(onceflag, []() { cout << "call once" << endl; }); } int main() { std::thread threads[5]; for (int i = 0; i < 5; ++i) { threads[i] = std::thread(CallOnce); } for (auto& th : threads) { th.join(); } return 0; }
std::condition_variable相关:
条件变量是c++11引入的一种同步机制,它可以阻塞一个线程或者个线程,直到有线程通知或者超时才会唤醒正在阻塞的线程,条件变量需要和锁配合使用,这里的锁就是上面介绍的std::unique_lock。
这里使用条件变量实现一个CountDownLatch:
class CountDownLatch { public: explicit CountDownLatch(uint32_t count) : count_(count); void CountDown() { std::unique_lock<std::mutex> lock(mutex_); --count_; if (count_ == 0) { cv_.notify_all(); } } void Await(uint32_t time_ms = 0) { std::unique_lock<std::mutex> lock(mutex_); while (count_ > 0) { if (time_ms > 0) { cv_.wait_for(lock, std::chrono::milliseconds(time_ms)); } else { cv_.wait(lock); } } } uint32_t GetCount() const { std::unique_lock<std::mutex> lock(mutex_); return count_; } private: std::condition_variable cv_; mutable std::mutex mutex_; uint32_t count_ = 0; };
智能指针相关:
c++11引入了三种智能指针:
-
std::shared_ptr
-
std::weak_ptr
-
std::unique_ptr
shared_ptr:
shared_ptr使用了引用计数,每一个shared_ptr的拷贝都指向相同的内存,每次拷贝都会触发引用计数+1,每次生命周期结束析构的时候引用计数-1,在最后一个shared_ptr析构的时候,内存才会释放。
使用方法如下:
struct ClassWrapper { ClassWrapper() { cout << "construct" << endl; data = new int[10]; } ~ClassWrapper() { cout << "deconstruct" << endl; if (data != nullptr) { delete[] data; } } void Print() { cout << "print" << endl; } int* data; }; void Func(std::shared_ptr<ClassWrapper> ptr) { ptr->Print(); } int main() { auto smart_ptr = std::make_shared<ClassWrapper>(); auto ptr2 = smart_ptr; // 引用计数+1 ptr2->Print(); Func(smart_ptr); // 引用计数+1 smart_ptr->Print(); ClassWrapper *p = smart_ptr.get(); // 可以通过get获取裸指针 p->Print(); return 0; }
智能指针还可以自定义删除器,在引用计数为0的时候自动调用删除器来释放对象的内存,代码如下:
std::shared_ptr<int> ptr(new int, [](int *p){ delete p; });
关于shared_ptr有几点需要注意:
- 不要用一个裸指针初始化多个shared_ptr,会出现double_free导致程序崩溃
- •通过shared_from_this()返回this指针,不要把this指针作为shared_ptr返回出来,因为this指针本质就是裸指针,通过this返回可能 会导致重复析构,不能把this指针交给智能指针管理。
-
尽量使用make_shared,少用new。
-
不要delete get()返回来的裸指针。
-
不是new出来的空间要自定义删除器。
-
要避免循环引用,循环引用导致内存永远不会被释放,造成内存泄漏。
weak_ptr:
weak_ptr是用来监视shared_ptr的生命周期,它不管理shared_ptr内部的指针,它的拷贝的析构都不会影响引用计数,纯粹是作为一个旁观者监视shared_ptr中管理的资源是否存在,可以用来返回this指针和解决循环引用问题。
-
作用1:返回this指针,上面介绍的shared_from_this()其实就是通过weak_ptr返回的this指针,这里参考我之前写的源码分析shared_ptr实现的文章,最后附上链接。
-
作用2:解决循环引用问题。
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
unique_ptr
std::unique_ptr是一个独占型的智能指针,它不允许其它智能指针共享其内部指针,也不允许unique_ptr的拷贝和赋值。使用方法和shared_ptr类似,区别是不可以拷贝:
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; }
chrono库
c++11关于时间引入了chrono库,源于boost,功能强大,chrono主要有三个点:
-
duration
-
time_point
-
clocks
duration:
std::chrono::duration表示一段时间,常见的单位有s、ms等,示例代码:
// 拿休眠一段时间举例,这里表示休眠100ms std::this_thread::sleep_for(std::chrono::milliseconds(100));
sleep_for里面其实就是std::chrono::duration,表示一段时间,实际是这样:
typedef duration<int64_t, milli> milliseconds;
typedef duration<int64_t> seconds;
duration具体模板如下:
1 template <class Rep, class Period = ratio<1> > class duration;
Rep表示一种数值类型,用来表示Period的数量,比如int、float、double,Period是ratio类型,用来表示【用秒表示的时间单位】比如second,常用的duration<Rep, Period>已经定义好了,在std::chrono::duration下:
-
ratio<3600, 1>:hours
-
ratio<60, 1>:minutes
-
ratio<1, 1>:seconds
-
ratio<1, 1000>:microseconds
-
ratio<1, 1000000>:microseconds
-
ratio<1, 1000000000>:nanosecons
ratio的具体模板如下:
template <intmax_t N, intmax_t D = 1> class ratio;
N代表分子,D代表分母,所以ratio表示一个分数,我们可以自定义Period,比如ratio<2, 1>表示单位时间是2秒。
time_point
表示一个具体时间点,如2020年5月10日10点10分10秒,拿获取当前时间举例:
std::chrono::time_point<std::chrono::high_resolution_clock> Now() { return std::chrono::high_resolution_clock::now(); }
clocks
时钟,chrono里面提供了三种时钟:
-
steady_clock
-
system_clock
-
high_resolution_clock
steady_clock
稳定的时间间隔,表示相对时间,相对于系统开机启动的时间,无论系统时间如何被更改,后一次调用now()肯定比前一次调用now()的数值大,可用于计时。
system_clock
表示当前的系统时钟,可以用于获取当前时间:
int main() { using std::chrono::system_clock; system_clock::time_point today = system_clock::now(); std::time_t tt = system_clock::to_time_t(today); std::cout << "today is: " << ctime(&tt); return 0; }
high_resolution_clock
high_resolution_clock表示系统可用的最高精度的时钟,实际上就是system_clock或者steady_clock其中一种的定义,官方没有说明具体是哪个,不同系统可能不一样,我之前看gcc chrono源码中high_resolution_clock是steady_clock的typedef。
用法:
使用最多的几种用法。
long long GetNow_Steady() // 获取系统开机时间。单位ms { return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count(); } // 有点像UTC+8时间 long long GetNow_SysTime() { return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count(); } void Sleep(int nMillisec) { #if defined(_WIN32) || defined(_WIN64) ::Sleep(nMillisec); #else std::this_thread::sleep_for(std::chrono::milliseconds(nMillisec)); #endif }