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
在所有权模型、内存管理、线程安全、循环引用等方面的主要区别以及它们各自的使用场景。在选择使用哪种智能指针时,应根据具体的需求和设计来决定。