1.概要
2.内容
在C++中,主要有四种智能指针,它们是:std::auto_ptr(已被废弃)、std::unique_ptr、std::shared_ptr 和 std::weak_ptr。下面将分别介绍这些智能指针的特点和使用场景。
1. std::auto_ptr(已废弃)
std::auto_ptr 是C++98和C++03标准库中的智能指针,但由于其设计存在缺陷(如拷贝语义),已在C++11中被废弃,并在C++17中被完全移除。
- 特点:
- 管理单个对象的所有权。
- 拷贝时会转移所有权,导致源指针变为空。
- 不适合用于容器或在多线程环境中使用。
- 示例:
std::auto_ptr<int> p1(new int(10)); std::auto_ptr<int> p2 = p1; // p1 变为空,p2 拥有对象的所有权
2. std::unique_ptr
std::unique_ptr 是C++11引入的智能指针,用于替代 std::auto_ptr。
- 特点:
- 独占对象的所有权,不能复制,只能通过移动语义转移所有权。
- 支持删除器(deleter),可以自定义删除逻辑。
- 适用于管理单个对象的生命周期。
- 示例:
std::unique_ptr<int> p1(new int(10)); std::unique_ptr<int> p2 = std::move(p1); // p1 变为空,p2 拥有对象的所有权
3. std::shared_ptr
std::shared_ptr 也是C++11引入的智能指针,用于共享对象的所有权。
- 特点:
- 多个
std::shared_ptr实例可以共享同一个对象。 - 使用引用计数来管理对象的生命周期,当最后一个
std::shared_ptr被销毁时,对象才会被删除。 - 支持删除器(deleter)。
- 线程安全(引用计数的增减是原子的)。
- 多个
- 示例:
std::shared_ptr<int> p1(new int(10)); std::shared_ptr<int> p2 = p1; // p1 和 p2 共享同一个对象
4. std::weak_ptr
std::weak_ptr 是为了配合 std::shared_ptr 而设计的,用于解决循环引用问题。
- 特点:
- 不能独立拥有对象,必须从一个
std::shared_ptr或另一个std::weak_ptr创建。 - 不影响对象的引用计数,不会阻止对象被销毁。
- 可以用于检查对象是否还存在,并获取一个
std::shared_ptr来访问对象。
- 不能独立拥有对象,必须从一个
- 示例:
std::shared_ptr<int> sp = std::make_shared<int>(10); std::weak_ptr<int> wp = sp; // 不会增加引用计数 if (auto locked = wp.lock()) { // 检查对象是否还存在 // locked 是一个 shared_ptr,可以安全访问对象 }
对比总结
- 所有权管理:
std::unique_ptr:独占所有权,不可共享。std::shared_ptr:共享所有权,通过引用计数管理。std::weak_ptr:不管理所有权,仅用于引用std::shared_ptr。
- 生命周期:
std::unique_ptr:生命周期由单个unique_ptr管理。std::shared_ptr:生命周期由所有shared_ptr共享管理。std::weak_ptr:不影响生命周期管理。
- 适用场景:
std::unique_ptr:适用于需要独占所有权的场景。std::shared_ptr:适用于需要共享所有权的场景。std::weak_ptr:适用于需要引用std::shared_ptr但不增加引用计数的场景,常用于解决循环引用问题。
选择适当的智能指针,可以帮助你更好地管理动态内存和资源,避免内存泄漏和其他资源管理问题。
3.关联知识
1.std::unique_ptr
std::unique_ptr 是 C++11 引入的一个智能指针类型,用于管理动态分配的内存。它确保当 std::unique_ptr 离开作用域或被显式销毁时,它所管理的内存会被自动释放,从而防止内存泄漏。std::unique_ptr 是独占所有权的,这意味着同一时间只能有一个 std::unique_ptr 拥有某块内存的所有权。
主要特性
-
独占所有权:
std::unique_ptr不允许有多个指针指向同一个资源。当std::unique_ptr被复制或赋值给另一个std::unique_ptr时,原来的指针会变为空,新的指针将拥有资源的所有权。这种机制确保了资源的唯一性和安全性。 -
自动内存管理:当
std::unique_ptr离开其作用域时,它所管理的资源会被自动释放,无需手动调用delete。 -
自定义删除器:
std::unique_ptr允许用户指定一个自定义的删除器,用于在资源被释放时执行特定的操作。
使用示例
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass Constructor\n"; }
~MyClass() { std::cout << "MyClass Destructor\n"; }
};
int main() {
std::unique_ptr<MyClass> ptr1(new MyClass()); // 创建一个 unique_ptr,指向新分配的 MyClass 对象
{
std::unique_ptr<MyClass> ptr2 = std::move(ptr1); // 使用 std::move 将 ptr1 的所有权转移给 ptr2
// 此时,ptr1 变为空,ptr2 拥有 MyClass 对象的所有权
} // ptr2 离开作用域,MyClass 对象被自动销毁
// ptr1 已经为空,不会再次销毁 MyClass 对象
return 0;
}
注意事项
- 由于
std::unique_ptr是独占所有权的,因此不能将其复制到另一个std::unique_ptr。需要使用std::move来转移所有权。 std::unique_ptr不能被复制到容器中,但可以使用std::move将它们移动到容器中,或者使用std::unique_ptr的指针容器(如std::vector<std::unique_ptr<T>>)。- 当
std::unique_ptr被销毁或重置时,它所管理的资源也会被销毁。因此,要确保在std::unique_ptr的生命周期内不要手动删除它所管理的资源。
总的来说,std::unique_ptr 是一种安全、高效的内存管理方式,特别适用于需要独占所有权和自动内存管理的场景。
2.std::shared_ptr
std::shared_ptr 是 C++11 引入的另一种智能指针类型,与 std::unique_ptr 不同,它允许多个 std::shared_ptr 实例共享同一个资源(通常是动态分配的内存)。std::shared_ptr 通过引用计数来管理资源的生命周期,当最后一个指向资源的 std::shared_ptr 被销毁或重置时,资源才会被释放。
主要特性
-
共享所有权:多个
std::shared_ptr可以共享同一个资源。每个std::shared_ptr都持有一个指向资源的指针和一个引用计数,该计数记录了有多少个std::shared_ptr共享这个资源。 -
自动内存管理:当最后一个指向资源的
std::shared_ptr被销毁或引用计数降为零时,资源会被自动释放。 -
线程安全:
std::shared_ptr的引用计数是线程安全的,但访问共享资源本身并不是线程安全的,需要额外的同步机制。 -
循环引用问题:如果两个或多个
std::shared_ptr实例相互引用,可能会导致循环引用问题,进而造成内存泄漏。为了避免这个问题,可以使用std::weak_ptr。
使用示例
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass Constructor\n"; }
~MyClass() { std::cout << "MyClass Destructor\n"; }
};
int main() {
std::shared_ptr<MyClass> ptr1(new MyClass()); // 创建一个 shared_ptr,指向新分配的 MyClass 对象
{
std::shared_ptr<MyClass> ptr2 = ptr1; // 复制 shared_ptr,两个 shared_ptr 共享同一个 MyClass 对象
std::cout << "ptr1 use_count: " << ptr1.use_count() << "\n"; // 输出引用计数
std::cout << "ptr2 use_count: " << ptr2.use_count() << "\n";
} // ptr2 离开作用域,但由于 ptr1 仍然存在,MyClass 对象不会被销毁
std::cout << "After ptr2 goes out of scope, ptr1 use_count: " << ptr1.use_count() << "\n";
// ptr1 离开作用域,MyClass 对象被自动销毁
return 0;
}
注意事项
- 不要在多个
std::shared_ptr实例之间形成循环引用,因为这会导致内存泄漏。使用std::weak_ptr来打破循环引用。 - 当
std::shared_ptr被复制时,它会增加引用计数;当它被销毁或重置时,它会减少引用计数。只有当引用计数降为零时,资源才会被释放。 std::shared_ptr的大小通常比原始指针大,因为它需要存储额外的引用计数信息。- 虽然
std::shared_ptr的引用计数是线程安全的,但访问共享资源时仍然需要小心,以避免数据竞争和其他并发问题。
总的来说,std::shared_ptr 是一种非常有用的智能指针,适用于需要共享所有权和自动内存管理的场景。然而,使用时需要注意循环引用和并发访问的问题。
3.std::unique_ptr和std::shared_ptr对比说明
std::unique_ptr 和 std::shared_ptr 都是 C++11 引入的智能指针类型,用于管理动态分配的内存资源。它们之间有一些重要的区别,下面是对这两个智能指针的对比说明:
所有权模型
-
std::unique_ptr:- 独占所有权:一个对象只能由一个
std::unique_ptr拥有。当std::unique_ptr被销毁时,它所拥有的对象也会被销毁。 - 不可复制:
std::unique_ptr不能被复制,只能被移动(使用std::move)。这意味着你不能有两个std::unique_ptr指向同一个对象。
- 独占所有权:一个对象只能由一个
-
std::shared_ptr:- 共享所有权:允许多个
std::shared_ptr实例共享同一个对象的所有权。 - 引用计数:
std::shared_ptr内部维护一个引用计数,跟踪有多少个std::shared_ptr指向同一个对象。当最后一个std::shared_ptr被销毁时,对象才会被销毁。 - 可复制:
std::shared_ptr可以被复制,复制操作会增加引用计数。
- 共享所有权:允许多个
内存管理
-
std::unique_ptr:- 当
std::unique_ptr离开作用域或被显式销毁时,它所拥有的对象也会被销毁。 - 没有引用计数的开销,性能通常更好。
- 当
-
std::shared_ptr:- 当最后一个指向对象的
std::shared_ptr被销毁时,对象才会被销毁。 - 需要维护引用计数,这可能会带来一定的性能开销。
- 提供了
std::make_shared函数,用于更高效地创建std::shared_ptr和分配内存(只进行一次内存分配)。
- 当最后一个指向对象的
线程安全
-
std::unique_ptr:- 本身不是线程安全的,因为它不涉及引用计数。
- 如果需要在多线程环境中使用,需要适当的同步机制。
-
std::shared_ptr:- 引用计数的操作(如增加和减少)是原子的,因此是线程安全的。
- 但是,通过
std::shared_ptr访问共享对象本身并不是线程安全的,需要额外的同步机制。
循环引用
-
std::unique_ptr:- 由于是独占所有权,不会出现循环引用的问题。
-
std::shared_ptr:- 可能会出现循环引用的问题,如果两个或多个
std::shared_ptr相互引用,可能会导致内存泄漏。 - 可以使用
std::weak_ptr来打破循环引用,std::weak_ptr允许观察std::shared_ptr管理的对象,但不影响引用计数。
- 可能会出现循环引用的问题,如果两个或多个
使用场景
-
std::unique_ptr:- 适用于需要独占所有权的场景,如管理动态分配的内存资源、文件句柄、网络连接等。
- 适用于单线程或多线程环境中的局部资源管理。
-
std::shared_ptr:- 适用于需要共享所有权的场景,如多线程环境中共享资源。
- 适用于复杂的对象图,其中对象之间有相互引用的情况。
示例代码
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass Constructor\n"; }
~MyClass() { std::cout << "MyClass Destructor\n"; }
};
int main() {
// std::unique_ptr 示例
{
std::unique_ptr<MyClass> uniquePtr(new MyClass());
// uniquePtr 不能被复制,只能被移动
// std::unique_ptr<MyClass> uniquePtr2 = uniquePtr; // 错误
std::unique_ptr<MyClass> uniquePtr2 = std::move(uniquePtr); // 正确
} // uniquePtr 和 uniquePtr2 离开作用域,MyClass 对象被自动销毁
// std::shared_ptr 示例
{
std::shared_ptr<MyClass> sharedPtr1(new MyClass());
std::shared_ptr<MyClass> sharedPtr2 = sharedPtr1; // 正确,共享所有权
std::cout << "sharedPtr1 use_count: " << sharedPtr1.use_count() << "\n"; // 输出引用计数
std::cout << "sharedPtr2 use_count: " << sharedPtr2.use_count() << "\n";
} // sharedPtr1 和 sharedPtr2 离开作用域,当最后一个 shared_ptr 被销毁时,MyClass 对象被自动销毁
return 0;
}
综上所述,std::unique_ptr 和 std::shared_ptr 各有优缺点,选择哪种智能指针取决于具体的需求和设计。
以下是std::unique_ptr和std::shared_ptr的对比表格:
| 特性/智能指针 | std::unique_ptr | std::shared_ptr |
|---|---|---|
| 所有权模型 | 独占所有权 | 共享所有权 |
| 所有权复制 | 不可复制,只能移动 | 可复制,复制时增加引用计数 |
| 内存管理 | 离开作用域或显式销毁时自动释放对象 | 最后一个std::shared_ptr销毁时自动释放对象 |
| 引用计数 | 无引用计数 | 有引用计数,维护对象共享状态 |
| 性能开销 | 无引用计数开销,通常更快 | 有引用计数开销,可能稍慢 |
| 线程安全 | 本身不是线程安全的 | 引用计数操作是线程安全的,但对象访问需要额外同步 |
| 循环引用 | 不会出现循环引用问题 | 可能出现循环引用问题,需使用std::weak_ptr解决 |
| 使用场景 | 单个所有者管理资源,如临时对象、局部资源管理 | 多个所有者共享资源,如复杂对象图、多线程资源共享 |
| 创建方式 | std::unique_ptr<T> ptr(new T()); 或 std::make_unique<T>(); | std::shared_ptr<T> ptr(new T()); 或 std::make_shared<T>(); |
| 访问对象 | 使用 *ptr 或 ptr->member | 使用 *ptr 或 ptr->member |
| 自定义删除器 | 支持自定义删除器 | 支持自定义删除器 |
| 与容器兼容性 | 可存储在支持移动语义的容器中,如std::vector<std::unique_ptr<T>> | 可存储在支持复制和移动语义的容器中,如std::vector<std::shared_ptr<T>> |
通过这个表格,可以清晰地看到std::unique_ptr和std::shared_ptr在所有权模型、内存管理、线程安全、循环引用等方面的主要区别以及它们各自的使用场景。在选择使用哪种智能指针时,应根据具体的需求和设计来决定。

586

被折叠的 条评论
为什么被折叠?



