• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2024-06-22 22:41 Aet 隐藏边栏 |   抢沙发  15 
文章评分 1 次,平均分 5.0

模板

概述

  1. C++模板是一个强大的编程工具,使得可以编写通用的、类型安全的代码
  2. 模板主要用于函数和类的泛型编程,允许你定义通用算法和数据结构,然后在需要时使用具体类型进行实例化

使用场景

  1. 通用算法:
    1. 模板允许你编写通用算法,如排序、搜索等,而无需针对每种类型重复实现
  2. 容器类:
    1. 标准模板库(STL)中的容器类(如 std::vectorstd::list 等)都是使用模板实现的,能够存储不同类型的数据
  3. 智能指针:
    1. std::unique_ptrstd::shared_ptr,使用模板实现通用的内存管理

函数模板

概述

  1. 函数模板允许你编写一次函数定义,然后使用不同的类型实例化该函数

类模板

概述

  1. 类模板允许你定义通用的数据结构,然后用不同的类型实例化

模板参数

概述

  1. 模板还可以接受非类型参数,这些参数在编译时是常量

非类型模板参数

概述

  1. 非类型模板参数(Non-Type Template Parameters, NTTP)是在模板中使用的常量值,而不是类型
    1. 这些参数可以是整数、指针、引用、枚举或其他常量表达式
  2. 非类型模板参数允许你在编译时指定一些固定的值,从而使模板实例化时可以根据这些值生成不同的代码

非类型模板参数的常见类型

  1. 整数:
    1. 常见的整数类型,如 intcharbool
  2. 指针:
    1. 指向对象或函数的指针
  3. 引用:
    1. 对对象的引用
  4. 枚举:
    1. 枚举常量

非类型模板参数的限制

  1. 必须是常量表达式:
    1. 非类型模板参数必须是编译时可以确定的常量表达式
  2. 类型限制:
    1. 只能是整数类型、指针类型、引用类型、枚举类型或常量表达式

应用场景

  1. 固定大小的数组或容器:
    1. 如下面的数组模板示例
  2. 静态配置:
    1. 在编译时确定某些配置参数,从而生成特定的代码路径
  3. 编译时多态:
    1. 根据非类型模板参数生成不同的代码实现,实现编译时的多态性

高级用法

  1. 非类型模板参数可以与模板元编程相结合,实现编译时的常量计算
    1. 见模板元编程示例

示例

  1. 整数非类型模板参数
    1. 这个示例中,Array 模板使用了一个 int 类型的非类型模板参数 Size 来指定数组的大小

  1. 指针非类型模板参数
    1. 这个示例中,callFunction 模板使用了一个指向 void() 类型函数的指针作为非类型模板参数

  1. 引用非类型模板参数
    1. 这个示例中,ReferenceWrapper 模板使用了一个 int 类型的引用作为非类型模板参数

模板特化

概述

  1. 在C++模板编程中,特化是指为某些特定类型或条件提供不同于通用模板的实现

    1. 模板特化允许你为特定类型提供特殊实现
  2. 特化分为两种:

    1. 全特化(全模板特化)
    2. 偏特化(部分特化)

全特化(全模板特化)

  1. 全特化(Explicit Specialization)是为某个特定类型提供完全不同的实现
  2. 全特化通常用于函数模板和类模板
  3. 函数模板的全特化示例:
    1. 这个例子中,add 函数模板有一个全特化版本,用于处理 const char* 类型的参数

  1. 类模板的全特化示例:
    1. 这个例子中,TypePrinter 类模板有两个全特化版本,分别用于处理 intdouble 类型

偏特化(部分特化)

  1. 偏特化(Partial Specialization)是为某些特定的模板参数模式提供不同的实现
  2. 偏特化通常用于类模板,允许为部分类型参数提供特化版本
  3. 类模板的偏特化示例:
    1. 这个例子中,Pair 类模板有一个部分特化版本,当第二个模板参数是 int 时,使用特化版本

对比全特化与偏特化

  1. 全特化:
    1. 为特定类型提供完全不同的实现。
    2. 使用场景:
      当需要为某个特定类型提供特殊处理逻辑时
    3. 适用于函数模板和类模板
  2. 偏特化:
    1. 为某些特定的模板参数模式提供不同的实现。
    2. 使用场景:
      当需要为一组类型模式提供特殊处理逻辑时,特别是当模板参数较多,且只有部分参数需要特化时
    3. 主要用于类模板

