为什么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
更适合复杂、需要共享所有权的场景。