首页 > 编程语言 >[Effective Modern C++] 条款19笔记 - 为什么deleter的类型是std::unique_ptr类型的一部分,而不是std::shared_ptr的一部分?

[Effective Modern C++] 条款19笔记 - 为什么deleter的类型是std::unique_ptr类型的一部分,而不是std::shared_ptr的一部分?

时间:2024-06-21 17:13:48浏览次数:11  
标签:std Effective 删除 类型 shared unique 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_ptrstd::shared_ptr 在 C++ 中设计上的一些关键区别,特别是在它们如何处理自定义删除器时的行为和接口。让我们深入理解为什么 std::unique_ptr 的类型包含删除器,而 std::shared_ptr 不需要这样做。

std::unique_ptr 的设计与删除器

std::unique_ptr 是一个轻量级的、独占所有权的智能指针。它的设计目的是简单和高效地管理动态分配的对象,确保对象在指针离开作用域时自动释放。以下是它与删除器相关的关键特性:

  1. 类型定义:

    • std::unique_ptr 的类型包括指向的对象类型(T)和删除器类型(D)。这种设计使得删除器类型是 std::unique_ptr 类型的一部分:
      std::unique_ptr<T, D>
      
    • 这样设计的原因是 std::unique_ptr 在编译时完全知道如何处理其管理的对象,包括如何销毁它。
    • 因此,std::unique_ptr 在对象销毁时可以直接调用删除器 D,不需要额外的存储来管理删除器的状态。
  2. 编译期特性:

    • 因为删除器是类型的一部分,所以 std::unique_ptr 可以在编译时确定其大小和行为。
    • 这使得 std::unique_ptr 在管理带有自定义删除器的对象时非常高效,因为删除器的开销在编译时就被固定了。
  3. 灵活性:

    • std::unique_ptr 可以支持无状态的(stateless)删除器,也可以支持有状态的(stateful)删除器。
    • 通过将删除器作为模板参数,std::unique_ptr 可以灵活地支持不同类型的删除器,而无需额外的运行时开销。

std::shared_ptr 的设计与删除器

std::shared_ptr 是一个更复杂的智能指针,设计用于共享所有权的场景。多个 std::shared_ptr 可以共享同一个对象,并在最后一个 std::shared_ptr 被销毁时自动释放对象。以下是它与删除器相关的关键特性:

  1. 类型定义:

    • std::shared_ptr 的类型定义中只包括指向的对象类型(T),而不包括删除器类型:
      std::shared_ptr<T>
      
    • 删除器在 std::shared_ptr 内部被处理为动态存储的部分,而不是类型的一部分。
  2. 动态行为:

    • std::shared_ptr 的核心是共享计数(reference count),它需要在运行时动态管理对象的生命周期。
    • 当创建 std::shared_ptr 时,可以传递一个删除器,但删除器不是 std::shared_ptr 类型的一部分。
    • 删除器被存储在 std::shared_ptr 内部的控制块(control block)中,控制块管理对象的引用计数和删除器。
    • 这种设计允许多个 std::shared_ptr 实例共享相同的对象,即使它们在不同的上下文中使用不同的删除器(尽管这种情况不常见)。
  3. 灵活性与复杂性:

    • 由于删除器是动态管理的,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_ptrstd::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 更适合复杂、需要共享所有权的场景。

标签:std,Effective,删除,类型,shared,unique,ptr
From: https://www.cnblogs.com/nuo-chen/p/18260916

