一、作用

        传统指针管理起来非常的麻烦,极易内存泄漏(申请一块内存空间,指针却丢了),重复释放(已经释放一块内存了,后面还要释放这一块)等各种情况。所以引入智能指针,归根结底就是为了方便。 

二、智能指针类型:

        auto ptr 现在已经不用了。 share ptr,weak ptr, unique ptr三种。

2.1share ptr:

        share ptr内部有两个东西,一个是指向实际内存的指针,另一个是记录指向该地址的指针的数量的计数器,count。 比如 share01 和share02 都指向了同一块内存,那么count就是2.   share01不指的话,count变为1。 02也不指后,count变为0,然后释放这块内存。

2.2 share ptr用法:

       1 初始化:通过构造函数、std::shared_ptr辅助函数和reset方法来初始化shared_ptr,代码如下:

// 智能指针初始化
std::shared_ptr<int> p1(new int(1));
std::shared_ptr<int> p2 = p1;
std::shared_ptr<int> p3;
p3.reset(new int(1));
if(p3) {
cout << "p3 is not null";
}

优先使用make shared,这种方式效率更好:

auto sp1 = make_shared<int>(100);
//相当于
shared_ptr<int> sp1(new int(100));

不能把在堆上new出来的东西直接赋给智能指针。:

std::shared_ptr<int> p = new int(1);

对于一个未初始化的智能指针,可以通过reset方法来初始化,当智能指针有值的时候调用reset会引起引用计数减1。

        2 获取原始指针:

当需要获取原始指针时,可以通过get方法来返回原始指针,代码如下所示:

std::shared_ptr<int> ptr(new int(1));
int *p = ptr.get(); 
 delete p;

谨慎使用p.get()的返回值,如果你不知道其危险性则永远不要调用get()函数。
p.get()的返回值就相当于一个裸指针的值,不合适的使用这个值,上述陷阱的所有错误都有可能发生,
遵守以下几个约定:
        不要保存p.get()的返回值 ,无论是保存为裸指针还是shared_ptr都是错误的
        保存为裸指针不知什么时候就会变成空悬指针,保存为shared_ptr则产生了独立指针
        不要delete p.get()的返回值 ,会导致对一块内存delete两次的错误

        3. 指定删除器
如果用shared_ptr管理非new对象或是没有析构函数的类时,应当为其传递合适的删除器。
示例代码如下:

//1-1-delete
#include <iostream>
#include <memory>
using namespace std;
void DeleteIntPtr(int *p) {
cout << "call DeleteIntPtr" << endl;
delete p;
}
int main()
{
std::shared_ptr<int> p(new int(1), DeleteIntPtr);
return 0;
}

就是自己写一个删除器,用来析构。

当我们用shared_ptr管理动态数组时,需要指定删除器,因为shared_ptr的默认删除器不支持数组对象,代码如下所示:

std::shared_ptr<int> p3(new int[10], [](int *p) { delete [] p;});

        4.使用shared_ptr要注意的问题

  (1)不要用一个原始指针初始化多个shared_ptr,例如下面错误范例:

int *ptr = new int;
shared_ptr<int> p1(ptr);
shared_ptr<int> p2(ptr); // 逻辑错误

(2)不要在函数实参中创建shared_ptr,对于下面的写法

function(shared_ptr<int>(new int), g()); //有缺陷

因为C++的函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的,一般是从右到左,但也可能从左到右,所以,可能的过程是先new int,然后调用g(),如果恰好g()发生异常,而shared_ptr还没有创建, 则int内存泄漏了,正确的写法应该是先创建智能指针,代码如下:

shared_ptr<int> p(new int);
function(p, g());

(3)通过shared_from_this()返回this指针。不要将this指针作为shared_ptr返回出来,因为this指针本质上是一个裸指针,因此,这样可能会导致重复析构,看下面的例子。

//1-1-shared_from_this2
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
shared_ptr<A> GetSelf()
{
return shared_ptr<A>(this); // 不要这么做
}
~A()
{
cout << "Deconstruction A" << endl;
};

int main()
{
shared_ptr<A> sp1(new A);
shared_ptr<A> sp2 = sp1->GetSelf();
return 0;
}

        A会被析构两次。由于用同一个指针(this)构造了两个智能指针sp1和sp2,而他们之间是没有任何关系的,在离开作用域之后this将会被构造的两个智能指针各自析构,导致重复析构的错误。