模板和友元

概述

  1. 友元(friend)在模板编程中可以用来访问类的私有成员和保护成员
    1. 友元声明可以用于函数、类和模板,使得它们能够访问指定类的非公有成员

友元在模板中的应用

  1. 函数模板作为类模板的友元
    1. 在类模板中,可以声明一个函数模板作为其友元,使得该函数模板可以访问类模板的私有成员
    2. 示例:
      这个例子中,print 函数模板被声明为 MyClass 类模板的友元,从而能够访问 MyClass 的私有成员 value_

  1. 类模板作为类模板的友元:
    1. 可以声明一个类模板作为另一个类模板的友元,使得友元类模板可以访问该类模板的私有成员

  1. 具体类型的友元:
    1. 你还可以为类模板的具体实例化声明友元
    2. 这在需要为特定类型提供特殊访问权限时非常有用

注意

  1. 访问控制:
    1. 友元关系可以破坏类的封装性,应谨慎使用,仅在必要时使用
  2. 代码维护:
    1. 友元关系增加了类之间的耦合度,可能影响代码的可维护性和可读性
  3. 模板编程:
    1. 在模板编程中使用友元需要注意友元声明的位置和模板参数的正确匹配

总结

  1. 用于定义能够访问类模板私有成员的函数或类

模板元编程

概述

  1. 使用模板进行编译时计算,如计算常量表达式或生成类型列表

原理

  1. 所谓编译时计算,是不是就是利用这种编程方法,在编译期完成对一些东西的计算,编译期生成汇编指令的时候,把某些地方的调用直接替换成编译期的计算结果?
    1. 所谓编译时计算(compile-time computation)是指在编译阶段而不是运行阶段进行计算
    2. 模板元编程(Template Metaprogramming)利用C++模板的特性,在编译期执行一些计算,并将结果用于生成代码
    3. 这种方法在编译阶段就完成了计算,避免了运行时的开销
  2. 编译时计算的核心在于编译器在编译代码时解析模板,进行必要的计算,并将计算结果直接嵌入生成的目标代码中
    1. 这意味着在编译生成汇编指令时,编译器已经知道并可以利用计算结果,从而优化代码

模板元编程的应用

  1. 编译时常量计算:
    1. 如阶乘、斐波那契数列等
  2. 类型属性查询:
    1. std::is_integralstd::is_pointer 等类型特性查询
    2. 这个示例中,std::is_pointer 是一个编译时常量表达式,可以在编译时确定类型属性,从而在编译期生成不同的代码

  1. 静态断言:
    1. 利用 static_assert 在编译期进行条件检查
      静态断言可以用于在编译期检查条件,如果条件不满足,编译器会生成错误

  1. 元函数:
    1. std::conditionalstd::enable_if 等元函数用于模板条件编译和SFINAESubstitution Failure Is Not An Error

SFINAE

概述

  1. SFINAESubstitution Failure Is Not An Error):一种模板编程技巧,用于实现条件编译
  2. 它允许编译器在模板参数替换失败时不产生编译错误,而是尝试其他重载或特化
    1. 这一特性使得模板元编程变得非常强大和灵活,尤其是在编写类型安全的库和泛型算法时

原理

  1. 当编译器在实例化模板时,如果某些模板参数导致了无效的模板定义(如无效类型或无效表达式),编译器不会立即报错,而是会忽略这个特定的模板实例化,并继续查找其他匹配的模板
  2. 这种机制允许编译器根据不同的类型或条件选择合适的模板实例化

用途

  1. 函数重载:
    1. 根据模板参数的类型或属性选择不同的函数重载
  2. 模板特化:
    1. 根据模板参数的类型或属性选择不同的模板特化
  3. 类型检查:
    1. 在编译期进行类型检查和条件编译

实现方式

  1. 通过函数重载实现SFINAE
    1. 上面概述的示例代码
  2. 通过类模板特化实现SFINAE
    1. std::enable_if 用于在编译期选择合适的模板特化

高级用法

  1. 多重SFINAE条件

  1. std::void_t
    1. std::void_t是一个在C++17中引入的工具,简化了SFINAE的实现
    2. 它用于检测表达式的有效性
    3. 示例:
      这个示例中,HasTypeMember 用于检测一个类是否有名为Type的类型成员。std::void_t 简化了这一检测过程

