网站制作有哪些种类,360度实景地图下载,网上购物流程,wordpress all in one seo pack1 std::shared_ptr 的概述
std::shared_ptr 是 C11 标准库中引入的一种智能指针#xff0c;用于表示共享所有权的智能指针。它允许多个 shared_ptr 实例共享同一个对象的所有权#xff0c;并在最后一个引用该对象的 shared_ptr 被销毁或被重置时自动删除该对象。这种特性使得…1 std::shared_ptr 的概述
std::shared_ptr 是 C11 标准库中引入的一种智能指针用于表示共享所有权的智能指针。它允许多个 shared_ptr 实例共享同一个对象的所有权并在最后一个引用该对象的 shared_ptr 被销毁或被重置时自动删除该对象。这种特性使得 std::shared_ptr 在需要在多个所有者之间共享对象时非常有用。
1.1 std::shared_ptr 的主要特性
1引用计数 std::shared_ptr 内部使用一个引用计数器来跟踪有多少个 shared_ptr 实例指向同一个对象。每次创建一个新的 shared_ptr 指向同一个对象时引用计数就会增加每次销毁或重置一个 shared_ptr 时引用计数就会减少。
2自动删除 当引用计数减少到 0 时std::shared_ptr 会自动删除它所指向的对象并释放相关资源。这避免了手动管理内存时可能出现的内存泄漏问题。
3自定义删除器 与 std::unique_ptr 类似std::shared_ptr 也支持自定义删除器。你可以提供一个可调用对象作为删除器用于在引用计数为 0 时执行特定的清理逻辑。
4线程安全 std::shared_ptr 的引用计数操作是线程安全的可以在多线程环境中安全地使用。
1.2 std::shared_ptr 的原理
以下是 std::shared_ptr 的详细原理
1引用计数
每个 std::shared_ptr 实例都关联一个引用计数用于跟踪当前有多少个 shared_ptr 实例指向同一个对象。这个引用计数通常存储在一个控制块control block中该控制块与所管理的对象一起在堆上分配。控制块可能还包含其他信息比如自定义的删除器。
2对象的创建与引用
当创建一个新的 std::shared_ptr 并让它指向一个对象时会发生以下事情
如果该对象是新分配的例如通过 std::make_shared 或 new 操作符则会在堆上分配一个控制块并将对象的指针和初始引用计数通常为 1存储在控制块中。 shared_ptr 内部保存指向这个控制块的指针而不是直接保存对象的指针。通过控制块shared_ptr 可以访问对象的指针和引用计数。 当另一个 shared_ptr 被构造为指向同一个对象时例如通过拷贝或赋值操作引用计数会增加。这是因为新的 shared_ptr 实例也会保存指向同一个控制块的指针。
3对象的销毁与引用计数减少
当 std::shared_ptr 被销毁例如离开作用域或被重置为指向另一个对象时会发生以下事情
shared_ptr 所指向的控制块中的引用计数会减少。如果引用计数减少到 0这意味着没有任何 shared_ptr 再指向该对象此时控制块会调用相应的删除器默认为 delete 操作符来销毁对象并释放对象和控制块所占用的内存。
4线程安全
std::shared_ptr 的引用计数操作是线程安全的这意味着在多线程环境中多个线程可以同时修改同一个 shared_ptr 的引用计数而不会导致数据竞争或未定义行为。这是通过使用原子操作或其他同步机制来实现的。
5自定义删除器
除了默认的 delete 操作符外std::shared_ptr 还允许使用自定义的删除器。自定义删除器是一个可调用对象如函数、函数对象或 lambda 表达式它会在引用计数减少到 0 时被调用用于执行特定的清理逻辑。这使得 std::shared_ptr 能够更灵活地管理资源例如关闭文件句柄、释放网络连接等。
通过引用计数和自动内存管理机制std::shared_ptr 简化了动态分配对象的生命周期管理减少了内存泄漏的风险并提高了代码的可读性和可维护性。
1.3 注意事项
1避免循环引用 在使用 std::shared_ptr 时要特别注意避免循环引用即两个或多个 shared_ptr 相互引用形成一个无法被打破的引用环。这会导致引用计数永远无法减少到 0从而造成内存泄漏。为了避免循环引用可以使用 std::weak_ptr 来打破引用环。
2与原始指针的交互 虽然 std::shared_ptr 提供了自动内存管理的便利但在某些情况下你可能仍然需要与原始指针进行交互。在这种情况下要特别小心确保不会意外地删除由 shared_ptr 管理的对象或者创建额外的引用计数问题。
2性能考虑 由于 std::shared_ptr 需要维护引用计数相比裸指针或 std::unique_ptr它可能会有一些额外的性能开销。因此在不需要共享所有权的场景中使用 std::unique_ptr 可能是更好的选择。
2 std::shared_ptr 的创建与初始化
2.1 使用 std::make_shared 创建
std::make_shared 是创建 std::shared_ptr 的推荐方式因为它在单个内存分配中同时分配控制块和对象本身从而提高了性能。
// 创建一个指向 int 的 shared_ptr并初始化为 12
std::shared_ptrint ptr1 std::make_sharedint(12); // 创建一个指向自定义类型的 shared_ptr并调用构造函数
struct MyStruct { MyStruct(int x, double y) : value1(x), value2(y) {} int value1; double value2;
};
std::shared_ptrMyStruct ptr2 std::make_sharedMyStruct(12, 3.14);2.2 使用构造函数创建
虽然可以使用 shared_ptr 的构造函数直接创建智能指针但这种方式不如 std::make_shared 高效因为它需要两次内存分配一次用于对象一次用于控制块。
// 使用原始指针创建 shared_ptr不推荐可能导致内存泄漏
int* raw_ptr new int(12);
std::shared_ptrint ptr1(raw_ptr); // 注意raw_ptr 不应再被 delete // 使用空指针初始化 shared_ptr
std::shared_ptrint ptr2; // 默认构造的 shared_ptr 不指向任何对象 // 复制另一个 shared_ptr 来初始化新的 shared_ptr引用计数会增加
std::shared_ptrint ptr3 ptr1; // ptr3 和 ptr1 现在共享同一个对象的所有权2.3 使用 std::allocate_shared 创建
对于需要自定义内存分配的场景可以使用 std::allocate_shared。它接受一个分配器作为参数并使用该分配器来分配对象和控制块。
std::allocatorint allocator;
std::shared_ptrint ptr std::allocate_sharedint(allocator, 12);2.4 自定义删除器
std::shared_ptr 还允许在构造时提供一个自定义的删除器。删除器是一个可调用对象当最后一个 shared_ptr 不再指向对象时它会被调用。
// 自定义删除器函数
void customDelete(int* ptr) { // 执行一些清理逻辑... delete ptr;
} // 使用自定义删除器创建 shared_ptr
std::shared_ptrint ptr1(new int(12), customDelete); // 使用 lambda 表达式作为删除器
std::shared_ptrint ptr2(new int(12), [](int* ptr) { // 执行清理逻辑... delete ptr;
});注意当使用原始指针和自定义删除器创建 std::shared_ptr 时必须确保原始指针只被一个 shared_ptr 管理以避免内存泄漏。如果原始指针被多个 shared_ptr 管理则当其中一个 shared_ptr 被销毁时对象可能会被意外删除。因此通常推荐使用 std::make_shared 来创建 std::shared_ptr。
2.5 初始化列表
在类的初始化列表中也可以初始化 std::shared_ptr 成员变量。
class MyClass {
public: MyClass() : mySharedPtr(std::make_sharedint(10)) {}
private: std::shared_ptrint mySharedPtr;
};3 std::shared_ptr 的自定义删除器
std::shared_ptr 的自定义删除器是一种功能强大的特性它允许程序员为 shared_ptr 指定一个特定的函数或函数对象该函数或对象会在最后一个 shared_ptr 失去对对象的所有权时即引用计数减少到 0 时被调用以执行特定的清理逻辑。
自定义删除器通常用于处理那些不是通过 new 操作符分配的对象或者那些需要特殊处理来释放的对象。例如当你管理一些不是简单内存块的资源如文件句柄、网络连接或者自定义内存管理器分配的内存时你可能需要使用自定义删除器。
3.1 函数指针作为删除器
可以将一个普通的函数指针作为删除器传递给 shared_ptr 的构造函数。这个函数应该接受一个指向对象的指针并执行适当的清理操作。
struct MyType { };void customDelete(MyType* ptr) { // 清理逻辑比如关闭文件句柄、释放网络连接等 delete ptr; // 假设 MyType 是通过 new 分配的
} std::shared_ptrMyType ptr(new MyType, customDelete);3.2 函数对象仿函数作为删除器
也可以使用函数对象也称为仿函数作为删除器。函数对象是一个重载了 operator() 的类它可以像普通函数一样被调用。
struct MyType { };struct CustomDeleter { void operator()(MyType* ptr) const { // 清理逻辑 delete ptr; }
}; std::shared_ptrMyType ptr(new MyType, CustomDeleter());3.3 Lambda 表达式作为删除器
C11 引入了 lambda 表达式它们也可以作为 shared_ptr 的删除器。Lambda 表达式提供了一种简洁的方式来定义匿名函数对象。
struct MyType { };auto deleter [](MyType* ptr) { // 清理逻辑 delete ptr;
}; std::shared_ptrMyType ptr1(new MyType, deleter);// 也可以直接使用
std::shared_ptrMyType ptr2(new MyType, [](MyType* ptr) {// 清理逻辑 delete ptr;
});3.4 注意事项
1确保正确删除 自定义删除器必须确保正确地释放资源。如果资源是通过 new 分配的那么删除器应该使用 delete 来释放它。如果资源是通过其他方式如自定义内存管理器分配的那么删除器应该使用相应的方法来释放它。
2避免重复删除 如果原始指针被多个 shared_ptr 管理并且每个 shared_ptr 都使用了自定义删除器那么可能会导致资源被重复释放。因此必须确保每个原始指针只被一个 shared_ptr 管理或者确保删除器能够安全地处理重复调用。
3使用 std::make_shared 通常推荐使用 std::make_shared 来创建 shared_ptr因为它在一个内存分配中同时分配对象和控制块从而提高了性能。当使用自定义删除器时你可能需要直接使用 shared_ptr 的构造函数但请确保正确管理内存。
4类型安全 自定义删除器应该与 shared_ptr 所指向的对象类型相匹配。如果删除器期望一个不同类型的指针那么可能会导致未定义行为。
4 std::shared_ptr 与数组
std::shared_ptr 是 C11 引入的一种智能指针用于共享对象的所有权。虽然 std::shared_ptr 主要用于单个对象的内存管理但它也可以用于管理动态数组。不过在使用 std::shared_ptr 管理数组时需要特别注意一些细节以确保正确和安全的内存管理。
4.1 使用 std::shared_ptr 的一个特化版本推荐
当使用 std::shared_ptr 管理数组时需要使用特殊的构造函数这些版本接受一个额外的参数来表示数组的大小。此外当 std::shared_ptr 销毁其管理的数组时它会调用 delete[] 而不是 delete。
#include memory
#include iostream struct MyStruct { ~MyStruct() { std::cout MyStruct destroyed\n; }
}; int main()
{ // 使用 std::shared_ptr 管理数组 std::shared_ptrMyStruct[] arr_ptr(new MyStruct[5]); // 当 arr_ptr 离开作用域时MyStruct 数组会被自动使用 delete[] 删除 return 0;
}上面代码的输出为
MyStruct destroyed
MyStruct destroyed
MyStruct destroyed
MyStruct destroyed
MyStruct destroyed上面的这个例子创建了一个 std::shared_ptrMyStruct[] 来管理一个 MyStruct 类型的数组。注意类型中的方括号 []它告诉 std::shared_ptr 正在管理一个数组而不是单个对象。
4.2 自定义删除器与数组
就像普通的 std::shared_ptr 一样也可以为数组版本的 std::shared_ptr 提供自定义删除器。这在某些情况下可能很有用比如需要使用自定义的内存管理策略时。
void customArrayDelete(MyStruct* ptr) {// 自定义的数组删除逻辑 delete[] ptr;
}std::shared_ptrMyStruct[] arr_ptr(new MyStruct[5], customArrayDelete);在这个例子中传递了一个自定义的删除器 customArrayDelete 给 std::shared_ptr。当 arr_ptr 被销毁时它会调用 customArrayDelete 来释放数组。
4.3 注意事项
不要混用 new 和 delete[] 或 new[] 和 delete当使用 std::shared_ptr 管理数组时确保你使用 new[] 来分配数组这样 std::shared_ptr 就可以在销毁时正确地使用 delete[] 来释放内存。如果错误地使用了 new而不是 new[]那么当 std::shared_ptr 尝试使用 delete[] 来释放内存时会导致未定义行为。不要使用 std::make_shared 管理数组虽然 std::make_shared 通常是创建 std::shared_ptr 的推荐方式但它并不直接支持数组。尝试使用 std::make_shared 来创建管理数组的 std::shared_ptr 会导致编译错误因为 std::make_shared 的模板参数推导不会正确处理数组类型。
5 std::shared_ptr 的循环引用问题
std::shared_ptr 的循环引用问题是在使用智能指针时经常遇到的一个难题。循环引用指的是两个或多个 std::shared_ptr 对象相互引用形成一个闭环导致它们的引用计数永远无法降至零从而无法自动释放所管理的内存资源。
1循环引用的产生
循环引用通常发生在对象间存在相互依赖关系时。例如考虑两个类 A 和 B每个类都持有一个指向对方类型的 std::shared_ptr。
class B; class A {
public: std::shared_ptrB b_ptr; // ... 其他成员 ...
}; class B {
public: std::shared_ptrA a_ptr; // ... 其他成员 ...
}; void create_cycle() { auto a std::make_sharedA(); auto b std::make_sharedB(); a-b_ptr b; b-a_ptr a; // 此时a 和 b 形成了循环引用
}在 create_cycle 函数中创建了两个 std::shared_ptr 对象 a 和 b并将它们相互引用。这意味着 a 的引用计数因为 b-a_ptr 的存在而增加同时 b 的引用计数因为 a-b_ptr 的存在而增加。当 create_cycle 函数结束时a 和 b 离开作用域但它们的引用计数仍然不为零因此它们所管理的内存不会被释放。
2循环引用的影响
循环引用会导致内存泄漏因为 std::shared_ptr 无法检测到循环引用并自动释放内存。即使所有外部对 a 和 b 的引用都已消失它们所指向的对象仍然不会被销毁因为它们的引用计数永远不会降到零。
3解决循环引用 有几种方法可以解决 std::shared_ptr 的循环引用问题
使用弱指针 (std::weak_ptr) 弱指针不控制所指向对象的生命周期它们只是观察 std::shared_ptr 的对象。将循环引用中的一个 std::shared_ptr 替换为 std::weak_ptr 可以打破循环。当只有弱指针指向一个对象时该对象可以被安全地删除。 在下面的章节会详细讲解该方法。
手动管理内存 在某些情况下可能需要回退到使用原始指针和手动管理内存。这通常不是首选方法因为它容易引入内存泄漏和野指针等问题但它可以作为一种解决方案特别是在其他方法不适用或过于复杂时。
使用其他智能指针 根据具体情况可以考虑使用其他类型的智能指针如 std::unique_ptr 或 std::observer_ptrC20 中引入这些智能指针具有不同的所有权语义可能更适合某些情况。
解决循环引用问题的关键是确保没有路径使得对象的引用计数永远无法降至零。通过仔细设计类的关系和内存管理策略可以有效地避免循环引用导致的内存泄漏问题。
6 std::weak_ptr 的引入与使用
std::weak_ptr 是 C11 引入的一种智能指针主要用于解决 std::shared_ptr 之间的循环引用问题以避免内存泄漏。与 std::shared_ptr 不同std::weak_ptr 不控制所指向对象的生命周期它只是对对象的一个弱引用不会增加对象的引用计数。
1引入
在 C 中智能指针被设计用来自动管理动态分配的内存以防止内存泄漏。std::shared_ptr 允许多个智能指针共享同一个对象的所有权当最后一个 std::shared_ptr 被销毁或重置时它所指向的对象才会被删除。然而当两个或多个 std::shared_ptr 对象相互引用形成一个闭环时它们的引用计数将永远无法降至零导致内存无法被释放这就是循环引用问题。
为了解决这个问题C11 引入了 std::weak_ptr。它允许程序员创建对 std::shared_ptr 所管理对象的弱引用但不会增加对象的引用计数。当最后一个 std::shared_ptr 销毁时即使还有 std::weak_ptr 引用该对象对象也会被删除。同时std::weak_ptr 提供了检查其引用的 std::shared_ptr 是否仍然有效的机制从而避免了悬空指针的问题。
2使用
使用 std::weak_ptr 的主要场景是在需要观察 std::shared_ptr 所管理的对象但又不希望控制其生命周期时。例如在解决循环引用问题时可以将其中一个 std::shared_ptr 替换为 std::weak_ptr。
下面是一个使用 std::weak_ptr 解决循环引用的示例
#include memory
#include iostream class B;class A {
public:~A() {std::cout A destroyed\n;}
public:std::weak_ptrB b_ptr; // 使用 weak_ptr 替代 shared_ptr 以解决循环引用问题
};class B {
public:~B() {std::cout B destroyed\n;}
public:std::shared_ptrA a_ptr;
};int main()
{{auto a std::make_sharedA();auto b std::make_sharedB();a-b_ptr b;b-a_ptr a;}// 此时a 和 b 之间存在循环引用但由于 a 使用的是 weak_ptr // 因此当离开作用域时b 会首先被销毁随后 a 也会被销毁避免了内存泄漏。 // 输出 B destroyed 和 A destroyed return 0;
}在这个例子中A 类使用 std::weak_ptr 来引用 B 类的对象而 B 类使用 std::shared_ptr 来引用 A 类的对象。这样当 create_objects 函数结束时即使 a 和 b 之间存在循环引用由于 a 使用的是弱引用因此当 b 的 std::shared_ptr 被销毁时B 的对象会被删除随后 A 的对象也会因为没有任何 std::shared_ptr 引用它而被删除从而避免了内存泄漏。
需要注意的是std::weak_ptr 没有重载 * 和 - 等运算符因此不能单独用来访问对象。如果需要访问 std::weak_ptr 所引用的对象需要首先将其转换为 std::shared_ptr这可以通过调用 std::weak_ptr 的 lock 方法来实现。如果转换成功即 std::weak_ptr 仍然有效lock 方法将返回一个指向对象的 std::shared_ptr否则将返回一个空的 std::shared_ptr。