正确返回this的shared_ptr的做法是:让目标类通过std::enable_shared_from_this类,然后使用基类的成员函数shared_from_this()来返回this的shared_ptr,如下所示。

#include <iostream>
#include <memory>
using namespace std;
class A: public std::enable_shared_from_this<A>
{
public:
shared_ptr<A>GetSelf()
{
return shared_from_this(); //
}
~A()
{
cout << "Deconstruction A" << endl;
}
};
int main()
{
shared_ptr<A> sp1(new A);
shared_ptr<A> sp2 = sp1->GetSelf(); // ok
return 0;
}

(4)避免循环引用。循环引用会导致内存泄漏,比如:

class A;
class B;
class A {
public:
std::shared_ptr<B> bptr;
~A() {
cout << "A is deleted" << endl;
}
};
class B {
public:
std::shared_ptr<A> aptr;
~B() {
cout << "B is deleted" << endl;
}
};
int main()
{
{
std::shared_ptr<A> ap(new A);
std::shared_ptr<B> bp(new B);
ap->bptr = bp;
bp->aptr = ap;
}
cout<< "main leave" << endl; // 循环引用导致ap bp退出了作用域都没有析构
return 0;
}

循环引用导致ap和bp的引用计数为2,在离开作用域之后,ap和bp的引用计数减为1,并不回减为0,导致两个指针都不会被析构,产生内存泄漏。解决的办法是把A和B任何一个成员变量改为weak_ptr,具体方法见weak_ptr章节。

3.1 unique ptr:

        

unique_ptr特性

  1. 基于排他所有权模式:两个指针不能指向同一个资源
  2. 无法进行左值unique_ptr复制构造,也无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值
  3. 保存指向某个对象的指针,当它本身离开作用域时会自动释放它指向的对象。
  4. 在容器中保存指针是安全的

无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值

unique_ptr<string> p1(new string("I'm Li Ming!"));
unique_ptr<string> p2(new string("I'm age 22."));
	
cout << "p1:" << p1.get() << endl;
cout << "p2:" << p2.get() << endl;

p1 = p2;					// 禁止左值赋值
unique_ptr<string> p3(p2);	// 禁止左值赋值构造

unique_ptr<string> p3(std::move(p1));
p1 = std::move(p2);	// 使用move把左值转成右值就可以赋值了,效果和auto_ptr赋值一样

cout << "p1 = p2 赋值后:" << endl;
cout << "p1:" << p1.get() << endl;
cout << "p2:" << p2.get() << endl;

move包裹后,强行告诉编译器 ,p1为右值,赋给p3. 那么原先有p1指向存有字符串的内存,现在p3指向这块区域,p1放弃这一块内存。说白了,就是建材王哥p1 娶了不吃香菜p3,然后p1的房产证上写了不吃香菜,王哥净身出户,离婚被分走全部财产。

在 STL 容器中使用unique_ptr,不允许直接赋值

vector<unique_ptr<string>> vec;
unique_ptr<string> p3(new string("I'm P3"));
unique_ptr<string> p4(new string("I'm P4"));

vec.push_back(std::move(p3));
vec.push_back(std::move(p4));

cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;

vec[0] = vec[1];	/* 不允许直接赋值 */
vec[0] = std::move(vec[1]);		// 需要使用move修饰,使得程序员知道后果

cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;

支持对象数组的内存管理

// 会自动调用delete [] 函数去释放内存
unique_ptr<int[]> array(new int[5]);	// 支持这样定义

除了上面ABC三项外,unique_ptr的其余用法都与auto_ptr用法一致。

  1. 构早    

class Test {
public:
	Test() { cout << "Test的构造函数..." << endl; }
	~Test() { cout << "Test的析构函数..." << endl; }

	void doSomething() { cout << "do something......" << endl; }
};


// 自定义一个内存释放其
class DestructTest {
	public:
	void operator()(Test *pt) {
		pt->doSomething();
		delete pt;
	}
};

// unique_ptr<T> up; 空的unique_ptr,可以指向类型为T的对象
unique_ptr<Test> t1;

// unique_ptr<T> up1(new T());	定义unique_ptr,同时指向类型为T的对象
unique_ptr<Test> t2(new Test);