相关文章

  • 【Effective Python教程】(90个有效方法)笔记——第3章:函数——23:用关键字参数来传参(位
    文章目录第3章:函数第23条用关键字参数来传参位置传递参数关键字传递参数位置和关键字传递参数混合使用另外,关键字形式与位置形式也可以混用。下面这四种写法的效果相同:==如果混用,那么位置参数必须出现在关键字参数之前,否则就会出错。==每个参数只能指定一次,不能既通过位......
  • Effective Java 学习总结
    前言EffectiveJava作为Java四大名著之一,聚焦于Java语言习惯和高效的用法。EJ告诉读者如何更好地构建代码,以便代码能够更好地工作;也便于其他人能够理解这些代码,便于修改和改善;程序也会因此变得更加令人愉快,更加优雅。全书共90条,接下来笔者将逐条进行总结。第1条:用......
  • centos7离线升级gcc , 报错:/lib64/libstdc++.so.6: version `CXXABI_1.3.8' not found
     因为需要依赖gcc高版本但是目前服务器版本是4.8.5的然后服务器又是内网所以只能离线升级gcc 分别下载https://ftp.gnu.org/gnu/gcc/gcc-8.3.0/gcc-8.3.0.tar.gzhttps://ftp.gnu.org/pub/gnu/gmp/gmp-6.1.0.tar.bz2https://ftp.gnu.org/gnu/mpc/mpc-1.0.3.tar.gzhttp:......
  • C++11智能指针 unique_ptr、shared_ptr、weak_ptr与定制删除器
    目录智能指针场景引入-为什么需要智能指针?内存泄漏什么是内存泄漏内存泄漏的危害内存泄漏分类如何避免内存泄漏智能指针的使用及原理RAII简易例程智能指针的原理智能指针的拷贝问题智能指针的发展历史std::auto_ptr模拟实现auto_ptr例程:这种方案存在的问题:Boost库中的智能指针......
  • 操作系统B期末复习(STD)
    操作系统1、什么是操作系统基本特征是什么?操作系统是配置在计算机硬件上的第一层软件,是对硬件系统的首次扩充2、PCBTCBFCB相关内容PCB:①基本信息:进程控制块,又叫进程表,是操作系统中最重要的记录型数据结构。记录了操作系统所需的,用于描述进程的当前情况以及管理进程运行的......
  • 何时/如何使用 std::enable_shared_from_this<T>?
    要点回顾继承自std::enable_shared_from_this<T>的类能够在其自身实例中通过std::shared_from_this方法创建一个指向自己的std::shared_ptr<T>智能指针。从一个裸指针创建多个std::shared_ptr<T>实例会造成严重的后果,其行为是未定义的。std::enable_shared_from_this......
  • Large Language Models as Financial Data Annotators: A Study on Effectiveness and
    本文是LLM系列文章,针对《LargeLanguageModelsasFinancialDataAnnotators:AStudyonEffectivenessandEfficiency》的翻译。作为财务数据注释器的大型语言模型:有效性和效率研究摘要引言相关工作数据集实验结果讨论局限性结论摘要由于缺乏领域专......
  • 【类脑计算】突触可塑性模型之Hebbian学习规则和STDP
    1引言突触可塑性(Synapticplasticity)指经验能够修改神经回路功能的能力。特指基于活动修改突触传递强度的能力,是大脑适应新信息的主要调查机制。分为短期和长期突触可塑性,分别作用于不同时间尺度,对感官刺激的短期适应和长期行为改变及记忆存储至关重要。非对称ST......
  • c++11新特性之关键字(关于auto、nullptr)
    1.auto用途:用于编译器自动推断出变量类型,这里列举几种比较典型的情况:(1)自动类型推导autox=10;//x的类型是intautoy=3.14;//y的类型是doubleautoz='c';//z的类型是char(2)与迭代器一起使用:当处理STL容器时,auto可以帮助我们自动推导迭代......
  • C++内联函数、内联函数的概念、内联函数的特性、auto关键字、类型名字的问题、auto使
    文章目录前言一、内联函数1.内联函数概念2.内联函数特性二、auto关键字(C++11)1.类型名字的问题2.auto简介3.auto的使用细则1.auto与指针和引用结合起来用2.auto在同一行定义多个变量4.auto不能推导的场景1.auto不能作为函数的参数2.auto不能直接用来声明数组3......