C++17 STL Cook Book
  • Introduction
  • 前言
  • 关于本书
  • 各章梗概
  • 第1章 C++17的新特性
    • 使用结构化绑定来解包绑定的返回值
    • 将变量作用域限制在if和switch区域内
    • 新的括号初始化规则
    • 构造函数自动推导模板的类型
    • 使用constexpr-if简化编译
    • 只有头文件的库中启用内联变量
    • 使用折叠表达式实现辅助函数
  • 第2章 STL容器
    • 擦除/移除std::vector元素
    • 以O(1)的时间复杂度删除未排序std::vector中的元素
    • 快速或安全的访问std::vector实例的方法
    • 保持对std::vector实例的排序
    • 向std::map实例中高效并有条件的插入元素
    • 了解std::map::insert新的插入提示语义
    • 高效的修改std::map元素的键值
    • std::unordered_map中使用自定义类型
    • 过滤用户的重复输入,并以字母序将重复信息打印出——std::set
    • 实现简单的逆波兰表示法计算器——std::stack
    • 实现词频计数器——std::map
    • 实现写作风格助手用来查找文本中很长的句子——std::multimap
    • 实现个人待办事项列表——std::priority_queue
  • 第3章 迭代器
    • 建立可迭代区域
    • 让自己的迭代器与STL的迭代器兼容
    • 使用迭代适配器填充通用数据结构
    • 使用迭代器实现算法
    • 使用反向迭代适配器进行迭代
    • 使用哨兵终止迭代
    • 使用检查过的迭代器自动化检查迭代器代码
    • 构建zip迭代适配器
  • 第4章 Lambda表达式
    • 使用Lambda表达式定义函数
    • 使用Lambda为std::function添加多态性
    • 并置函数
    • 通过逻辑连接创建复杂谓词
    • 使用同一输入调用多个函数
    • 使用std::accumulate和Lambda函数实现transform_if
    • 编译时生成笛卡尔乘积
  • 第5章 STL基础算法
    • 容器间相互复制元素
    • 容器元素排序
    • 从容器中删除指定元素
    • 改变容器内容
    • 在有序和无序的vector中查找元素
    • 将vector中的值控制在特定数值范围内——std::clamp
    • 在字符串中定位模式并选择最佳实现——std::search
    • 对大vector进行采样
    • 生成输入序列的序列
    • 实现字典合并工具
  • 第6章 STL算法的高级使用方式
    • 使用STL算法实现单词查找树类
    • 使用树实现搜索输入建议生成器
    • 使用STL数值算法实现傅里叶变换
    • 计算两个vector的误差和
    • 使用ASCII字符曼德尔布罗特集合
    • 实现分割算法
    • 将标准算法进行组合
    • 删除词组间连续的空格
    • 压缩和解压缩字符串
  • 第7章 字符串, 流和正则表达
    • 创建、连接和转换字符串
    • 消除字符串开始和结束处的空格
    • 无需构造获取std::string
    • 从用户的输入读取数值
    • 计算文件中的单词数量
    • 格式化输出
    • 使用输入文件初始化复杂对象
    • 迭代器填充容器——std::istream
    • 迭代器进行打印——std::ostream
    • 使用特定代码段将输出重定向到文件
    • 通过集成std::char_traits创建自定义字符串类
    • 使用正则表达式库标记输入
    • 简单打印不同格式的数字
    • 从std::iostream错误中获取可读异常
  • 第8章 工具类
    • 转换不同的时间单位——std::ratio
    • 转换绝对时间和相对时间——std::chrono
    • 安全的标识失败——std::optional
    • 对元组使用函数
    • 使用元组快速构成数据结构
    • 将void*替换为更为安全的std::any
    • 存储不同的类型——std::variant
    • 自动化管理资源——std::unique_ptr
    • 处理共享堆内存——std::shared_ptr
    • 对共享对象使用弱指针
    • 使用智能指针简化处理遗留API
    • 共享同一对象的不同成员
    • 选择合适的引擎生成随机数
    • 让STL以指定分布方式产生随机数
  • 第9章 并行和并发
    • 标准算法的自动并行
    • 让程序在特定时间休眠
    • 启动和停止线程
    • 打造异常安全的共享锁——std::unique_lock和std::shared_lock
    • 避免死锁——std::scoped_lock
    • 同步并行中使用std::cout
    • 进行延迟初始化——std::call_once
    • 将执行的程序推到后台——std::async
    • 实现生产者/消费者模型——std::condition_variable
    • 实现多生产者/多消费者模型——std::condition_variable
    • 并行ASCII曼德尔布罗特渲染器——std::async
    • 实现一个小型自动化并行库——std::future
  • 第10章 文件系统
    • 实现标准化路径
    • 使用相对路径获取规范的文件路径
    • 列出目录下的所有文件
    • 实现一个类似grep的文本搜索工具
    • 实现一个自动文件重命名器
    • 实现一个磁盘使用统计器
    • 计算文件类型的统计信息
    • 实现一个工具:通过符号链接减少重复文件,从而控制文件夹大小
