c++关键字__三类智能指针
shared_ptr
shared_ptr允许多个该智能指针共享的 “拥有” 同一堆分配对象的内存, 这通过引用计数(reference counting) 实现, 会记录有多少个shared_ptr共同指向一个对象, 一旦最后一个这样的指针被销毁, 也就是一旦某个对象的引用计数变为0, 这个对象会被自动删除.
void mytest()
{
std::shared_ptr<int> sp1(new int(22));
std::shared_ptr<int> sp2 = sp1;
std::cout << "cout: " << sp2.use_count() << std::endl; // 打印引用计数
std::cout << *sp1 << std::endl;
std::cout << *sp2 << std::endl;
sp1.reset(); // 显示让引用计数减一
std::cout << "count: " << sp2.use_count() << std::endl; // 打印引用计数
std::cout << *sp2 << std::endl; // 22
return;
}
多个shared_ptr指向同一个对象时候, 这些shared_ptr指针指向的数据的地址是相同的, 并且这些shared_ptr实例其实都是同一个, 即一个单例. 只是这个实例中保存了引用计数个数. 可以通过获取shared_ptr自身地址可知只有一个shared_ptr实例对象.
weak_ptr
weak_ptr是为配合shared_ptr而引入的一种智能指针, 来协助shared_ptr工作, 它可以从一个shared_ptr或另一个weak_ptr对象构造, 它的构造和析构不会引起引用计数的增加或减少. 没有重载 * 和 -> 但可以使用lock()接口获得一个可用的shared_ptr对象.
weak_ptr的使用更为复杂一点, 它可以指向shared_ptr指针指向的对象内存, 却并不拥有该内存, 而使用weak_ptr成员lock(), 则可返回其指向内存的一个share_ptr对象, 且在所指对象内存已经无效时, 返回指针空值nullptr.
注意:weak_ptr并不拥有资源的所有权, 所以不能直接使用资源. 可以从一个weak_ptr构造一个shared_ptr以取得共享资源的所有权.
void check(std::weak_ptr<int> &wp)
{
std::shared_ptr<int> sp = wp.lock(); // 转换为shared_ptr<int>
if (sp != nullptr)
{
std::cout << "still: " << *sp << std::endl;
}
else
{
std::cout << "still: " << "pointer is invalid" << std::endl;
}
}
void mytest()
{
std::shared_ptr<int> sp1(new int(22));
std::shared_ptr<int> sp2 = sp1;
std::weak_ptr<int> wp = sp1; // 指向shared_ptr<int>所指对象
std::cout << "count: " << wp.use_count() << std::endl; // count: 2
std::cout << *sp1 << std::endl; // 22
std::cout << *sp2 << std::endl; // 22
check(wp); // still: 22
sp1.reset();
std::cout << "count: " << wp.use_count() << std::endl; // count: 1
std::cout << *sp2 << std::endl; // 22
check(wp); // still: 22
sp2.reset();
std::cout << "count: " << wp.use_count() << std::endl; // count: 0
check(wp); // still: pointer is invalid
return;
}
wark_ptr的深入研究
我们知道shared_ptr是采用引用计数的智能指针, 多个shared_ptr实例可以指向同一个动态对象, 并维护了一个共享的引用计数器. 对于引用计数法实现的计数, 总是避免不了循环引用(或环形引用)的问题, shared_ptr也不例外.
#include <memory>
using namespace std;
class Class_B;
class Class_A {
public:
Class_A() { cout << "construct Class_A..." << endl; }
~Class_A() { cout << "destruct Class_A..." << endl; }
shared_ptr<Class_B> pb_; //在A中引用B
};
class Class_B {
public:
Class_B() { cout << "construct Class_B..." << endl; }
~Class_B() { cout << "destruct Class_B..." << endl; }
shared_ptr<Class_A> pa_; //在B中引用A
};
int main() {
shared_ptr<Class_A> spa = make_shared<Class_A>();
shared_ptr<Class_B> spb = make_shared<Class_B>();
spa->pb_ = spb;
spb->pa_ = spa;
}
//////////////////// 输出以下:
construct Class_A...
construct Class_B...
从上面代码中, ClassA和ClassB间存在着循环引用, 从运行结果中我们可以看到:当main函数运行结束后, spa和spb管理的动态资源并没有得到释放, 产生了内存泄露.
weak_ptr是为了配合shared_ptr而引入的一种智能指针, 它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期, 也就是将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数. 不论是否有weak_ptr指向, 一旦最后一个指向对象的shared_ptr被销毁, 对象就会被释放. 从这个角度看, weak_ptr更像是shared_ptr的一个助手而不是智能指针.
weak_ptr创建
当我们创建一个weak_ptr时, 需要用一个shared_ptr实例来初始化weak_ptr, 由于是弱共享, weak_ptr的创建并不会影响shared_ptr的引用计数值.
int main() {
shared_ptr<int> sp(new int(5)); //此时 sp.use_count()==1
weak_ptr<int> wp(sp); //此时 sp.use_count()==1
}
判断weak_ptr指向对象是否存在
既然weak_ptr并不改变其所共享的shared_ptr实例的引用计数, 那就可能存在weak_ptr指向的对象被释放掉这种情况. 这时, 我们就不能使用weak_ptr直接访问对象. 那么我们如何判断weak_ptr指向对象是否存在呢?C++中提供了lock函数来实现该功能. 如果对象存在, lock()函数返回一个指向共享对象的shared_ptr, 否则返回一个空shared_ptr.
class Class_A {
public:
Class_A() { cout << "construct Class_A..." << endl; }
~Class_A() { cout << "destruct Class_A..." << endl; }
int a;
};
int main() {
shared_ptr<Class_A> sp(new Class_A());
weak_ptr<Class_A> wp(sp);
//sp.reset();
if (shared_ptr<Class_A> pa = wp.lock()) {
cout << "get, num:" << pa->a <<endl;
}else {
cout << "wp point to nil" <<endl;
}
}
试试把sp.reset()这行的注释去掉看看结果有什么不同. 除此之外, weak_ptr还提供了expired()函数来判断所指对象是否已经被销毁.
class A
{
public:
A() : a(3) { cout << "A Constructor..." << endl; }
~A() { cout << "A Destructor..." << endl; }
int a;
};
int main() {
shared_ptr<A> sp(new A());
weak_ptr<A> wp(sp);
sp.reset(); // 此时sp被销毁
cout << wp.expired() << endl; // true表示已被销毁, 否则为false
}
/////////////// 输出:
A Constructor...
A Destructor...
1
weak_ptr并没有重载operator->和operator * 操作符, 因此不可直接通过 weak_ptr使用对象, 典型的用法是调用其lock函数来获得shared_ptr示例, 进而访问原始对象. 最后, 我们来看看如何使用weak_ptr来改造最前面的代码, 打破循环引用问题.
class ClassB;
class ClassA
{
public:
ClassA() { cout << "ClassA Constructor..." << endl; }
~ClassA() { cout << "ClassA Destructor..." << endl; }
weak_ptr<ClassB> pb; // 在A中引用B
};
class ClassB
{
public:
ClassB() { cout << "ClassB Constructor..." << endl; }
~ClassB() { cout << "ClassB Destructor..." << endl; }
weak_ptr<ClassA> pa; // 在B中引用A
};
int main() {
shared_ptr<ClassA> spa = make_shared<ClassA>();
shared_ptr<ClassB> spb = make_shared<ClassB>();
spa->pb = spb;
spb->pa = spa;
// 函数结束, 思考一下:spa和spb会释放资源么?
}
//////////////输出:
ClassA Constructor...
ClassB Constructor...
ClassA Destructor...
ClassB Destructor...
Program ended with exit code: 0
unique_ptr
unique_ptr持有对象的独有权, 同一时刻只能有一个unique_ptr指向给定对象 (通过禁止拷贝语义、只有移动语义来实现). unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始, 直到离开作用域.
离开作用域时, 若其指向对象, 则将其所指对象销毁(默认使用delete操作符, 用户可指定其他操作).
void mytest()
{
std::unique_ptr<int> up1(new int(11)); // 无法复制的unique_ptr
//unique_ptr<int> up2 = up1; // err, 不能通过编译
std::cout << *up1 << std::endl; // 11
std::unique_ptr<int> up3 = std::move(up1); // 现在p3是数据的唯一的unique_ptr
std::cout << *up3 << std::endl; // 11
//std::cout << *up1 << std::endl; // err, 运行时错误
up3.reset(); // 显式释放内存
up1.reset(); // 不会导致运行时错误
//std::cout << *up3 << std::endl; // err, 运行时错误
std::unique_ptr<int> up4(new int(22)); // 无法复制的unique_ptr
up4.reset(new int(44)); //"绑定"动态对象
std::cout << *up4 << std::endl; // 44
up4 = nullptr;//显式销毁所指对象, 同时智能指针变为空指针. 与up4.reset()等价
std::unique_ptr<int> up5(new int(55));
int *p = up5.release(); //只是释放控制权, 不会释放内存
std::cout << *p << std::endl;
//cout << *up5 << endl; // err, 运行时错误
delete p; //释放堆区资源
return;
}
unique_ptr.release(): 只是释放控制权, 不会释放内存 unique_ptr.reset(): 重新绑定动态对象
使用unique_ptr + raii自动释放对象
struct D {
void operation() (Foo* fo) {
std::cout << "handle something && D operator() " << std::endl;
delete fo;
}
}
void TestA() {
{
std::unique_ptr<Foo, D> p1(new Foo);
...
} // 当 p1 生命期结束时候自动调用自定义deleter, 执行析构
}