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章 工具类

对共享对象使用弱指针

本节和shared_ptr有关,我们已经了解了如何使用共享指针。和unique_ptr一样,其提升了C++对动态分配对象的管理能力。

拷贝shared_ptr时,我们会将内部计数器加1。当持有共享指针的拷贝时,其指向的对象则不会被删除。但是,当使用某种弱指针时,其能像普通指针一样对指向对象进行操作,但依旧能让所指向对象被销毁?然而,销毁之后我们应该如何确定对象是否存在呢?

这种情况下weak_ptr就是我们最佳的选择。其相对于unique_ptr和shared_ptr来说有些复杂,但是在本节随后的内容中,我们将会对其进行使用。

How to do it...

我们将使用shared_ptr对一个实例进行管理,然后我们将weak_ptr混入其中,从而了解对weak_ptr的操作对智能指针的内存处理有何影响:

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

    #include <iostream>
    #include <iomanip>
    #include <memory>
    
    using namespace std;
  2. 接下来,我们将实现一个类,将在析构函数的实现中进行打印。当类型被析构时,我们可以从打印输出进行判断:

    struct Foo {
        int value;
    
        Foo(int i) : value{i} {}
        ~Foo() { cout << "DTOR Foo " << value << '\n'; }
    };
  3. 让我们来实现一个函数用于对弱指针的信息进行打印,这样我们就可以了解弱指针不同指向时的状态。expired成员函数会告诉我们,弱指针指向的对象是否依旧存在,因为使用弱指针持有这个对象并无法让其生命周期延长!use_count计数器告诉我们,当前shared_ptr实例中对象的引用次数:

    void weak_ptr_info(const weak_ptr<Foo> &p)
    {
        cout << "---------" << boolalpha
            << "\nexpired: " << p.expired()
            << "\nuse_count: " << p.use_count()
            << "\ncontent: ";
  4. 当我们要访问一个实际对象时,需要调用lock函数,会返回一个指向对象的共享指针。当对象不存在时,返回的共享指针则是一个空指针。我们将对其进行检查,然后对其进行访问:

        if (const auto sp (p.lock()); sp) {
            cout << sp->value << '\n';
        } else {
            cout << "<null>\n";
        }
    }
  5. 主函数中实例化一个空的弱指针,并且对其内容进行打印:

    int main()
    {
        weak_ptr<Foo> weak_foo;
        weak_ptr_info(weak_foo);
  6. 新的代码段中,使用Foo类实例化了一个共享指针,再将其拷贝给弱指针。需要注意的是,这个操作并不会对共享指针的引用计数有任何影响。其引用计数依旧为1,因为只有共享指针对其具有所有权:

        {
            auto shared_foo (make_shared<Foo>(1337));
            weak_foo = shared_foo;
  7. 离开代码段前,我们对弱指针的状态进行打印;离开时候,再打印一次。虽然弱指针依旧指向Foo的对象,但是Foo实例还是会在离开代码段时立即被销毁:

            weak_ptr_info(weak_foo);
        }
    
        weak_ptr_info(weak_foo);
    }
  8. 编译并运行程序,就会看到weak_ptr_info函数的输出。第一次,是弱指针为空的时候。第二次,是其指向我们创建的Foo实例,并在弱指针锁定后对其进行解引用。第三次调用之前,我们离开了内部代码区域,会触发Foo类型的析构。之后,我们就无法通过弱指针获取已经删除的Foo对象,并且在这时弱指针也意识到,原先指向的对象已经不存在了:

    $ ./weak_ptr
    ---------
    expired: true
    use_count: 0
    content: <null>
    ---------
    expired: false
    use_count: 1
    content: 1337
    DTOR Foo 1337
    ---------
    expired: true
    use_count: 0
    content: <null>

How it works...

弱指针为我们提供了一种指向共享指针对象,但不会增加其引用计数的方式。Okay,一个裸指针也可以做这样的事,不过裸指针无法告诉我们其是否处于悬垂的状态,而弱指针可以!

为了能更好的了解弱指针为共享指针添加的功能,我们画了一张图供大家参考:

流程与共享指针的图类似。第1步中,我们有两个共享指针和一个弱指针,都指向Foo类型的实例。虽然有三个指针指向这个对象,但是其共享指针引用数依旧为2,弱指针在控制块有属于自己的计数器。第2和3步中,共享指针的实例被销毁,这步将会让引用计数归0。第4步,Foo对象也被销毁了,不过控制块依旧存在。因为弱指针依旧需要控制块来对其是否悬垂进行判断。只有当最后一个指向对象的弱指针被销毁,那么控制块才会被销毁。

也可以说处于悬垂状态的弱指针是无效的。为了对这个属性进行检查,我们可以调用weak_ptr指针的expired成员函数,其将会为我们返回一个布尔值。当其返回true时,我们就不能对这个弱指针进行解引用,因为其说明这个对象已经不存在了。

为了对弱指针解引用,我们需要调用lock()函数。这是种安全和方便的方法,因为函数返回给我们一个共享指针。当持有这个共享指针时,我们对其进行了锁定,所以这时对象的计数器无法进行变化。lock()之后,对象被删除,我们将会得到一个空的共享指针。

Previous处理共享堆内存——std::shared_ptrNext使用智能指针简化处理遗留API

Last updated 6 years ago

Was this helpful?