文章目录
指针是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;
}
// 结果:32、在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