// unique_ptr<T[]> up;	空的unique_ptr,可以指向类型为T[的数组对象
unique_ptr<int[]> t3;

// unique_ptr<T[]> up1(new T[]);	定义unique_ptr,同时指向类型为T的数组对象
unique_ptr<int[]> t4(new int[5]);

// unique_ptr<T, D> up();	空的unique_ptr,接受一个D类型的删除器D,使用D释放内存
unique_ptr<Test, DestructTest> t5;

// unique_ptr<T, D> up(new T());	定义unique_ptr,同时指向类型为T的对象,接受一个D类型的删除器D,使用删除器D来释放内存
unique_ptr<Test, DestructTest> t6(new Test);

2赋值

unique_ptr<Test> t7(new Test);
unique_ptr<Test> t8(new Test);
t7 = std::move(t8);	// 必须使用移动语义,结果,t7的内存释放,t8的内存交给t7管理
t7->doSomething();

3.主动释放对象

unique_ptr<Test> t9(new Test);
t9 = NULL;
t9 = nullptr;
t9.reset();

4.放弃对象的控制权

Test *t10 = t9.release();

5.重置

t9.reset(new Test);

auto_ptr 与 unique_ptr智能指针的内存管理陷阱

auto_ptr<string> p1;
string *str = new string("智能指针的内存管理陷阱");
p1.reset(str);	// p1托管str指针
{
	auto_ptr<string> p2;
	p2.reset(str);	// p2接管str指针时,会先取消p1的托管,然后再对str的托管
}

// 此时p1已经没有托管内容指针了,为NULL,在使用它就会内存报错!
cout << "str:" << *p1 << endl;

4.1.weak_ptr
weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。 同时weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象。

弱指针的使用;
        1weak_ptr wpGirl_1; // 定义空的弱指针
        2weak_ptr wpGirl_2(spGirl); // 使用共享指针构造
        3wpGirl_1 = spGirl; // 允许共享指针赋值给弱指针

弱指针也可以获得引用计数;
wpGirl_1.use_count()

弱指针不支持 * 和 -> 对指针的访问;

        4在必要的使用可以转换成共享指针 lock();

shared_ptr<Girl> sp_girl;
sp_girl = wpGirl_1.lock();

// 使用完之后,再将共享指针置NULL即可
sp_girl = NULL;

使用代码:

        

shared_ptr<Boy> spBoy(new Boy());
shared_ptr<Girl> spGirl(new Girl());

// 弱指针的使用
weak_ptr<Girl> wpGirl_1;			// 定义空的弱指针
weak_ptr<Girl> wpGirl_2(spGirl);	// 使用共享指针构造
wpGirl_1 = spGirl;					// 允许共享指针赋值给弱指针

cout << "spGirl \t use_count = " << spGirl.use_count() << endl;
cout << "wpGirl_1 \t use_count = " << wpGirl_1.use_count() << endl;

	
// 弱指针不支持 * 和 -> 对指针的访问
/*wpGirl_1->setBoyFriend(spBoy);
(*wpGirl_1).setBoyFriend(spBoy);*/

// 在必要的使用可以转换成共享指针
shared_ptr<Girl> sp_girl;
sp_girl = wpGirl_1.lock();

cout << sp_girl.use_count() << endl;
// 使用完之后,再将共享指针置NULL即可
sp_girl = NULL;

六、智能指针的使用陷阱
不要把一个原生指针给多个智能指针管理;

        int *x = new int(10);
        unique_ptr< int > up1(x);
        unique_ptr< int > up2(x);
// 警告! 以上代码使up1 up2指向同一个内存,非常危险。或以下形式:
up1.reset(x);
up2.reset(x);

记得使用u.release()的返回值;
在调用u.release()时是不会释放u所指的内存的,这时返回值就是对这块内存的唯一索引,如果没有使用这个返回值释放内存或是保存起来,这块内存就泄漏了.

禁止delete 智能指针get 函数返回的指针;
如果我们主动释放掉get 函数获得的指针,那么智能 指针内部的指针就变成野指针了,析构时造成重复释放,带来严重后果!

禁止用任何类型智能指针get 函数返回的指针去初始化另外一个智能指针!
shared_ptr< int > sp1(new int(10));
// 一个典型的错误用法 shared_ptr< int > sp4(sp1.get());
 

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