【C++】C++中的四种个智能指针.md

智能指针的出现的目的就是为了解决c++中出现异常安全资源管理问题,智能指针的实现原理是依据RAII机制(Resource Acquisition Is Initialization),这是的智能指针本质上是一个对象,行为表现上是一个指针。

智能指针都是类模板使用时是必须指定类型参数的,如:auto_ptr<int> ptr

1.auto_ptr

  • auto_ptr采用所有权的模式,指针内不做引用计数,因此一个对象只能由一个auto_ptr指针所拥有,在给其他auto_ptr指针赋值时,所有权会转移
  • auto_ptr中使用delete来释放资源,所以auto_ptr不能指向数组,因为数组的释放使用的是delete[]
  • auto_ptr指针的判空使用auto_ptr.get()==NULL来判断

auto_ptr是C++98中的标准,在C++11中已经被弃用。

auto_ptr由于是所有权模式,所在在auto_ptr做参数时,会转移所有权,即将函数外的auto_ptr的所有权转义给函数内的auto_ptr的所有权,如果函数内不做所有权转移的话,函数外的auto_ptr将变为空指针,而导致引用出错。如:

不转移所有权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void Test(auto_ptr<int> apt)
{
cout << *apt << endl;
return apt;
}

int main()
{
auto_ptr<int> ptr(new int(1));
Test(ptr);
if (ptr.get() == NULL)
cout << "NULL" << endl;
else
cout << "NO NULL" << endl;
system("pause");
return 0;
}

输出结果:

1
2
1
NULL

转移所有权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
auto_ptr<int> Test(auto_ptr<int> apt)
{
cout << *apt << endl;
return apt;
}

int main()
{
auto_ptr<int> ptr(new int(1));
ptr = Test(ptr);
if (ptr.get() == NULL)
cout << "NULL" << endl;
else
cout << "NO NULL" << endl;
system("pause");
return 0;
}

输出结果:

1
2
1
NO NULL

2.unique_ptr

  • unique_ptr是C++11中对C++98中的auto_ptr的替换与强化

  • unique_ptr也是使用独占所有权模式,即一个unique_ptr指针指向一个对象后,不可以在把这个对象赋予给另一个unique_ptr指针,后来出于一些考虑C++又提供了srd::move()函数来做所有权的移交,被移交所有权的unique_ptr指针会指向空,这时再使用这个指针就会报错

  • unique_ptr指针支持直接使用==判空,如:

1
2
3
unique_ptr<int> ptr;
if(ptr == NULL)
cout<<"NULL"<<endl;

当然unique_ptr依旧支持unique_ptr.get()==NULL来判空。

  • 当unique_ptr作为实参进行传递时,必须使用std::move()来移交所有权,这是unique_ptr的独占所有权性质决定的。如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void Test(unique_ptr<int> apt)
{
cout << *apt << endl;
}

int main()
{
unique_ptr<int> ptr(new int(1));
unique_ptr<int> pt = move(ptr);
Test(std::move(ptr));
if (ptr == NULL)
cout << "NULL" << endl;
else
cout << "NO NULL" << endl;
system("pause");
return 0;
}

3.shared_ptr

  • shared_ptr指针支持一个对象被多个指针指向

  • shared_ptr使用计数机制来记录对象被多少个shared_ptr指针所指向,可以使用share_ptr.reset()函数来释放当前指针,对象的引用计数减一,当引用计数减为0时,释放对象资源

  • 可以使用shared_ptr.use_count()来获取当前对象的引用计数

我们来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void Test(shared_ptr<int> ptr)
{
cout << "count_1:"<< ptr.use_count() << endl;
}

int main()
{
shared_ptr<int> ptr(new int(1));
shared_ptr<int> pt = ptr;
Test(ptr);
pt.reset();
cout << "count_2:"<< ptr.use_count() << endl;
if (ptr == NULL)
cout << "NULL" << endl;
else
cout << "NO NULL" << endl;
system("pause");
return 0;
}

输出结果:

1
2
3
count_1:3
count_2:1
NO NULL

咦!为什么引用计数count_1是3呢?这是因为在函数内形参shared_ptr<int> ptr也指向了同一个对象。而当函数Test结束形参指针释放,同时又使用reset()函数释放pt指针,所以count_2:1

4.weak_ptr

虽然shared_ptr使用起来更接近C的原生指针,但是当shared_ptr指针作为类成员时,可能会出现互相引用的而形成死锁,导致引用计数永远无法将为0的现象,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class B;
class A
{
public:
shared_ptr<B> pa;
};
class B
{
public:
shared_ptr<A> pb;
};
int main()
{
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
pa->pa = pb;
pb->pb = pa;
system("pause");
return 0;
}

这种情况是,pa是指向A类型的shared_ptr指针指向A类型对象,而pa指向的内存里面又有一个shared_ptr指针指向B类型对象,同理pb也是如此,这样就导致,当A要被释放时,要先释放B,而B要释放时又要先释放A,如此便形成了一个互相等待的死循环。weak_ptr的存在就是为了解决这种问题。

weak_ptr指针是一种弱引用,它指向对象和释放时不会引起引用计数的变化,这样便可以打破shared_ptr的这种死循环了,我们将上面的代码改成如下,就可以解死循环了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class B;
class A
{
public:
weak_ptr<B> pa;
};
class B
{
public:
shared_ptr<A> pb;
};
int main()
{
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
pa->pa = pb;
pb->pb = pa;
system("pause");
return 0;
}