# 1.背景
在C++中,动态内存的管理使用的是new
和delete
运算符,手动的分配和释放堆上的内存。
动态内存的使用很容易出问题,因为确保在正确的时候释放内存是极其困难的。
为更容易也更安全的使用动态内存,C++11
标准库中提供了两种智能指针shared_ptr
和unique_ptr
。智能指针与常规指针行为类似,但两者的重要区别是智能指针能自动释放他所指向的内存。两种智能指针的主要区别在于管理底层指针的方式,shared_ptr
允许多个指针指向同一个对象;unique_ptr
则“独占"所指向的对象。
类似于vector
等容器,标准库中提供的两种智能指针也都是模板。因此,在创建智能指针的时候,必须提供指针可以指向的类型。
#include <memory>
shared_ptr<int> p1;
unique_ptr<int> p2;
智能指针包含在头文件memory
中,如上默认初始化的智能指针中保存着一个空指针。
p.get()
返回p
中保存的指针。要小心使用,若智能指针释放了对象,get
返回的指针所指向的对象就消失了。
# 2.shared_ptr
shared_ptr
正如其名,允许多个指针指向同一个对象;其采用引用计数的方式来管理内存的释放,当其检查到指针的引用数为0
时会自动释放对象所分配的内存。
# 2.1创建shared_ptr并初始化
- 使用
make_shared
函数
最安全的分配和使用动态内存的方式是调用make_shared
的标准库函数。此函数在动态内存中分配一个对象并初始化它,返回指向对象的shared_ptr
。
shared_ptr<int> p1 = make_shared<int>(4);
- 结合new来使用
可以使用new
返回的指针来初始化智能指针。
shared_ptr<int> p1(new int(2));
std::cout << *p1 << std::endl;
接受指针参数的智能指针构造函数是explicit
的,因此,不能将一个内置指针隐式转换为一个智能指针,必须使用直接初始化形式。
shared_ptr<int> p1 = new int(2); // error
shared_ptr<int> p1(new int(2)); // right
不推荐混合使用普通指针和智能指针,使用make_shared
能在分配对象的同时就将shared_ptr
与之绑定,从而避免了无意中将同一块内存绑定到多个独立创建的shared_ptr
上。
看两个例子:
内存被释放两次:
int *p = new int(2);
shared_ptr<int> p1(p);
std::cout << *p1 << std::endl;
shared_ptr<int> p2(p);
std::cout << *p2 << std::endl;
// free(): double free detected in tcache 2
// Aborted (core dumped)
内存被意外释放:
#include<iosteam>
#include<memory>
using namespace std;
void log(shared_ptr<int> p)
{
} // `shared_ptr`按值传递,离开作用域,内存被销毁
int main(int argc, char **argv) {
int *p = new int(2);
log(shared_ptr<int>(p));
cout << *p << endl; // p所指向的内存已被销毁,0
}
# 2.2不要使用‵get`初始化另一个智能指针
get
返回了指向智能指针管理对象的内置指针,提供这个函数,主要是为了向不能使用智能指针的代码中传递一个内置指针。
不能delete get
返回的指针。
但同样使用get
返回的指针给其他智能指针赋值,有可能会导致内存的意外释放。
# 2.3shared_ptr计数器增减
每个shared_ptr
都有一个关联的计数器,通常称为引用计数器,无论何时拷贝一个shared_ptr
,或使用shared_ptr
赋值时,计数器都会递增,被赋值的shared_ptr
的计数器会减1,当计数器小于1时,shared_ptr
中所指向的内存会被释放。如下:
#include <memory>
#include <iostream>
using namespace std;
int main(int argc, char **argv)
{
auto p1 = make_shared<int>(1);
auto q(p1);
cout << p1.use_count() << endl;
cout << q.use_count() << endl;
auto r = make_shared<int>(2);
auto r1 = r;
cout << r.use_count() << endl;
cout << r1.use_count() << endl;
r = q;
cout << r.use_count() << endl;
cout << r1.use_count() << endl;
cout << q.use_count() << endl;
cout << p1.use_count() << endl;
return 0;
}
// 2
// 2
// 2
// 2
// 3
// 1
// 3
// 3
# 2.4shared_ptr与多线程
shared_ptr
的引用计数本身是安全无锁的,但对象的读写不是,shared_ptr
有两个数据成员,读写操作不能原子化。shared_ptr
的线程安全级别和内建类型/标准容器/string等一样,如果要从多个线程读写同一个shared_ptr
对象,需要加锁。
# 3.unique_ptr
与shared_ptr
不同,某个时刻只能有一个unique_ptr
指向一个给定对象。当unique_ptr
被销毁时,它所指向的对象也被销毁。
有个小插曲,‵C++11‵标准中没有提供类似make_shared
的函数,到C++14
中才给出make_unqiue
函数用来创建unique_ptr
类型的对象,据说是因为C++11
的标准制定者忘了*_*
。
test.cpp:88:14: note: ‘std::make_unique’ is only available from C++14 onwards
test.cpp:88:26: error: expected primary-expression before ‘int’
unique_ptr
拥有它指向的对象,unique_ptr
不支持普通的赋值和复制操作。
auto q = make_unique<int>(10);
auto p = q; // error
unique_ptr<int> uq(q); // error
可以通过.release/.reset
函数将指针所有权从一个unique_ptr
转移给另一个unique_ptr
.
auto q = make_unique<int>(10);
unique_ptr<int> p(q.release()); // 释放q的所有权并交给p,类似复制
auto q1 = make_unique<int>(20);
p.reset(q1.release()); // 释放p原来指向的内存并将q1的所有权给p,类似赋值
cout << *p << endl;
cout << *q << endl; // q已为空,error
例外:可以拷贝或赋值一个将要被销毁的unique_ptr
// 复制
unique_ptr<int> clone(int p)
{
return unique_ptr<int>(new int(p));
}
// 局部变量
unique_ptr<int> clone(int p)
{
unique_ptr<int> q(new int(p));
return q;
}
# 4.weak_ptr
weak_ptr
是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr
所指向的对象。
weak_ptr
绑定到一个shared_ptr
不会改变shared_ptr
的引用计数。
auto q = make_shared<int> (10);
cout << q.use_count() << endl;
weak_ptr<int> wq(q);
cout << q.use_count() << endl;
// 1
// 1
一旦最后一个指向对象的shared_ptr
被销毁,对象的内存就会被释放,即使有weak_ptr
指向对象,对象也还是会被释放,这也正是weak_ptr
这种智能指针“弱”共享对象的特点。
由于对象可能不存在,不能使用weak_ptr
直接访问对象,而必须调用weak_ptr
的lock
函数。lock
函数检查weak_ptr
指向的对象是否存在。如果存在,lock
返回一个指向共享对象的shared_ptr
。
auto q = make_shared<int> (10);
cout << q.use_count() << endl;
weak_ptr<int> wq(q);
if(shared_ptr<int> sp = wq.lock())
cout << *sp << endl;
cout << q.use_count() << endl;
// 1
// 10
// 1
避免shared_ptr
出现相互引用,导致对象无法析构,内存无法释放的问题
#include <iostream>
#include <memory>
// 前置声明
class A;
class B
{
public:
~B()
{
std::cout << "B destructor\n";
}
std::shared_ptr<A> ptr;
};
class A
{
public:
~A()
{
std::cout << "A destructor\n";
}
std::shared_ptr<B> ptr;
};
int main()
{
{ // 作用域开始
std::shared_ptr<A> aPtr = std::make_shared<A>();
std::shared_ptr<B> bPtr = std::make_shared<B>();
aPtr->ptr = bPtr;
bPtr->ptr = aPtr;
} // 作用域结束
std::cout << "end\n";
return 0;
}
创建aPtr和bPtr时,各自的引用计数会变成1,接着2个赋值语句,又把各自的引用计数加了1,就都变成了2,然后离开作用域,会减1,这样各自的引用计数还保持1,这样就无法释放内存空间,就不会去执行A和B的析构函数。而weak_ptr不增加引用计数,只需要将A/B类中的shared_ptr
替换成weak_ptr
即可。2 (opens new window)
# 参考资料
- [1.Primer c++ 第5版.pdf](Primer c++ 第5版.pdf)
- 2.https://blog.csdn.net/whahu1989/article/details/122443129 (opens new window)