✏️
CPP-17-STL-Cookbook
  • 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...
  • There's more...

Was this helpful?

  1. 第1章 C++17的新特性

使用constexpr-if简化编译

模板化编程中,通常要以不同的方式做某些事情,比如特化模板类型。C++17带了constexpr-if表达式,可以在很多情况下简化代码。

How to do it...

本节中,我们会实现一个很小的辅助模板类。它能处理不同模板类型的特化,因为它可以在完全不同的代码中,选取相应的片段,依据这些片段的类型对模板进行特化:

  1. 完成代码中的通用部分。在我们的例子中,它是一个简单的类,它的成员函数add,支持对U类型值与T类型值的加法:

    template <typename T>
    class addable
    {
      T val;
    public:
      addable(T v) : val{v} {}
      template <typename U>
      T add(U x) const {
        return val + x;
      }
    };
  2. 假设类型T是std::vector<something>,而类型U是int。这里就有问题了,为整个vector添加整数是为了什么呢?其应该是对vector中的每个元素加上一个整型数。实现这个功能就需要在循环中进行:

    template <typename U>
    T add(U x)
    {
      auto copy (val); // Get a copy of the vector member
      for (auto &n : copy) {
        n += x;
      }
      return copy;
    }
  3. 下一步也是最后一步,将两种方式结合在一起。如果T类型是一个vector,其每个元素都是U类型,择进行循环。如果不是,则进行普通的加法:

    template <typename U>
    T add(U x) const{
        if constexpr(std::is_same<T, std::vector<U>>::value){
            auto copy(val);
            for (auto &n : copy){
                n += x;
            }
            return copy;
        } else {
            return val + x;
        }
    }
  4. 现在就可以使用这个类了。让我们来看下其对不同类型处理的是多么完美,下面的例子中有int,float, std::vector<int>和std::vector<string>:

    addable<int> {1}.add(2); // is 3
    addable<float> {1.f}.add(2); // is 3.0
    addable<std::string> {"aa"}.add("bb"); // is "aabb"
    
    std::vector<int> v{1, 2, 3};
    addable<std::vector<int>> {v}.add(10); // is std::vector<int> {11, 12, 13}
    
    std::vector<std::string> sv{"a", "b", "c"};
    addable<std::vector<std::string>> {sv}.add(std::string{"z"}); // is {"az", "bz", "cz"}

How it works...

新特性constexpr-if的工作机制与传统的if-else类似。不同点就在于前者在编译时进行判断,后者在运行时进行判断。所以,使用constexpr-if的代码在编译完成后,程序的这一部分其实就不会有分支存在。有种方式类似于constexpr-if,那就是#if-#else的预编译方式进行宏替换,不过这种方式在代码的构成方面不是那么优雅。组成constexpr-if的所有分支结构都是优雅地,没有使用分支在语义上不要求合法。

为了区分是向vector的每个元素加上x,还是普通加法,我们使用std::is_same来进行判断。表达式std::is_same<A, B>::value会返回一个布尔值,当A和B为同样类型时,返回true,反之返回false。我们的例子中就写为std::is_same<T, std::vector<U>>::value()(is_same_v = is_same<T, U>::value;),当返回为true时,且用户指定的T为std::vector<X>,之后试图调用add,其参数类型U = X。

当然,在一个constexpr-if-else代码块中,可以有多个条件(注意:a和b也可以依赖于模板参数,并不需要其为编译时常量):

if constexpr(a){
    // do something
} else if constexpr(b){
    // do something else
} else {
    // do something completely different
}

C++17中,很多元编程的情况更容易表达和阅读。

There's more...

这里对比一下C++17之前的实现和添加constexpr-if后的实现,从而体现出这个特性的加入会给C++带来多大的提升:

template <typename T>
class addable{
    T val;
public:
    addable(T v):val{v}{}

    template <typename U>
    std::enable_if_t<!std::is_same<T, std::vector<U>>::value, T> 
    add(U x) const {
        return val + x;
    }

    template <typename U>
    std::enable_if_t<!std::is_same<T, std::vector<U>>::value, std::vector<U>>
    add (U x) const{
        auto copy(val);
        for (auto &n: copy){
            n += x;
        }
        return copy;
    }
};

在没有了constexpr-if的帮助下,这个类看起特别复杂,不像我们所期望的那样。怎么使用这个类呢?

简单来看,这里重载实现了两个完全不同的add函数。其返回值的类型声明,让这两个函数看起里很复杂;这里有一个简化的技巧——表达式,例如std::enable_if_t<condition, type>,如果条件为真,那么就为type类型,反之std::enable_if_t表达式不会做任何事。这通常被认为是一个错误,不过我们能解释为什么什么都没做。

对于第二个add函数,相同的判断条件,但是为反向。这样,在两个实现不能同时为真。

很麻烦是吧,C++17中实现起来就变得简单多了。

Previous构造函数自动推导模板的类型Next只有头文件的库中启用内联变量

Last updated 4 years ago

Was this helpful?

当编译器看到具有相同名称的不同模板函数并不得不选择其中一个时,一个重要的原则就起作用了:替换失败不是错误(, Substitution Failure is not An Error)。这个例子中,就意味着如果函数的返回值来源一个错误的模板表示,无法推断得出,这时编译器不会将这种情况视为错误(和std::enable_if中的条件为false时的状态一样)。这样编译器就会去找函数的另外的实现。

SFINAE