Powered by GitBook
On this page
  • How to do it...
  • How it works...

Was this helpful?

  1. 第8章 工具类

自动化管理资源——std::unique_ptr

C++11之后,STL提供了新的智能指针,能对动态内存进行跟踪管理。C++11之前,C++中也有一个智能指针auto_ptr,也能对内存进行管理,但是很容易被用错。

不过,使用C++11添加的智能指针的话,我们就很少需要使用到new和delete操作符。智能指针是自动化内存管理的一个鲜活的例子。当我们使用unique_ptr来动态分配对象,基本上不会遇到内存泄漏,因为在析构时会自动的为其所拥有内存使用delete操作。

唯一指针表达了其对对象指针的所有权,当对这段内存不在进行使用时,我们会将相关的对象所具有的内存进行释放。这个类将让我们永远远离内存泄漏(智能指针还有shared_ptr和weak_ptr,不过本节中,我们只关注于unique_ptr)。其不会多占用空间,并且不会影响运行时性能,这相较于原始的裸指针和手动内存管理来说十分便捷。(当我们对相应的对象进行销毁后,其内部的裸指针将会被设置为nullptr)。

本节中,我们将来看一下unique_ptr如何使用。

How to do it...

我们将创建一个自定义的类型,在构造和析构函数中添加一些调试打印信息,之后展示unique_ptr如何对内存进行管理。我们将使用unique指针,并使用动态分配的方式对其进行实例化:

  1. 包含必要的头文件,并声明所使用的命名空间:

    #include <iostream>
    #include <memory>
    
    using namespace std;
  2. 我们将实现一个小类型,后面会使用unque_ptr对其实例进行管理。其构造函数和析构函数都会在终端上打印相应的信息,所以之后的自动删除中,我们会看到相应输出的打印:

    class Foo
    {
    public:
        string name;
    
        Foo(string n)
            : name{move(n)}
        { cout << "CTOR " << name << '\n'; }
    
        ~Foo() { cout << "DTOR " << name << '\n'; }
    };
  3. 为了了解函数对唯一指针在作为参数传入函数的限制,我们可以实现一个这样的函数。其能处理一个Foo类型实例,并能将其名称进行打印。注意,unique指针是非常智能的,其无额外开销,并且类型安全,也可以为null。这就意味着我们仍然要在解引用之前,对指针进行检查:

    void process_item(unique_ptr<Foo> p)
    {
        if (!p) { return; }
    
        cout << "Processing " << p->name << '\n';
    }
  4. 主函数中,我们将开辟一个代码段,在堆上创建两个Foo对象,并且使用unique指针对内存进行管理。我们显式的使用new操作符创建第一个对象实例,并且将其用来创建unique_ptr<Foo>变量p1。我们通过make_unique<Foo>的调用来创建第二个unique指针p2,我们直接传入参数对Foo实例进行构建。这种方式更加的优雅,因为我们使用auto类型对类型进行推理,并且能在第一时间对对象进行访问,并且其已经使用unique_ptr进行管理:

    int main()
    {
        {
            unique_ptr<Foo> p1 {new Foo{"foo"}};
            auto p2 (make_unique<Foo>("bar"));
        }
  5. 离开这个代码段时,所创建的对象将会立即销毁,并且将内存进行释放。让我们来看一下process_item函数和如何使用unique_ptr。当创建一个新的Foo实例时,其就会被unique_ptr进行管理,然后参数的生命周期就在这个函数中。当process_item返回时,这个对象就会被销毁:

        process_item(make_unique<Foo>("foo1"));
  6. 如将已经存在的对象传入process_item函数,就需要将指针的所有权进行转移,因为函数需要使用unique_ptr作为输入参数,这就会有一次拷贝。但是,unique_ptr是无法进行拷贝的,其只能移动。现在让我们来创建两个Foo对象,并且将其中一个移动到process_item函数中。通过对输出的查阅,我们可以了解到foo2在process_item返回时会被析构,因为其所有权已经被转移。foo3将会持续留存于主函数中,直到主函数返回时才进行析构:

        auto p1 (make_unique<Foo>("foo2"));
        auto p2 (make_unique<Foo>("foo3"));
    
        process_item(move(p1));
    
        cout << "End of main()\n";
    }
  7. 编译并运行程序。首先,我们将看到foo和bar的构造和析构的输出,离开代码段时就被销毁。我们要注意的是,销毁的顺序与创建的顺序相反。下一个构造的就是foo1,其在对process_item调用时进行创建。当函数返回时,其就会被立即销毁。然后,我们会创建foo2和foo3。因为之前转移了指针的所有权,foo2会在process_item函数调用返回时被立即销毁。另一个元素foo3将会在主函数返回时进行销毁:

    $ ./unique_ptr
    CTOR foo
    CTOR bar
    DTOR bar
    DTOR foo
    CTOR foo1
    Processing foo1
    DTOR foo1
    CTOR foo2
    CTOR foo3
    Processing foo2
    DTOR foo2
    End of main()
    DTOR foo3

