共享指针与唯一指针例子#
#ifdef LY_EP105
#include <iostream>
#include <memory>
class Entity {
public:
Entity() { std::cout << "Entity Created" << std::endl; }
~Entity() { std::cout << "Entity Destroyed" << std::endl; }
};
int main() {
// --- unique_ptr 示例 ---
{
std::unique_ptr<Entity> e1 = std::make_unique<Entity>();
// std::unique_ptr<Entity> e2 = e1; // 错误!禁止复制
std::unique_ptr<Entity> e2 = std::move(e1); // 允许移动所有权,e1 现在为空
} // e2 离开作用域,Entity 在这里被销毁
std::cout << "-----------------" << std::endl;
{
// --- shared_ptr 示例 ---
std::shared_ptr<Entity> sharedOuter;
{
//std::shared_ptr对象实例内部有一个引用计数,简单记录有多少个共享指针指向内部Entity对象的实例
std::shared_ptr<Entity> sharedInner = std::make_shared<Entity>();
sharedOuter = sharedInner; // 允许复制(拷贝赋值函数),引用计数变为 2
std::cout << "Inner scope ending1..." << std::endl;
std::shared_ptr<Entity> sharedInner1 = std::move(sharedOuter);//这里把sharedInner的所有权移走了,计数减一,但是本身又导致计数加一,所以目前是2
std::cout << "Inner scope ending2..." << std::endl;
} // sharedInner1 离开作用域,sharedOuter所有权被移走,所以没有任何共享指针指向Entity了, Entity 被销毁
std::cout << "Outer scope still holds Entity?" << std::endl;
} //作用域结束,sharedOuter 离开作用域,Entity 最终在这里被销毁
std::cin.get();
}
/*
Entity Created
Entity Destroyed
-----------------
Entity Created
Inner scope ending1...
Inner scope ending2...
Entity Destroyed
Outer scope still holds Entity?
*/
#endif共享指针、强引用,能防止对象销毁,也被称作具名引用。即他们对一个对象各自都拥有所有权
共享指针例子#
共享指针例子#
#ifdef LY_EP105
#include <iostream>
#include <memory>
struct Object
{
~Object()
{
std::cout << "object destroyed!" << std::endl;
}
};
struct Manager
{
std::shared_ptr<Object> shareObj2;
};
int main() {
{
Manager manager;
{
std::shared_ptr<Object> shareObj1 = std::make_shared<Object>();
manager.shareObj2 = shareObj1;
}//代码块结束时,shareObj1内部的Object实例对象并没有销毁
std::cout << "obj still alive?" << std::endl;
}
//now obj still die,打印 object destroyed!
std::cout << "obj still die?" << std::endl;;
std::cin.get();
}
#endif
/*
obj still alive?
object destroyed!
obj still die?
*/强引用例子#
#ifdef LY_EP105
#include <iostream>
#include <memory>
struct Object
{
~Object()
{
std::cout << "object destroyed!" << std::endl;
}
};
struct Manager
{
//强引用,没有共享指针对象
Object* shareObj2;
};
int main() {
{
Manager manager;
{
std::shared_ptr<Object> shareObj1 = std::make_shared<Object>();
//没有阻止作用域外shareObj1内部实例销毁
manager.shareObj2 = shareObj1.get();
}//代码块结束时,shareObj1内部的Object实例对象被销毁
std::cout << "obj still alive?" << (manager.shareObj2 == nullptr ? "true" : "false") << std::endl; //这里答案是false,即不是nullptr,但是shareObj2指向的对象(shareObje1内部的Object实例)早就销毁了
}
std::cout << "obj still die?" << std::endl;;
std::cin.get();
}
/*
object destroyed!
obj still alive?false
obj still die?
*/
#endif 指向的对象已经销毁但是指针却不为空
循环引用#
#ifdef LY_EP105_
#include <memory>
#include <iostream>
/* 以下
* a 的引用计数:指向堆内存 a 的所有 shared_ptr 共同拥有的那个控制块里的强引用计数
* b 的引用计数:指向堆内存 b 的所有 shared_ptr 共同拥有的那个控制块里的强引用计数
*/
struct B; // 前置声明
struct A {
A()
{
std::cout << "堆内存a 创建了~" << std::endl;
}
std::shared_ptr<B> ptrB;
~A() { std::cout << "堆内存a 销毁了\n"; }
};
struct B {
B()
{
std::cout << "堆内存b 创建了~" << std::endl;
}
std::shared_ptr<A> ptrA;
~B() { std::cout << "堆内存b 销毁了\n"; }
};
void test() {
std::cout << "---1" << std::endl;
auto sharedA = std::make_shared<A>();//假设引用了堆内存a
std::cout << "---2" << std::endl;
auto sharedB = std::make_shared<B>();//假设引用了堆内存b
sharedA->ptrB = sharedB; // sharedA.ptrB 又引用了b
sharedB->ptrA = sharedA; // sharedB.ptrA 又引用 a
/*分析
* 堆内存 a:被两个 shared_ptr 盯着:
* 栈上的变量 sharedA(强引用 +1)
* 堆内存 b 里的成员变量 ptrA [sharedB.ptrA](强引用 +1)
* 总计计数:2
* 堆内存 b:被两个 shared_ptr 盯着:
* 栈上的变量 sharedB(强引用 +1)
* 堆内存 a 里的成员变量 ptrB [sharedA.ptrB](强引用 +1)
* 总计计数:2
*/
//打印目前引用计数
std::cout << "a被引用次数:" << sharedA.use_count() << std::endl;
std::cout << "a被引用次数:" << sharedB->ptrA.use_count() << std::endl;
std::cout << "b被引用次数:" << sharedB.use_count() << std::endl;
std::cout << "b被引用次数:" << sharedA->ptrB.use_count() << std::endl;
}
/*
当函数执行到 } 时,栈帧销毁,局部变量 sharedA 和 sharedB 被释放。
* 变量 sharedA 销毁:它原本指向 a,现在它没了,a 的引用计数从 2 降到了 1。
* 注意:因为计数还没到 0,a 不会调用析构函数。
* 变量 sharedB 销毁:它原本指向 b,现在它也没了,b 的引用计数从 2 降到了 1。
* 注意:因为计数还没到 0,b 不会调用析构函数。
*/
int main()
{
test();
std::cin.get();
return 0;
}
/*
---1
堆内存a 创建了~
---2
堆内存b 创建了~
a被引用次数:2
a被引用次数:2
b被引用次数:2
b被引用次数:2
*/
#endif弱引用解决循环引用的问题#
#ifdef LY_EP105
#include <memory>
#include <iostream>
/* 以下
* a 的引用计数:指向堆内存 a 的所有 shared_ptr 共同拥有的那个控制块里的强引用计数
* b 的引用计数:指向堆内存 b 的所有 shared_ptr 共同拥有的那个控制块里的强引用计数
*/
struct B; // 前置声明
struct A {
A()
{
std::cout << "堆内存a 创建了~" << std::endl;
}
std::shared_ptr<B> ptrB;
~A() { std::cout << "堆内存a 销毁了\n"; }
};
struct B {
B()
{
std::cout << "堆内存b 创建了~" << std::endl;
}
std::weak_ptr<A> ptrA;
~B() { std::cout << "堆内存b 销毁了\n"; }
};
void test() {
std::cout << "---1" << std::endl;
auto sharedA = std::make_shared<A>();//假设引用了堆内存a
std::cout << "---2" << std::endl;
auto sharedB = std::make_shared<B>();//假设引用了堆内存b
sharedA->ptrB = sharedB; // sharedA.ptrB 又引用了b
sharedB->ptrA = sharedA; // sharedB.ptrA 又引用 a
/*分析
* 堆内存 a:被两个 shared_ptr 盯着:
* 栈上的变量 sharedA(强引用 +1)
* 堆内存 b 里的成员变量 ptrA [sharedB.ptrA](弱引用 +1)
* 总计计数:1
* 堆内存 b:被两个 shared_ptr 盯着:
* 栈上的变量 sharedB(强引用 +1)
* 堆内存 a 里的成员变量 ptrB [sharedA.ptrB](强引用 +1)
* 总计计数:2
*/
//打印目前引用计数
std::cout << "a被引用次数:" << sharedA.use_count() << std::endl;
std::cout << "a被引用次数:" << sharedB->ptrA.use_count() << std::endl;
std::cout << "b被引用次数:" << sharedB.use_count() << std::endl;
std::cout << "b被引用次数:" << sharedA->ptrB.use_count() << std::endl;
}
/*
当函数执行到 } 时,栈帧销毁,局部变量 sharedA 和 sharedB 被释放。
* 变量 sharedA 销毁:它原本指向 a,现在它没了,a 的引用计数从 1 降到了 0,所以 a 调用了析构函数,同时a.ptrB 也被销毁了,b的引用计数降到了1。
* 变量 sharedB 销毁:它原本指向 b,现在它也没了,b 的引用计数从 1 降到了 0,所以 b 调用了析构函数
*/
int main()
{
test();
std::cin.get();
return 0;
}
/*
---1
堆内存a 创建了~
---2
堆内存b 创建了~
a被引用次数:1
a被引用次数:1
b被引用次数:2
b被引用次数:2
堆内存a 销毁了
堆内存b 销毁了
*/
#endifC++ 中,局部变量的析构顺序确实是严格的“后进先出”(LIFO),即与定义的顺序相反。
在你的代码中,sharedB 后定义,所以它会先于 sharedA 析构。
虽然在这个特定的 weak_ptr 例子中,谁先析构最终都能成功释放内存,但引用计数的中间跳变顺序确实会因为这个析构顺序而不同。
1. 析构顺序详解:sharedB 先走#
按照 C++ 标准,test() 结束时的具体步骤如下:
第一步:sharedB 析构#
- 栈上的
sharedB销毁。 - 它去减掉 堆内存 b 的强引用计数。
- b 的计数变化:从 2(
sharedB和sharedA->ptrB)降到 1。- 注意:此时 b 不会销毁,因为
sharedA->ptrB还拉着它。
- 注意:此时 b 不会销毁,因为
第二步:sharedA 析构#
- 栈上的
sharedA销毁。 - 它去减掉 堆内存 a 的强引用计数。
- a 的计数变化:从 1(只有
sharedA,因为ptrA是弱引用不计入强计数)降到 0。 - a 触发析构:打印 “堆内存 a 销毁了”。
- 连锁反应:随着
a的销毁,其成员a->ptrB也随之销毁。 - b 的计数变化:
ptrB销毁时,将 堆内存 b 的计数从 1 降到了 0。 - b 触发析构:打印 “堆内存 b 销毁了”。
2. 如果调换定义顺序,会有区别吗?#
如果你的代码是先定义 sharedB 再定义 sharedA,那么 sharedA 会先析构:
sharedA析构,a 的计数归 0 → a 销毁。- a 销毁带动
a->ptrB销毁 → b 的计数从 2 降到 1。 - 接着栈上的
sharedB析构 → b 的计数从 1 降到 0 → b 销毁。
结论: 无论谁先谁后,最终两块内存都能安全释放。但在复杂的系统中(特别是对象之间有严格依赖逻辑时),这种“倒序析构”的特性非常重要,因为它保证了后创建的对象(可能依赖先创建的对象)会先被安全地拆除。
什么是 std::weak_ptr?#
std::weak_ptr 是一种不增加引用计数的智能指针。它必须与 std::shared_ptr 配合使用。当你将一个 shared_ptr 赋值给 weak_ptr 时,它会指向相同的人内存,但不会增加该内存的“所有权”计数(reference count)。
为什么需要它?(解决循环引用)#
- 循环引用问题: 如果两个类互相持有对方的
shared_ptr,它们的引用计数永远不会归零,导致内存泄漏。 - 解决方案: 将其中一个改为
weak_ptr。这样,当其中一个对象被销毁时,由于weak_ptr不计入引用计数,另一个对象也能被正确释放。
如何检查对象是否还存在?#
由于 weak_ptr 不保证它指向的对象一定有效,在使用前必须检查。
expired()方法: 返回一个布尔值,检查底层对象是否已被销毁。lock()方法: 这是最常用的安全访问方式。它会返回一个std::shared_ptr。如果对象还活着,你会得到一个有效的shared_ptr(此时引用计数临时加 1);如果对象已销毁,则返回一个空的shared_ptr。
#ifdef LY_EP105
#include <iostream>
#include <memory>
struct Object
{
~Object()
{
std::cout << "object destroyed!" << std::endl;
}
};
struct Manager
{
//弱引用,可以用来共享指针对象但是不增加计数
std::weak_ptr<Object> shareObj2;
};
int main() {
{
Manager manager;
{
std::shared_ptr<Object> shareObj1 = std::make_shared<Object>();
//没有阻止作用域外shareObj1内部实例销毁
//弱指针有一个接受共享指针的构造器
manager.shareObj2 = shareObj1;
}//代码块结束时,shareObj1内部的Object实例对象被销毁
std::cout << "obj still alive?" << (manager.shareObj2.expired() ? "false" : "true") << std::endl; //这里答案是true,即shareObj2指向的对象(shareObje1内部的Object实例)早就销毁了
//阻止了一种情况,就是检查后执行完//1之后,对象在其他线程中被
//析构了
//1. 由于 lock() 返回的是一个 shared_ptr(即代码中的 obj),根据 shared_ptr 的规则:
//只要 obj 还在生命周期内(即在 if 分支的大括号 {} 里面),强引用计数至少为 1。
//即使在执行 //1 或 //2 的瞬间,另一个线程销毁了 manager 里的原始 shared_ptr,对象也不会被析构。
//2. 内存的释放被推迟到了 obj 离开作用域(大括号结束)的那一刻。
if (auto obj = manager.shareObj2.lock())
{
//1
//2
}
//检查是否存活
if (manager.shareObj2.expired())
{
}
//检查引用计数
if (manager.shareObj2.use_count() == 0)
{
}
}
std::cout << "obj still die?" << std::endl;;
std::cin.get();
}
/*
object destroyed!
obj still alive?true
obj still die?
*/
#endif 点评#
- 唯一指针没有什么开销,共享指针会有开销
实际应用场景#
- 缓存(Caching): 你可能想保存一个指向对象的指针,但不希望因为这个缓存而强制该对象一直留在内存中。
- 观察者模式(Observer Pattern): 观察者可以持有被观察对象的
weak_ptr,这样被观察者被销毁时,观察者不会持有“僵尸”引用。