|
作者:lucasfan,腾讯IEGGlobalPub.Tech.客户端工程师智能指针在C++11标准中被引入真正标准库(C++98中引入的auto_ptr存在较多问题),但目前很多C++开发者仍习惯用原生指针,视智能指针为洪水猛兽。但很多实际场景下,智能指针却是解决问题的神器,尤其是一些涉及多线程的场景下。本文将介绍智能指针可以解决的问题,用法及最佳实践。并且根据源码分析智能指针的实现原理。一、为什么需要使用智能指针1.1内存泄漏C++在堆上申请内存后,需要手动对内存进行释放。代码的初创者可能会注意内存的释放,但随着代码协作者加入,或者随着代码日趋复杂,很难保证内存都被正确释放。尤其是一些代码分支在开发中没有被完全测试覆盖的时候,就算是内存泄漏检查工具也不一定能检查到内存泄漏。void test_memory_leak(bool open){ A *a = new A(); if(open) { // 代码变复杂过程中,很可能漏了 delete(a); return; } delete(a); return;}1.2多线程下对象析构问题多线程遇上对象析构,是一个很难的问题,稍有不慎就会导致程序崩溃。因此在对于C++开发者而言,经常会使用静态单例来使得对象常驻内存,避免析构带来的问题。这势必会造成内存泄露,当单例对象比较大,或者程序对内存非常敏感的时候,就必须面对这个问题了。先以一个常见的C++多线程问题为例,介绍多线程下的对象析构问题。比如我们在开发过程中,经常会在一个Class中创建一个线程,这个线程读取外部对象的成员变量。// 日志上报Classclass ReportClass{private: ReportClass() {} ReportClass(const ReportClass&) = delete; ReportClass& operator=(const ReportClass&) = delete; ReportClass(const ReportClass&) = delete; ReportClass& operator=(const ReportClass&) = delete;private: std::mutex mutex_; int count_ = 0; void addWorkThread();public: void pushEvent(std::string event);private: static void workThread(ReportClass *report);private: static ReportClass* instance_; static std::mutex static_mutex_;public: static ReportClass* GetInstance(); static void ReleaseInstance();};std::mutex ReportClass::static_mutex_;ReportClass* ReportClass::instance_;ReportClass* ReportClass::GetInstance(){ // 单例简单实现,非本文重点 std::lock_guard lock(static_mutex_); if (instance_ == nullptr) { instance_ = new ReportClass(); instance_->addWorkThread(); } return instance_;}void ReportClass::ReleaseInstance(){ std::lock_guard lock(static_mutex_); if(instance_ != nullptr) { delete instance_; instance_ = nullptr; }}// 轮询上报线程void ReportClass::workThread(ReportClass *report){ while(true) { // 线程运行过程中,report可能已经被销毁了 std::unique_lock lock(report->mutex_); if(report->count_ > 0) { report->count_--; } usleep(1000*1000); }}// 创建任务线程void ReportClass::addWorkThread(){ std::thread new_thread(workThread, this); new_thread.detach();}// 外部调用void ReportClass::pushEvent(std::string event){ std::unique_lock lock(mutex_); this->count_++;}使用ReportClass的代码如下:ReportClass::GetInstance()->pushEvent("test");但当这个外部对象(即ReportClass)析构时,对象创建的线程还在执行。此时线程引用的对象指针为野指针,程序必然会发生异常。解决这个问题的思路是在对象析构的时候,对线程进行join。// 日志上报Classclass ReportClass{private: //... ~ReportClass();private: //... bool stop_ = false; std::thread *work_thread_; //...};// 轮询上报线程void ReportClass::workThread(ReportClass *report){ while(true) { std::unique_lock lock(report->mutex_); // 如果上报停止,不再轮询上报 if(report->stop_) { break; } if(report->count_ > 0) { report->count_--; } usleep(1000*1000); }}// 创建任务线程void ReportClass::addWorkThread(){ // 保存线程指针,不再使用分离线程 work_thread_ = new std::thread(workThread, this);}ReportClass::~ReportClass(){ // 通过join来停止内部线程 stop_ = true; work_thread_->join(); delete work_thread_; work_thread_ = nullptr;}这种方式看起来没问题了,但是由于这个对象一般是被多个线程使用。假如某个线程想要释放这个对象,但另外一个线程还在使用这个对象,可能会出现野指针问题。就算释放对象的线程将对象释放后将指针置为nullptr,但仍然可能在多线程下在指针置空前被另外一个线程取得地址并使用。线程A线程BReportClass::GetInstance()->ReleaseInstance();ReportClass*report=ReportClass::GetInstance();if(report){//此时切换到线程Areport->pushEvent("test");}此种场景下,锁机制已经很难解决这个问题。对于多线程下的对象析构问题,智能指针可谓是神器。接下来我们先对智能指针的基本用法进行说明。二、智能指针的基本用法智能指针设计的初衷就是可以帮助我们管理堆上申请的内存,可以理解为开发者只需要申请,而释放交给智能指针。目前C++11主要支持的智能指针为以下几种unique_ptrshared_ptrweak_ptr2.1unique_ptr先上代码class A{public: void do_something() {}};void test_unique_ptr(bool open){ std::unique_ptr
|
|