目录
由普通指针管理的动态内存在被显式释放前一直会存在
不要混合使用普通指针和智能指针
值传递时,实参会被拷贝,会递增其引用计数,局部变量被释放后,计数减一
不要使用get初始化另一个智能指针或为智能指针赋值
unique_ptr类
unique_ptr操作:release和reset
unique_ptr作为函数参数和返回值
weak_ptr类
常用函数
使用智能指针的目的:为了更容易更安全的使用动态内存
智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。
新标准库提供的两个智能指针shared_ptr和unique_ptr的区别在于管理底层指针的方式:
智能指针也是模板,所以在创建一个智能指针时,必须提供指针指向的类型:
shared_ptr p1;
shared_ptr > p2;
- shared_ptr
sp 、unique_ptr 空智能指针,可以指向类型为T的对象up - p 将p用作一个条件判断,若p指向一个对象,则为true
- *p 解引用p,获得它指向的对象
- p->mem 等价于(*p).men
- p.get() 返回p中保存的指针,若智能指针释放了其对象,返回的指针所指向的对象也就消失了
- swap(p,q) 交换p和q中指针 ,p.swap(q)
- make_shared
(args) 返回一个shared_ptr,指向一个动态分配的类型为T的对象。使用args初始化此对象。- shared_ptr
p(q) p是shared_ptr q的拷贝:此操作会递增q中的计数器。q中的指针必须能转换为T*- p=q p和q都是shared_ptr,所保存的指针必须能相互转换。此操作会递减p的引用计数,递增q的引用计数。若p的引用计数变为0,则将其管理的原内存释放
- p.unique() 若p.use_count()为1,返回true,否则返回false
- p.use_count() 返回与p共享对象的智能指针数量:可能很慢,主要用于调试
最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数
//指向一个值为666的int的shared_ptr
shared_ptr p1 = make_shared(666);
//指向一个值为“999999999”的string
shared_ptr p2 = make_shared(10,'9');
//指向一个值初始化的int,即值为0;
shared_ptr p3 = make_shared();
cout << *p1 << endl;//666
cout << *p2 << endl;//9999999999
cout << *p3 << endl;//0
类似顺序容器的emplace成员,make_shared用其参数来构造给定类型的对象。例如调用make_shared
当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象。其析构函数会递减它所指向对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。
void use1(T arg){
shared_ptr p = factory(arg);
//使用p
}//p离开了作用域,它指向的内存会被自动释放掉
由于p是use1的局部变量,在use1结束时它将被销毁,当p被销毁时,将递减其引用计数并检查它是否为0。在此例中,p是唯一引用factory返回的内存的对象。
但是有其他shared_ptr也指向这块内存,她就不会被释放掉:
void use1(T arg){
shared_ptr p = factory(arg);
//使用p
return p;//当我们返回p时,引用计数进行了递增操作
}//p离开了作用域,它指向的内存不会被自动释放掉
void use2(T arg){
Foo* p = factory(arg);
//使用p,但不delete它
}//p离开了作用域,它指向的内存不会被自动释放掉
接受指针参数的智能指针构造函数是explicit的,因此我们不能将一个内置指针隐式转换为一个智能指针,必须用直接初始化的方式来初始化一个智能指针:
shared_ptr p1 = new int(1024); //错误
shared_ptr p2(new int(1024)); //正确
shared_ptr clone(int p){
return new int(p); //错误
}
默认情况下,一个用来初始化智能指针的普通指针必须指向动态内存。
shared_ptr可以协助对象的析构,但这仅限于自身的拷贝之间。
推荐使用make_shared而不是new的原因:
再分配对象的同时就将shared_ptr与之绑定,从而避免无意中将同一块内存绑定到多个独立创建的shared_ptr上
int * a = new int(999);
shared_ptr p1(a);
shared_ptr p2(a);
cout << *p1 << endl;//999
cout << p1.use_count() << endl;//1
cout << *p2 << endl;//999
cout << p2.use_count() << endl;//1
//会发生内存泄漏,因为两个独立的shared_ptr同时指向了一个内存空间,该内存会被释放两次
正确做法:使用另一个智能指针初始化其他智能指针
int * a = new int(999);
shared_ptr p1(a);
shared_ptr p2(p1);//使用p1初始化p2,两个指针的计数器都为2,安全
cout << *p1 << endl;//999
cout << p1.use_count() << endl;//2
cout << *p2 << endl;//999
cout << p2.use_count() << endl;//2
void test(shared_ptr p) {
++*p;
cout <<"值传递时,引用计数加一:"<< p.use_count() << endl;//2
}
int main(void) {
int * a = new int(999);
shared_ptr p1(a);
cout << *p1 << endl;//999
cout << p1.use_count() << endl;//2
test(p1);
cout << *p1 << endl;//1000
cout << p1.use_count() << endl;//1
return 0;
}
当一个shared_ptr绑定到一个普通指针时,我们就将内存的管理交给了这个shared_ptr,就不应该再使用普通指针来访问shared_ptr所指向的内存。
void test(shared_ptr p) {
++*p;
cout <<"引用计数为1:"<< p.use_count() << endl;//1
}
int main(void) {
int *x(new int(1024));//x 是一个普通指针,不是智能指针
//test(x); 错误,不能将int*转换为一个shared_ptr
test(shared_ptr(x));//合法,但离开函数体后内存会被释放,里面的引用计数为1
cout << *x << endl;//此时x是个悬空指针,其值未定义
return 0;
}
get()函数返回一个内置指针,指向智能指针管理的对象。使用场景:我们需要向不能使用智能指针的代码传递一个内置指针。使用get返回的指针的代码不能delete指针。
使用reset来将一个新的指针赋予一个shared_ptr:
p = new int(1024); //错误:不能将一个指针直接赋值给shared_ptr
p.reset(new int(1024)); //正确:p指向一个新对象
与赋值类似,reset会更新引用计数,如果需要的话,会释放p指向的对象。
智能指针使用总结:
一个unique_ptr”拥有“它所指向的对象。与shared_ptr不同,某时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁。
与shared_ptr不同,没有类似make_shared的标准库函数返回一个unique_ptr。当我们定义一个unique_ptr时,需要将其绑定到一个new返回的指针上。类似shared_ptr,初始化unique_ptr必须采用直接初始化的形式:
unique_ptr p1(new string("hello MTK"));
//unique_ptr p2(p1);//错误:unique_ptr不支持拷贝
unique_ptr p3;
//p3 = p1;//错误:unique_ptr不支持赋值
- unique_ptr
u1 空unique_ptr,可以指向类型为T的对象。u1会使用delete来释放它的指针;- unique_ptr
u2 使用一个类型为D的可调用对象来释放它的指针- unique_ptr
u(d) 空unique_ptr,指向类型为T的对象,用类型为D的对象d代替delete- u=nullptr 释放u指向的对象,将u置为空
- u.release() u放弃对指针的控制权,返回指针,并将u置为空
- u.reset() 释放u指向的对象
- u.reset(q) 如果提供了内置指针q,令u指向这个对象;否则将u置为空
- u.reset(nullptr)
虽然我们不能拷贝或赋值unique_ptr,但可以通过release或reset将指针的所有权从一个(非const)unique_ptr转移给另一个unique_ptr:
unique_ptr p1(new string("hello MTK"));
cout << *p1 << endl;
//将所有权从p1转移给p2
unique_ptr p2(p1.release());//release将p1置为空
unique_ptr p3(new string("hello world"));
//将所有权从p3转移给p2
p2.reset(p3.release());//reset释放了p2原来指向的内存
release成员返回unique_ptr当前保存的指针并将其置为空。因此,p2被初始化为p1原来保存的指针,而p1被置为空。
reset成员接受一个可选的指针参数,令unique_ptr重新指向给定的指针。如果unique_ptr不为空,它原来指向的对象被释放。因此,对p2调用reset释放了用“hello MTK”初始化的string所使用的内存,将p3对指针的所有权转移给p2,并将p3置为空。
调用release会切断unique_ptr和它原来管理的对象间的联系。release返回的指针通常用来初始化另一个智能指针或给另一个智能指针赋值。如果我们不用另一个智能指针来保存release返回的指针,我们的程序就要负责资源的释放:
p2.release(); //错误,p2不会释放内存,而且我们丢失了指针
auto p = p2.release(); //正确,当时我们必须记得delete(p)
不能拷贝unique_ptr的规则有个例外:我可以拷贝或赋值一个将要被销毁的unique_ptr,比如作为函数参数或返回值:
unique_ptr clone1(int p) {
//正确:从int*创建一个unique_ptr
return unique_ptr(new int(p));
}
unique_ptr clone2(int p) {
//正确:从int*创建一个unique_ptr
unique_ptr ret(new int(p));
return ret;
}
weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个由shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr,不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。它是一种弱引用。
- weak_ptr
w 空weak_ptr可以指向类型为T的对象- weak_ptr
w(sp) 与shared_ptr sp 指向相同对象的weak_ptr。T必须能转换为sp指向的类型- w = p ,p可以是一个shared_ptr或者weak_ptr,赋值后w与p共享对象
- w.reset() 将w置为空
- w.use_count() 与w共享对象的的shared_ptr的数量
- w.expired() 若w.use_count()为0,返回true,否则返回false
- w.lock() 如果expired为true,返回一个空shared_ptr;否则返回一个指向w的对象的shared_ptr
当我们创建一个weak_ptr时,要用一个shared_ptr来初始化它:
auto p = make_shared(42);
weak_ptr wp(p); //wp弱共享p;p的引用计数不会改变
本例中,wp和p指向相同的对象,由于是弱共享,创建wp不会改变p的引用计数;wp指向的对象可能被释放掉。
由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock。此函数检查weak_ptr指向的对象是否存在。如果存在,lock返回一个指向共享对象的shared_ptr。与任何其他shared_ptr类似,只要此shared_ptr存在,它所指向的底层对象也就会一直存在。
shared_ptr p1(new int(666));
weak_ptr wp(p1);
cout << wp.use_count() << endl;//1
if (shared_ptr sp = wp.lock()) {
(*sp)++;
cout << sp.use_count() << endl;//2
cout << p1.use_count() << endl;//2
}
class B;
class A
{
public:
shared_ptr pb_;
~A()
{
cout << "析构A" << endl;
}
};
class B
{
public:
shared_ptr pa_;
~B()
{
cout << "析构B" << endl;
}
};
void testShared_ptr() {
shared_ptr pb(new B);
shared_ptr pa(new A);
pb->pa_ = pa;
pa->pb_ = pb;
cout << "pb 的引用计数:"<< pb.use_count() << endl;
cout << "pa 的引用计数:"<< pa.use_count() << endl;
}
int main(void) {
//pb 的引用计数:2
//pa 的引用计数:2
// 且未调用析构函数,说明两块内存一直没被释放
testShared_ptr();
//跳出函数后,二者的引用计数减一,但还是1,不为0,所以内存得不到释放
}
如上程序,会造成shared_ptr间的循环引用问题,A类中有shared_ptr 指针,B类中有shared_ptr指针。现创建A类和B类对象,并使用shared_ptr,pb,pa指向它们。同时使A对象和B对象的成员变量--各自的shared_ptr指针pa_,pb_指向对方。这就造成了循环引用:
有两个内存块,但是有四个shared_ptr指针,两个shared_ptr指向同一块内存空间:
使得其该内存块的引用计数为2。当跳出函数时,局部变量pb,pa被销毁,所以引用计数减一,但引用计数仍为1,不为0,所以A对象和B对象的内存得不到释放(其析构函数没有被调用)。
解决办法:使用weak_ptr
class B;
class A
{
public:
weak_ptr pb_;
~A()
{
cout << "析构A" << endl;
}
};
class B
{
public:
shared_ptr pa_;
~B()
{
cout << "析构B" << endl;
}
};
void testShared_ptr() {
shared_ptr pb(new B);
shared_ptr pa(new A);
pb->pa_ = pa;
pa->pb_ = pb;//shared_ptr可以直接给weak_ptr赋值
cout << "pb 的引用计数:"<< pb.use_count() << endl;
cout << "pa 的引用计数:"<< pa.use_count() << endl;
}
int main(void) {
// pb 的引用计数:1
// pa 的引用计数:2
// 析构B
// 析构A
testShared_ptr();
}
运行过程解析:
- 使用weak_ptr指向shared_ptr指向的内存,不会使shared_ptr内的引用计数加一(弱引用),所以指向B对象内存的引用计数为1,指向A对象的引用计数仍为2.
- 跳出函数后,局部变量pb和pa被析构,其指向的内存引用计数都要减一,所以指向B对象的引用计数为0,指向A对象的引用计数为1。此时将析构B对象,所以先调用了B的析构函数。
- B对象被析构,其内部的pa_成员也将被析构,导致指向A对象的引用计数减一,为0。
- A对象的引用计数为0,析构A对象。所以最后调用A的析构函数。
参考书籍:
《C++ Primer 第五版》