智能指针(smart pointer)事实上是一个装指针的容器,在析构时会 delete 对象,一定程度上的解决了垃圾回收的问题
头文件:
测试环境:
和名称一样,它能与其他指针共享同一个对象
shared_ptr 与普通指针的用法类似,举个例子
shared_ptr<int> p;
if (p == nullptr) cout << "nullptr\n";
shared_ptr<int> p2 = p;
if (p2 == nullptr) cout << "nullptr\n";
shared_ptr<int> p3(new int(20)); //注意这里 p3 指向了一个动态对象
//shared_ptr p3 = new int(20); //错误,必须使用直接初始化的形式
if (p3 != nullptr) cout << "not null\n";
cout << *p3 << endl; //20
最安全的分配和使用动态内存的方法是调用 make_shared
函数,它在动态内存中分配一个对象并初始化它,返回指向此对象的 shared_ptr
shared_ptr<int> p3 = make_shared<int>(42) //括号内为初始化的值;
shared_ptr<int> p4 = p3;
shared_ptr<int> p5(p3); //拷贝初始化
cout << p3.use_count(); //所指对象被多少个 shared_ptr 所共享
if (p3.unique()) cout << "如果p3.use_count()为1,返回真\n";
//p4 指向一个值为 "99999" 的 string
shared_ptr<string> p6 = make_shared<string>(5, '9');
//指向一个动态分配的空 vector
auto p7 = make_shared<vector<string>>();
shared_ptr 是允许拷贝和赋值操作的(与 unique_ptr 不同),前面我们看到,在 shared_ptr 中有 use_count
这个方法,事实上每个 shared_ptr 都有一个关联的计数器,称为引用计数(reference count),其实就是他们所指的对象到底被多少个 shared_ptr 共享。当发生拷贝初始化、作为参数传递给一个函数、作为函数返回值,计数器都会递增;显而易见,当 shared_ptr 更换指向或者被销毁,这个原对象的计数器都会减少。当 shared_ptr 被销毁时,它析构函数就会递减所指对象的引用计数,若引用计数变为 0,shared_ptr 就会负责销毁这些元素,并释放他们的内存。
class Test {
public:
Test() { cout << "Test()\n"; }
~Test() { cout << "~Test()\n"; }
};
void DisplayParam(shared_ptr<Test> from) { //注意这里是值传递,如果是引用则情况不同
cout << "Display Start\n";
cout << from.use_count() << endl; //2
shared_ptr<Test> p = from;
shared_ptr<Test> p2 = p;
cout << p.use_count() << endl; //4
cout << "Display end\n";
}
int main() {
shared_ptr<Test> p = make_shared<Test>();
DisplayParam(p);
}
// 运行结果
// Test()
// Display Start
// 2
// 4
// Display end
另外一个析构的测试:
void DisplayDelete() {
cout << "Display Start\n";
shared_ptr<Test> p = make_shared<Test>();
shared_ptr<Test> p2 = p;
cout << "Display end\n";
}
// 运行结果
// Display Start
// Test()
// Display end
// ~Test()
这个函数运行结束,内部的局部变量(shared_ptr)离开作用域被销毁,shared_ptr 自动销毁所指向的对象并释放内存(当然这是要在所指对象没有其他引用的情况下)。
shared_ptr 成员函数:
->
,可以直接操作指向对象的可见成员和方法,实现和普通指针一样的操作*
,解引用,可以返回指向的对象,实现和普通指针一样的操作=
,赋值操作,实现和普通指针一样的操作[]
,类似数组的操作,实现和普通指针一样的操作bool
,指针不为 nullptr 返回真,实现和普通指针一样的操作get
,可以获取原生指针(raw pointer)reset
,重置,接受原生指针和 deleter 为实参,不传入时默认 nullptr(reset 后如果原对象没有被其他智能指针引用,原对象会被 delete)swap
,顾名思义,交换指向use_count
,返回当前指向的对象被引用的个数unique
,use_count 为 1 时返回真owner_before
shared_ptr<int> p = make_shared<int>(45);
shared_ptr<int> p2(p);
p2.reset();
if (p2 == nullptr) cout << "nullptr\n";
shared_ptr<int> p3(p);
p3.reset(new int(25));
cout << *p3 << endl; //25
最好不要混合使用普通指针与智能指针
//[1]一种危险的情况
int *x(new int(1024));
shared_ptr<int> foo(x);
cout << foo.use_count() << endl; //1
cout << *x << endl; //1024
foo.reset();
cout << *x << endl; //未定义的数
//[2]不要使用get初始化另一个智能指针或为智能指针赋值
shared_ptr<int> foo(new int(45));
int *p = foo.get();
shared_ptr<int> foo2(p);
cout << foo2.use_count() << endl; //1,但是事实上有2个智能指针指向 new int(45);
foo2.reset(); //显然,这种时候 reset 会 delete 原对象
cout << foo2.use_count() << endl; //0
cout << *foo << endl; //未定义的数,foo 已变成空悬指针,foo 他晕了,它指向的对象没了,但它并不清楚
cout << foo.use_count(); //1,foo 还傻傻认为自己引用到正确的对象了
和名称一样,它不与其他指针共享同一个对象,拥有对对象的唯一管理权。
Reference: unique_ptr
初始化 unique_ptr 只能采用直接初始化的形式
unique_ptr<int> u(new int(40));
//unique_ptr u2(u); //错误,不可拷贝构造
//unique_ptr u3;
//u3 = u; //错误,不可赋值
unique_ptr 不支持普通的拷贝和赋值操作,其他成员函数:
->
,可以直接操作指向对象的可见成员和方法,实现和普通指针一样的操作*
,解引用,可以返回指向的对象,实现和普通指针一样的操作=
,不支持赋值,但有其他用途(移动赋值操作)[]
,类似数组的操作,实现和普通指针一样的操作bool
,指针不为 nullptr 返回真,实现和普通指针一样的操作get
,可以获取原生指针(raw pointer)reset
,重置,接受原生指针和 deleter 为实参,不传入时默认 nullptrrelease
,放弃管理权,内部指针赋 nullptr,不会释放所指对象的内存,最后返回指针。swap
,顾名思义,交换管理权//u.release(); //编译可通过,但我们丢失了指针,这是严重的错误
auto p = u.release();
刚刚提到,没办法拷贝构造和赋值,那 unique_ptr 不就不能作为返回值,也不能值传递了吗?事实上有两种比较特殊的方法,移动拷贝构造与移动赋值。这里就不深入了,这个“移动”可以就字面意思简单地理解为——“窃取”了原变量的堆内存空间,节省了开辟空间的代价。
unique_ptr<int> u(new int(40));
unique_ptr<int> u2 = std::move(u); //移动赋值
cout << *u2 << endl;
if(u == nullptr) cout << "nullptr\n";
//结果而言上面的操作和 unique_ptr u2(u.release()); 一样
Weak shared pointer,是一种不控制所指对象生命周期的智能指针,它指向由一个 shared_ptr 管理的对象。将一个 weak_ptr 绑定到一个 shared_ptr 不会改变 shared_ptr 的引用计数。一旦最后一个指向对象的 shared_ptr 被销毁,对象内存被释放,即便 weak_ptr 还指着它。(所以他很弱)
Reference: weak_ptr,提供的成员函数:
=
,可接受 shared_ptr 与 weak_ptr 类型,赋值后共享对象reset
,置空(只有这一种,和其他的智能指针不同)use_count
,返回与之共享对象的 shared_ptr 的数量expired
,若 use_count 为 0 返回真lock
,如果 expired 为真,返回空的 shared_ptr,否则返回指向同对象的 shared_ptrowner_before
weak_ptr 可以用来检测对象是否还存在,可以阻止用户访问一个不再存在的对象。
@deprecated 被 unique_ptr 取代
Reference: auto_ptr
auto_ptr<string> ap;
auto_ptr<string> ap(new string("auto_ptr"));
->
,可以直接操作指向对象的可见成员和方法,这点和普通指针一样*
,可以返回指向的对象=
,完全夺取对象的管理权,被夺取的智能指针指向赋 nullptrget
,可以获取原生指针(raw pointer)reset
,重置,释放当前管理的内存,管理新传入的对象(默认为 nullptr)release
,放弃管理权,内部指针赋 nullptr,不会释放所指对象的内存,最后返回所指对象地址。C++ Primer 中提到了可能会出现的三种情况:
1.忘记delete内存。忘记释放动态内存会导致人们常说的“内存泄露”问题,值得注意的是查找内存泄露错误是非常困难的,因为通常程序运行了很久之后,真正耗尽内存时,才能检测到这种错误。忘记释放动态内存会导致人们常说的“内存泄露”问题,值得注意的是查找内存泄露错误是非常困难的,因为通常程序运行了很久之后,真正耗尽内存时,才能检测到这种错误。
2.使用已经释放掉的对象。释放内存后将指针置为空,有事可以检测出这种错误。通过释放内存后将指针置为空,有事可以检测出这种错误。
3.同一块内存释放两次。当有两个指针指向相同的动态分配对象时,可能发生这种错误。如果对其中一个指针进行了 delete 操作,对象的内存已经归还给自由空间了,如果再次 delete 第二个指针,自由空间就可能被破坏。
原书作者建议使用智能指针,就能避免上述情况。
[1] 动态内存与智能指针 -《C++ Primer(第5版)中文版》 p400