文章目录
指针是C的灵魂,在C++里面指针也有着很重要的地位,但是指针的使用带来了很多问题,比如内存泄漏。C++没有自动内存管理机制,在堆上面分配的资源需要自己释放。所以在c语言中的malloc/free和C++中的new/delete总是需要成对出现。我们平常看到的指针通常被叫做裸指针,在编写程序时因为各种原因可能会出现不释放的问题,为此智能指针就有他的用处了。智能指针并不是指针,它仅仅是一个栈对象而已,在栈对象的生命周期结束时,智能指针调用析构函数释放其管理的堆内存。所有的智能指针都重载了'operator->'操作符,用来返回其管理对象的引用。从而可以执行所管理对象的一些操作。
零、准备
1、C++11前智能指针使用方法:
std::auto_ptr // C++11中被弃用
boost::scoped_ptr
boost::shared_ptr
boost::intrusive_ptr
boost::scoped_array
boost::shared_array
boost::weak_ptr
2、C++11中的智能指针:
shared_ptr
weak_ptr
unique_ptr
3、两个重要方法
A、get()
访问智能指针包含的裸指针引用。
B、reset()
若传递参数为空或NULL 则智能指针会释放当前管理的内存。
若传递参数为一个对象 则智能指针会释放当前管理的内存,管理新传入的对象。
4、定义一个类用于测试
class Evan { public: Evan() : key_(0), value_("") { cout<< "\t Construct Evan with no parameter!" << endl; } Evan(int key, string value) : key_(key), value_(value) { cout<< "\t Construct Evan with [" << key << "," << value << "]!" << endl; } ~Evan() { cout<< "\t Destruct Evan object!" << endl; } void PrintEvan() { cout << "\t PrintEvan: key = " << key_ << ", value = " << value_ << endl; } void SetKey(int key) { key_ = key; } void SetValue(string value) { value_ = value; } private: int key_; string value_; };
5、程序结构
#include <iostream> #include <string> using namespace std; void TestAutoPtr(); void TestBoostScopedPtr(); void TestBoostSharedPtr(); void TestBoosTintrusivePtr(); void TestBoostScopedArray(); void TestBoostSharedArray(); void TestBoostWeakPtr(); void TestSharedPtr(); void TestWeakPtr(); void TestUniquePtr(); int main(int argc, char const *argv[]) { TestAutoPtr(); // boost TestBoostScopedPtr(); TestBoostSharedPtr(); TestBoosTintrusivePtr(); TestBoostScopedArray(); TestBoostSharedArray(); TestBoostWeakPtr(); // C++ 11 TestSharedPtr(); TestWeakPtr(); TestUniquePtr(); return 0; }
一、std::auto_ptr
1、在namespace std中 添加头文件 #include <memory>即可使用。一般用于管理单个堆内存对象。
2、代码
#include <memory> void TestAutoPtr() { cout << "TestAutoPtr" <<endl; auto_ptr<Evan> evan_auto_ptr(new Evan(1, " xia")); if (evan_auto_ptr.get()) // 判断智能指针是否为空 { evan_auto_ptr->PrintEvan(); Evan* evan_ptr = evan_auto_ptr.get(); // get() 返回裸指针的引用 evan_ptr->SetKey(2); // 通过裸指针访问 evan_ptr->PrintEvan(); (*evan_auto_ptr).SetValue("evan"); // operator* 返回智能指针管理的对象 (*evan_auto_ptr).PrintEvan(); evan_auto_ptr.reset(new Evan()); // 赋值一个新的 evan_auto_ptr->PrintEvan(); } }
3、结果
4、注意
a. 切记使用 "operator=",万一真用了,就不要再使用旧的对象了。
// bug - 赋值问题 : Segmentation fault { auto_ptr<Evan> evan_auto_ptr1; evan_auto_ptr1 = evan_auto_ptr; // 原因在这行代码 evan_auto_ptr1->PrintEvan(); evan_auto_ptr->PrintEvan(); // 崩溃在这行代码 // evan_auto_ptr1剥夺了evan_auto_ptr的内存管理所有权,导致evan_auto_ptr1悬空。 }
b. release()方法不会释放内存 仅仅只是让出所有权。
// bug - release问题 { evan_auto_ptr.release(); // 并没有看到析构函数被调用的迹象 evan_auto_ptr.reset(NULL); // 正确调用 }
二、boost::scoped_ptr
1、Boost库
是一个可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的开发引擎之一。安装方法参考:
A、安装依赖库:
apt-get install mpi-default-dev #安装mpi库 apt-get install libicu-dev #支持正则表达式的UNICODE字符集 apt-get install python-dev #需要python的话 apt-get install libbz2-dev #如果编译出现错误:bzlib.h: No such file or directory
B、下载安装
下载:http://www.boost.org/users/history/version_1_58_0.html 建议查看G++版本,找支持的版本的boost;
编译安装:解压、进入目录、./bootstrap.sh 然后 ./b2 install
C、测试
#include<iostream> #include<boost/bind.hpp> using namespace std; using namespace boost; int fun(int x,int y){return x+y;} int main(){ int m=1;int n=2; cout<<boost::bind(fun,_1,_2)(m,n)<<endl; return 0; } // 结果:3
2、在namespace boost中,添加头文件 #include<boost/smart_ptr.hpp> 即可使用。
3、与auto_ptr类似,scoped_ptr也可管理单个堆内存的对象,但是它独享所有权,因此也就避免了auto_ptr的缺陷。
4、代码
#include <boost/smart_ptr.hpp> using namespace boost; void TestBoostScopedPtr() { cout << "TestBoostScopedPtr" <<endl; scoped_ptr<Evan> evan_scoped_ptr(new Evan(1,"xia") ); if (evan_scoped_ptr.get()) { evan_scoped_ptr->PrintEvan(); // 通过智能指针访问 Evan* evan_ptr = evan_scoped_ptr.get(); // 获取裸指针 evan_ptr->PrintEvan(); // 裸指针访问 evan_ptr->SetKey(2); (*evan_scoped_ptr).PrintEvan(); // operator* 返回智能指针管理的对象 // evan_scoped_ptr.release(); // 错误,没有release成员 // scoped_ptr<Evan> evan_scoped_ptr2; // evan_scoped_ptr2 = evan_scoped_ptr; // 错误,没有赋值 } }
5、结果
6、注意
scoped_ptr没有release()函数 屏蔽了operator=操作, 因此不会出现auto_ptr中出现的内存泄漏和崩溃问题。 但这也带来了另一个新问题,我们无法复制智能指针。
1、由于scoped_ptr不允许赋值 拷贝 独享内存的所有权。 这里的shared_ptr是专门解决智能指针的内存所有权共享这个问题的, 其内部使用了引用计数。
2、代码
void TestBoostSharedPtr(shared_ptr<Evan> evan_shared_ptr) { evan_shared_ptr->PrintEvan(); cout << "\t used count:" << evan_shared_ptr.use_count() << endl; shared_ptr<Evan> evan_shared_ptr2; evan_shared_ptr2 = evan_shared_ptr; cout << "\t used count:" << evan_shared_ptr.use_count() << endl; cout << "\t used count:" << evan_shared_ptr2.use_count() << endl; } void TestBoostSharedPtr() { cout << "TestBoostSharedPtr" <<endl; shared_ptr<Evan> evan_shared_ptr(new Evan(1, "xia") ); if (evan_shared_ptr.get() ) { evan_shared_ptr->PrintEvan(); // 通过智能指针访问 Evan* evan_ptr = evan_shared_ptr.get(); // 获取裸指针 evan_ptr->PrintEvan(); // 通过裸指针访问 evan_ptr->SetValue("haha"); (*evan_shared_ptr).PrintEvan(); // operator* 返回智能指针管理的对象 } cout << "\t used count:" << evan_shared_ptr.use_count() << endl; TestBoostSharedPtr(evan_shared_ptr); cout << "\t used count:" << evan_shared_ptr.use_count() << endl; // evan_shared_ptr.release(); // 错误,没有release成员 }
3、结果
4、注意
shared_ptr可以赋值,但是同样也没有release成员;
四、boost::intrusive_ptr
1、intrusive_ptr是一种插入式智能指针,其内部没有引用计数,需要自己加入引用计数,否则会编译错误。
2、代码
//接口引用计数,示例例子不是线程安全的 class IUnknown { int m_lCountRef; public: IUnknown() : m_lCountRef(0) {} virtual ~IUnknown() {} unsigned long AddRef(void) { ++m_lCountRef; return m_lCountRef; } unsigned long Release(void) { if (--m_lCountRef==0) { delete this; } return m_lCountRef; } protected: IUnknown& operator=(const IUnknown&) { return *this; } private: // 禁止复制构造函数 IUnknown(const IUnknown&); }; class com_class : public IUnknown { public: com_class() { std::cout << "\t com_class::com_class()\n"; } com_class(const com_class& other) { std::cout << "\t com_class(const com_class& other)\n"; } ~com_class() { std::cout << "\t com_class::~com_class()\n"; } //定义两个给intrusive_ptr调用函数 friend void intrusive_ptr_add_ref(IUnknown* p) { p->AddRef(); } friend void intrusive_ptr_release(IUnknown* p) { p->Release(); } }; void TestBoosTintrusivePtr() { cout << "TestBoosTintrusivePtr" <<endl; boost::intrusive_ptr<com_class> p1(new com_class()); boost::intrusive_ptr<com_class> p2(p1); }
3、结果
4、注意
在多线程环境下,对保持引用计数的变量的任何操作都必须同步化。
intrusive_ptr是一个侵入式的引用计数型指针,它可以用于以下两种情形:
对内存占用的要求非常严格,要求必须与原始指针一样;
现存代码已经有了引用计数机制管理的对象;
五、boost::scoped_array
1、上面介绍的几种智能指针都针对单个对象的内存管理。其实智能指针也可管理数组。boost::scoped_array用于管理动态数组,也是独享所有权的。
2、代码
void TestBoostScopedArray() { cout << "TestBoostScopedArray" <<endl; scoped_array<Evan> evan_scoped_arr(new Evan[2] ); // 初始化动态数组 if (evan_scoped_arr.get() ) { evan_scoped_arr[0].PrintEvan(); // 通过智能指针访问 evan_scoped_arr.get()[1].PrintEvan(); // 通过裸指针访问 evan_scoped_arr.get()[0].SetKey(2); // (*evan_scoped_arr)[0].PrintEvan(); // 没有operator* // evan_scoped_arr[0].release(); // 没有成员release // scoped_array<Evan> evan_scoped_arr2; // evan_scoped_arr2 = evan_scoped_arr; // 禁止拷贝 } }
3、结果
4、注意
scoped_array的使用和scoped_ptr大致差不多。都禁止拷贝,独享所有权。但是scoped_array没有重载operator*操作符。
1、shared_array 也是管理数组的智能指针,内部使用了引用计数,解决参数传递和拷贝赋值时的问题。
2、代码
void TestBoostSharedArray(shared_array<Evan> evan_shared_arr) { cout << "\t used count:" << evan_shared_arr.use_count() << endl; shared_array<Evan> evan_shared_arr2; evan_shared_arr2 = evan_shared_arr; cout << "\t used count:" << evan_shared_arr.use_count() << endl; cout << "\t used count:" << evan_shared_arr2.use_count() << endl; } void TestBoostSharedArray() { cout << "TestBoostSharedArray" <<endl; shared_array<Evan> evan_shared_arr(new Evan[2] ); // 初始化动态数组 if (evan_shared_arr.get() ) { evan_shared_arr[0].PrintEvan(); // 通过智能指针访问 evan_shared_arr.get()[1].PrintEvan(); // 通过裸指针访问 // (*evan_scoped_arr)[0].PrintEvan(); // 没有operator* } cout << "\t used count:" << evan_shared_arr.use_count() << endl; TestBoostSharedArray(evan_shared_arr); cout << "\t used count:" << evan_shared_arr.use_count() << endl; }
3、结果
4、注意
和数组相关的智能指针都没有实现operator*,所以不能使用*运算符;
boost库中智能指针都没有release成员;
七、boost::weak_ptr
1、若我们仅仅关心能否使用对象,而不关心内部的引用计数。 boost::weak_ptr 是 boost::shared_ptr 的观察者(Observer)对象, 意味着boost::weak_ptr只对boost::shared_ptr引用,但是并不更新 其引用计数。被观察的shared_ptr失效后,weak_ptr相应也失效。
2、代码
void TestBoostWeakPtr() { cout << "TestBoostWeakPtr" <<endl; shared_ptr<Evan> evan_shared_ptr(new Evan(1,"xia") ); if (evan_shared_ptr.get() ) { weak_ptr<Evan> evan_weak_ptr; cout << "\t used count:" << evan_shared_ptr.use_count() << endl; evan_weak_ptr = evan_shared_ptr; // evan_weak_ptr->PrintEvan(); // 没有-> // evan_weak_ptr.get()->SetKey(2); // 没有get() // (*evan_weak_ptr).PrintEvan(); // 没有* cout << "\t used count:" << evan_shared_ptr.use_count() << endl; shared_ptr<Evan> evan_shared_ptr2; evan_shared_ptr2 = evan_weak_ptr.lock(); // 使用前必须转换 evan_shared_ptr2->PrintEvan(); cout << "\t used count:" << evan_shared_ptr.use_count() << endl; } }
3、结果
4、注意
它对被 shared_ptr 管理的对象存在非拥有性(“弱”)引用。在访问所引用的对象前必须先lock将其转换为 shared_ptr。有点儿类似于void*;
5、应用
weak_ptr更多用于:在基类中定义一个weak_ptr,用于观察其子类中的shared_ptr。这样基类只要看自己的weak_ptr是否为空就知道子类有没对自己赋值,而不影响子类中shared_ptr的引用计数,从而降低复杂度,更好管理对象。同时weak_ptr也有防止环形引用问题。
1、C++11支持
为了使用C++11编译,代码中注释了boost部分的内容,并且编译时使用了:
g++ -std=c++11 ./main.cc
2、shared_ptr来源于boost::shared_ptr,思想和用法差不多。
3、代码
void TestSharedPtr(shared_ptr<Evan> evan_shared_ptr) { evan_shared_ptr->PrintEvan(); cout << "\t used count:" << evan_shared_ptr.use_count() << endl; shared_ptr<Evan> evan_shared_ptr2; evan_shared_ptr2 = evan_shared_ptr; cout << "\t used count:" << evan_shared_ptr.use_count() << endl; cout << "\t used count:" << evan_shared_ptr2.use_count() << endl; } void TestSharedPtr() { cout << "TestSharedPtr" <<endl; shared_ptr<Evan> evan_shared_ptr(new Evan(1, "xia") ); if (evan_shared_ptr.get() ) { evan_shared_ptr->PrintEvan(); // 通过智能指针访问 Evan* evan_ptr = evan_shared_ptr.get(); // 获取裸指针 evan_ptr->PrintEvan(); // 通过裸指针访问 evan_ptr->SetValue("haha"); (*evan_shared_ptr).PrintEvan(); // operator* 返回智能指针管理的对象 } cout << "\t used count:" << evan_shared_ptr.use_count() << endl; TestSharedPtr(evan_shared_ptr); cout << "\t used count:" << evan_shared_ptr.use_count() << endl; // evan_shared_ptr.release(); // 错误,没有release成员 }
4、结果
5、注意
同一个shared_ptr被多个线程读,是线程安全的;
同一个shared_ptr被多个线程写,不是 线程安全的;
共享引用计数的不同的shared_ptr被多个线程写,是线程安全的。
shared_ptr指向数组在默认情况下,shared_ptr将调用delete进行内存的释放;当分配内存时使用new[]时,我们需要对应的调用delete[]来释放内存;为了能正确的使用shared_ptr指向一个数组,我们就需要定制一个删除函数。
九、weak_ptr
1、来源于boost::weak_ptr,思想和用法一样;
2、代码
void TestWeakPtr() { cout << "TestWeakPtr" <<endl; shared_ptr<Evan> evan_shared_ptr(new Evan(1, "xia") ); if (evan_shared_ptr.get() ) { weak_ptr<Evan> evan_weak_ptr; cout << "\t used count:" << evan_shared_ptr.use_count() << endl; evan_weak_ptr = evan_shared_ptr; // evan_weak_ptr->PrintEvan(); // 没有-> // evan_weak_ptr.get()->SetKey(2); // 没有get() // (*evan_weak_ptr).PrintEvan(); // 没有* cout << "\t used count:" << evan_shared_ptr.use_count() << endl; shared_ptr<Evan> evan_shared_ptr2; evan_shared_ptr2 = evan_weak_ptr.lock(); // 使用前必须转换 evan_shared_ptr2->PrintEvan(); cout << "\t used count:" << evan_shared_ptr.use_count() << endl; } }
3、结果
十、unique_ptr
1、C++11中的unique_ptr是auto_ptr的替代品,它与auto_ptr一样拥有唯一拥有权的特性,与auto_ptr不一样的是,unique_ptr是没有复制构造函数的,这就防止了一些“悄悄地”丢失所有权的问题发生,如果需要将所有权进行转移,只有在使用者显示的调用std::move之后,才会发生所有权的转移。和boost::scoped_ptr类似。
2、代码
void TestUniquePtr() { cout << "TestUniquePtr" <<endl; unique_ptr<Evan> evan_unique_ptr(new Evan(1, "xia") ); if (evan_unique_ptr.get() ) { evan_unique_ptr->PrintEvan(); // 智能指针访问 Evan* evan_ptr = evan_unique_ptr.get(); // 裸指针访问 evan_ptr->PrintEvan(); (*evan_unique_ptr).PrintEvan(); // *获取对象访问 unique_ptr<Evan> evan_unique_ptr2; // evan_unique_ptr2 = evan_unique_ptr; // 错误,没有= evan_unique_ptr2 = move(evan_unique_ptr); // 必须通过move转移,函数传参也一样 evan_unique_ptr2->PrintEvan(); // evan_unique_ptr->PrintEvan(); // 转移所有权后原来的就不能访问了 } }
3、结果
4、注意
直接传参时,进行值传递时,建立临时变量时,就会出错了,所以需要显示的调用move,转移所有权;而函数的返回值已经进行了move操作,而不用显示的进行调用。
十一、总结
名称 | 管理对象 | 所有权 | 复制(构造) | operator=赋值 | operator* | release | |
C++98 | auto_ptr | 单个 | 赋值转让 | Y | Y | Y | Y |
boost | scoped_ptr | 单个 | 独享 | N | N | Y | N |
shared_ptr | 单个 | 共享 | Y | Y | Y | N | |
intrusive_ptr | 单个 | 共享 | Y | Y | Y | N | |
scoped_array | 动态数组 | 独享 | N | N | N | N | |
shared_array | 动态数组 | 共享 | Y | Y | N | N | |
weak_ptr | 单个 | 共享 | Y | Y | Y | N | |
C++11 | shared_ptr | 单个 | 共享 | Y | Y | Y | N |
weak_ptr | 单个 | 共享 | Y | Y | Y | N | |
unique_ptr | 单个 | 独享 | N | N | Y | N |
源码:https://git.oschina.net/evan-xia/x_1607_CppPtr.git
参考
1、http://blog.csdn.net/u013575812/article/details/51155815
2、http://blog.csdn.net/yhrun/article/details/8099630
3、http://www.jellythink.com/archives/684
4、http://coolshell.cn/articles/7992.html
5、http://www.cnblogs.com/TenosDoIt/p/3456704.html
6、http://blog.csdn.net/callmeback/article/details/7729251
7、http://blog.csdn.net/caimouse/article/details/8631381
转载标明出处:https://blog.evanxia.com/2016/07/824