unique_ptr 和 shared_ptr
unique_ptr
和 shared_ptr
是 C++ 标准库中的智能指针,用于管理动态分配的对象的生命周期,以避免内存泄漏和手动资源管理的问题。
-
unique_ptr
:std::unique_ptr
是一个独占所有权的智能指针,确保在任何时候只有一个unique_ptr
拥有对动态分配的对象的所有权。- 当
unique_ptr
被销毁或通过std::move
转移所有权时,关联的对象会被正确释放。 unique_ptr
的性能开销较小,因为它不需要维护引用计数。
#include <memory> std::unique_ptr<int> uniquePtr = std::make_unique<int>(42);
-
shared_ptr
:std::shared_ptr
允许多个智能指针共享对同一对象的所有权,通过引用计数来跟踪对象的共享情况。- 当最后一个拥有
shared_ptr
的实例被销毁时,关联的对象会被释放。 shared_ptr
的使用相对较方便,但由于引用计数的管理,可能涉及一些性能开销。
#include <memory> std::shared_ptr<int> sharedPtr1 = std::make_shared<int>(42); std::shared_ptr<int> sharedPtr2 = sharedPtr1; // 共享所有权
选择 unique_ptr
还是 shared_ptr
取决于你的需求。如果能确保对象只有一个所有者,使用 unique_ptr
可以更轻量和高效。如果需要多个地方共享对象所有权,使用 shared_ptr
。注意,shared_ptr
的引用计数机制可能导致循环引用问题,可以通过 std::weak_ptr
来解决。
循环引用(Circular Reference)是指两个或多个对象彼此之间相互引用,形成一个环状结构。在 C++ 中,当使用智能指针时,特别是 std::shared_ptr
,循环引用可能导致内存泄漏,因为循环引用会导致对象的引用计数永远不会减为零,从而对象的析构函数不会被调用,资源无法正确释放。
考虑以下示例,其中两个对象(A 和 B)相互引用:
#include <memory>
class B; // 前置声明
class A {
public:
std::shared_ptr<B> b_ptr;
A() {
std::cout << "A constructor" << std::endl;
}
~A() {
std::cout << "A destructor" << std::endl;
}
};
class B {
public:
std::shared_ptr<A> a_ptr;
B() {
std::cout << "B constructor" << std::endl;
}
~B() {
std::cout << "B destructor" << std::endl;
}
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
// 此时 a 和 b 形成循环引用
return 0; // 程序结束,但对象 A 和 B 的析构函数不会被调用,内存泄漏
}
在上述例子中,对象 A 拥有一个指向对象 B 的 shared_ptr
,而对象 B 也拥有一个指向对象 A 的 shared_ptr
,这样就形成了循环引用。当 main
函数结束时,智能指针的引用计数不会减为零,因此对象 A 和 B 的析构函数不会被调用,导致内存泄漏。
为了解决这个问题,可以使用 std::weak_ptr
来打破循环引用,因为std::weak_ptr
不会增加引用计数。在上面的例子中,可以将 b_ptr
和 a_ptr
改为 std::weak_ptr
类型。这样,循环引用将不再阻止对象的析构函数被调用。
std::weak_ptr
是 C++ 中的智能指针,用于解决循环引用的问题。它不会增加引用计数,因此不会导致循环引用问题。在使用 std::weak_ptr
时,需要配合 std::shared_ptr
使用。以下是一个简单的示例:
#include <iostream>
#include <memory>
class B; // 前置声明
class A {
public:
std::shared_ptr<B> b_ptr;
A() {
std::cout << "A constructor" << std::endl;
}
~A() {
std::cout << "A destructor" << std::endl;
}
};
class B {
public:
std::weak_ptr<A> a_weak_ptr;
B() {
std::cout << "B constructor" << std::endl;
}
~B() {
std::cout << "B destructor" << std::endl;
}
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_weak_ptr = a; // 使用 std::weak_ptr
// 此时 a 和 b 不再形成循环引用
return 0; // 程序结束,对象 A 和 B 的析构函数会被调用
}
在这个例子中,我们将 a_ptr
和 b_ptr
的类型改为 std::weak_ptr
。这样,std::weak_ptr
不会增加对象引用计数,从而防止了循环引用。在 main
函数结束时,std::shared_ptr
的引用计数会减少,当减到零时,对象 A 和 B 的析构函数会被调用,资源得到释放。
需要注意的是,在使用 std::weak_ptr
时,需要通过 lock
函数将其转换为 std::shared_ptr
来访问对象。这是因为 std::weak_ptr
本身并不拥有对象,而是只是观察 std::shared_ptr
的状态。
std::weak_ptr
通过 lock
函数可以尝试将其转换为 std::shared_ptr
,以便安全地访问被观察对象。如果 std::shared_ptr
对象已经被销毁,lock
将返回一个空的 std::shared_ptr
。以下是一个使用 lock
函数的实例:
#include <iostream>
#include <memory>
class B; // 前置声明
class A {
public:
std::shared_ptr<B> b_ptr;
A() {
std::cout << "A constructor" << std::endl;
}
~A() {
std::cout << "A destructor" << std::endl;
}
};
class B {
public:
std::weak_ptr<A> a_weak_ptr;
B() {
std::cout << "B constructor" << std::endl;
}
~B() {
std::cout << "B destructor" << std::endl;
}
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_weak_ptr = a;
// 使用 lock 尝试将 weak_ptr 转换为 shared_ptr
std::shared_ptr<A> sharedA = b->a_weak_ptr.lock();
if (sharedA) {
// 成功转换,对象 A 仍然存在
std::cout << "Accessing A through shared_ptr" << std::endl;
} else {
// 转换失败,对象 A 已被销毁
std::cout << "Object A is no longer available" << std::endl;
}
return 0; // 程序结束,对象 A 和 B 的析构函数会被调用
}
在这个例子中,我们使用 lock
函数尝试将 std::weak_ptr
b->a_weak_ptr
转换为 std::shared_ptr<A>
。如果成功转换,就可以通过返回的 std::shared_ptr
安全地访问对象 A。如果对象 A 已经被销毁,lock
返回一个空的 std::shared_ptr
。这样,我们可以在使用前检查 shared_ptr
是否为空,以避免访问已释放的对象。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)