How it works...

使用std::unique_ptr来处理堆上分配的对象非常简单。我们初始化unique指针之后,其就会指向对应的对象,这样程序就能自动的对其进行释放操作。

当我们将unique指针赋予一些新指针时,其就会先删除原先指向的对象,然后再存储新的指针。一个unique指针变量x,我们可以使用x.reset()将其目前所指向的对象进行销毁,然后在指向新的对象。另一种等价方式:x = new_pointer与x.reset(new_pointer)的方式等价。

Note:

的确只有一种方式对unique_ptr所指向对象的内存进行释放,那就是使用成员函数release,但这种方式并不推荐使用。

解引用之前,需要对指针进行检查,并且其能使用于裸指针相同的方式进行运算。条件语句类似于if (p){...}和if (p != nullptr){...},这与我们检查裸指针的方式相同。

解引用一个unique指针可以通过get()函数完成,其会返回一个指向对应对象的裸指针,并且可以直接进行解引用。

unique_ptr有一个很重要的特性——实例无法进行拷贝,只能移动。这就是我们会将已经存在的unique指针的所有权转移到process_item参数的原因。当我们想要拷贝unique指针时,就意味着两个unique指针指向相应的对象,这与该指针的设计理念不符,所以unique指针对其指向对象的所有权必须唯一。

Note:

对于其他的数据类型,由于智能指针的存在,所以很少使用new和delete对其进行手动操作。尽可能的使用智能指针!特别是unqiue_ptr,其在运行时无任何额外开销。

Previous存储不同的类型——std::variantNext处理共享堆内存——std::shared_ptr

Last updated 6 years ago

Was this helpful?