SFINAE工具

std::enable_if

  1. C++ 标准库中的一个元编程工具,用于在编译时根据条件选择模板函数或类的有效性
    1. 通常与 SFINAESubstitution Failure Is Not An Error)机制一起使用,以实现条件编译和类型约束
  2. 定义
    1. 定义在头文件 <type_traits>
    2. 当模板参数 Btrue 时,enable_if 提供一个名为 type 的成员类型定义,它的值是 T
    3. 当模板参数 Bfalse 时,enable_if 没有成员类型 type,因此模板实例化失败,从而实现 SFINAE
    4. 如果没有显式指定 T,则默认值为 void

  1. 没有指定类型,默认void,指定了,用指定的类型:

std::is_same

  1. 用于判断两个类型是否相同

std::is_integral

  1. 用于判断类型是否为整型

std::is_floating_point

  1. 用于判断类型是否为浮点型

std::void_t

  1. 用于简化基于 SFINAE 的检测机制
  2. 它的作用是将一系列类型转换为 void,用于在编译时进行条件检查

  1. 解析如下:
  2. template<typename, typename = std::void_t<>>
    1. 定义了一个模板结构 has_type_member,并且它接受两个模板参数
    2. 第一个模板参数是一个类型参数,未命名
    3. 第二个模板参数是一个类型参数,默认值为 std::void_t<>
    4. 在这个上下文中,std::void_t<> 实际上是 void
    5. 为什么?
    6. 因为,在 std::void_t 中不传递任何类型参数时,即使用 std::void_t<>,相当于将一个空参数包传递给 std::void_t,这意味着 std::void_t<> 就是 void
    7. 如果传了类型参数给std::void_t呢?
    8. 传递类型参数给 std::void_t 时,using void_t = void; 仍然会生效
      std::void_t 的作用是将任何传递给它的类型参数都转换为 void
      无论传递什么类型参数,std::void_t 都会返回 void
  3. struct has_type_member : std::false_type {};
    1. 定义了一个模板结构 has_type_member 的默认版本
    2. 默认版本继承自 std::false_type,这意味着在没有其他特化的情况下,has_type_member 默认值为 false
  4. struct has_type_member<T, std::void_t<typename T::type>> : std::true_type {};
    1. 定义了 has_type_member 的特化版本,当 T 类型有一个名为 type 的成员类型时,该特化才有效
  5. std::void_t<typename T::type>
    1. 如果 T 有一个成员类型 type,则 std::void_t 将成功替换并生成 void 类型
    2. 如果 T 没有 type 成员类型,则替换失败,特化版本不适用
  6. : std::true_type
    1. 如果替换成功,特化版本继承自 std::true_type,表示值为 true
  7. 上面代码结合在一起,我们可以使用 has_type_member 检测一个类型是否具有特定的成员类型

std::is_convertible

  1. 用于判断一个类型是否可以隐式转换为另一个类型

std::is_base_of

  1. 用于判断一个类型是否是另一个类型的基类

std::conditional

  1. 根据条件选择一种类型

std::is_arithmetic

  1. 用于判断一个类型是否为算术类型(整数或浮点数)

概念

概述

  1. 概念(C++20 引入):用于约束模板参数,使得模板编程更加安全和易读
  2. 允许更明确地定义模板的适用范围,提高模板代码的可读性和错误信息的可理解性

定义概念

  1. 模板参数 Ttemplate<typename T> 声明了一个模板参数 T,表示我们将对类型 T 进行约束检查
  2. 概念 Incrementableconcept Incrementable 声明了一个概念,类似于一个编译时的断言,用于检查类型 T 是否满足特定条件
  3. requires 子句requires(T t) 是一个要求表达式,表示接下来的条件必须满足
  4. 要求表达式{ t++ } -> std::same_as<T&>;
    1. { t++ }:要求类型 T 的对象 t 可以进行后置自增操作(即 t++ 必须是有效的)
    2. -> std::same_as<T&>:后置自增操作的结果类型必须是 T&。也就是说,t++ 的返回类型必须是对 T 类型的左值引用

使用概念约束模板参数

  1. template<Incrementable T> 声明了模板参数 T,并用概念 Incrementable 对其进行约束
    1. 这意味着只有满足 Incrementable 概念的类型才能用于这个模板

本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享