[Effective Modern C++] 条款19笔记 - 为什么deleter的类型是std::unique_ptr类型的一部分,而不是std::shared_ptr的一部分?
为什么deleter的类型是std::unique_ptr类型的一部分,而不是std::shared_ptr的一部分?
std::unique_ptr<Widget, decltype(loggingDel) > upw(new Widget, loggingDel);
std::shared_ptr<Widget> upw(new Widget, loggingDel);
这个问题涉及到 std::unique_ptr 和 std::shared_ptr 在 C++ 中设计上的一些关键区别,特别是在它们如何处理自定义删除器时的行为和接口。让我们深入理解为什么 std::unique_ptr 的类型包含删除器,而 std::shared_ptr 不需要这样做。
std::unique_ptr 的设计与删除器
std::unique_ptr 是一个轻量级的、独占所有权的智能指针。它的设计目的是简单和高效地管理动态分配的对象,确保对象在指针离开作用域时自动释放。以下是它与删除器相关的关键特性:
-
类型定义:
std::unique_ptr的类型包括指向的对象类型(T)和删除器类型(D)。这种设计使得删除器类型是std::unique_ptr类型的一部分:std::unique_ptr<T, D>- 这样设计的原因是
std::unique_ptr在编译时完全知道如何处理其管理的对象,包括如何销毁它。 - 因此,
std::unique_ptr在对象销毁时可以直接调用删除器D,不需要额外的存储来管理删除器的状态。
-
编译期特性:
- 因为删除器是类型的一部分,所以
std::unique_ptr可以在编译时确定其大小和行为。 - 这使得
std::unique_ptr在管理带有自定义删除器的对象时非常高效,因为删除器的开销在编译时就被固定了。
- 因为删除器是类型的一部分,所以
-
灵活性:
std::unique_ptr可以支持无状态的(stateless)删除器,也可以支持有状态的(stateful)删除器。- 通过将删除器作为模板参数,
std::unique_ptr可以灵活地支持不同类型的删除器,而无需额外的运行时开销。
std::shared_ptr 的设计与删除器
std::shared_ptr 是一个更复杂的智能指针,设计用于共享所有权的场景。多个 std::shared_ptr 可以共享同一个对象,并在最后一个 std::shared_ptr 被销毁时自动释放对象。以下是它与删除器相关的关键特性:
-
类型定义:
std::shared_ptr的类型定义中只包括指向的对象类型(T),而不包括删除器类型:std::shared_ptr<T>- 删除器在
std::shared_ptr内部被处理为动态存储的部分,而不是类型的一部分。
-
动态行为:
std::shared_ptr的核心是共享计数(reference count),它需要在运行时动态管理对象的生命周期。- 当创建
std::shared_ptr时,可以传递一个删除器,但删除器不是std::shared_ptr类型的一部分。 - 删除器被存储在
std::shared_ptr内部的控制块(control block)中,控制块管理对象的引用计数和删除器。 - 这种设计允许多个
std::shared_ptr实例共享相同的对象,即使它们在不同的上下文中使用不同的删除器(尽管这种情况不常见)。
-
灵活性与复杂性:
- 由于删除器是动态管理的,
std::shared_ptr可以更灵活地处理不同类型的删除器。 - 但是,这也引入了更多的复杂性和潜在的运行时开销,因为控制块需要管理删除器的生命周期。
- 由于删除器是动态管理的,
为什么 std::unique_ptr 的删除器是类型的一部分,而 std::shared_ptr 不是?
-
std::unique_ptr:- 设计目标是轻量级、高效的内存管理,它在编译时确定所有的行为,因此将删除器作为类型的一部分,使得它可以在最小的开销下管理对象。
- 这种设计避免了额外的运行时存储和开销,使得
std::unique_ptr尤其适合于不需要共享所有权的场景。
-
std::shared_ptr:- 设计目标是管理共享所有权,允许多个指针指向同一个对象,并在最后一个指针销毁时释放对象。
- 由于
std::shared_ptr必须动态管理引用计数和对象的生命周期,删除器被存储在控制块中,使得不同的std::shared_ptr实例可以共享同一个控制块,即使删除器不同。 - 这种设计虽然引入了更多的复杂性和开销,但提供了更大的灵活性,允许在共享所有权的情况下安全地管理对象。
示例说明
以下是分别使用 std::unique_ptr 和 std::shared_ptr 的示例,展示了如何处理自定义删除器:
std::unique_ptr 示例:
#include <memory>
#include <iostream>
struct Widget {
~Widget() { std::cout << "Widget destroyed\n"; }
};
// 自定义删除器
auto loggingDel = [](Widget* p) {
std::cout << "Deleting Widget with custom deleter\n";
delete p;
};
int main() {
// 删除器是类型的一部分
std::unique_ptr<Widget, decltype(loggingDel)> upw(new Widget, loggingDel);
// 使用 std::unique_ptr 时,类型包含了删除器的信息
return 0;
}
- 在这个示例中,
std::unique_ptr<Widget, decltype(loggingDel)>明确了删除器类型是decltype(loggingDel)。 - 由于删除器是类型的一部分,
std::unique_ptr可以在编译时确定其行为和大小。
std::shared_ptr 示例:
#include <memory>
#include <iostream>
struct Widget {
~Widget() { std::cout << "Widget destroyed\n"; }
};
// 自定义删除器
auto loggingDel = [](Widget* p) {
std::cout << "Deleting Widget with custom deleter\n";
delete p;
};
int main() {
// 删除器不在类型定义中,而是在实例化时指定
std::shared_ptr<Widget> spw(new Widget, loggingDel);
// 使用 std::shared_ptr 时,删除器是控制块的一部分,不是类型的一部分
return 0;
}
- 在这个示例中,
std::shared_ptr<Widget>类型只定义了对象类型Widget,删除器在实例化时传递。 std::shared_ptr动态地将删除器存储在控制块中,这样可以管理对象的生命周期,即使有多个共享所有权的实例。
总结
std::unique_ptr将删除器作为类型的一部分,这使得它在管理对象时非常高效,具有最小的运行时开销。std::shared_ptr动态地管理删除器,这赋予了它更大的灵活性,以便处理共享所有权的复杂场景,尽管这增加了一些运行时开销。
这种设计上的区别使得 std::unique_ptr 更适合简单、非共享所有权的场景,而 std::shared_ptr 更适合复杂、需要共享所有权的场景。

浙公网安备 33010602011771号