• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2026-08-07 23:54 Aet 隐藏边栏 |   抢沙发  3 
文章评分 2 次,平均分 5.0

成员模板、变量模板与模板模板参数

成员模板

  1. 普通类和类模板都可以包含成员模板

类模板中的成员模板

  1. 同一个 Box<int> 对象可以调用不同的成员模板特化

类外定义成员模板

不同类模板特化之间的转换

  1. 默认情况下它们是完全不同的类型:

  1. 可以用成员模板实现转换构造

为什么需要友元模板

  1. Box<double>Box<int>是不同的类
  2. 一个类模板特化默认不能访问另一个特化的私有成员,所以声明:

  1. 也可以避免友元,改用公共接口:
    1. 公共接口耦合更低
    2. 友元方式适合底层容器、智能指针等需要高效访问内部状态的实现

成员模板赋值运算符

成员模板不会取代特殊成员函数

  1. 这个构造函数模板不是 Box<T> 的拷贝构造函数

  1. 真正的拷贝构造函数是

  1. 同样,赋值运算符模板不会取代普通复制赋值运算符

  1. 总结
操作 同类型对象 不同特化对象
Box<int> → Box<int> 普通拷贝/移动成员 通常不使用成员模板
Box<int> → Box<double> 类型不同 使用成员模板

容易混淆的细节

  1. 构造函数模板虽然不是复制构造函数,但仍属于“用户声明的构造函数”,因此会阻止编译器自动生成默认构造函数

万能构造函数可能抢走复制构造

  1. 它对非 const 左值可能比隐式拷贝构造函数匹配得更好,导致模板构造函数被意外选中

成员函数模板不能是虚函数

  1. 原因是成员模板可以实例化出任意多个函数,而虚函数表需要在编译类时确定有限的虚函数集合
  2. 可以选择:
    1. 固定参数的虚函数
    2. 类型擦除
    3. std::variant
    4. CRTP 静态多态
    5. 非虚成员模板调用虚拟核心接口

成员函数模板的特化

  1. 针对 bool 完全特化

泛型Lambda 本质上也使用成员模板

  1. 编译器概念上生成一个匿名类

变量模板

  1. 变量模板从 C++14 引入

  1. 每组模板实参产生一个变量特化

  1. 默认模板实参

标准库的 _v

  1. C++11 的类型萃取写法

  1. C++17 使用变量模板简化

头文件中的变量模板

  1. 现代 C++17 代码推荐
    1. inline 允许变量模板特化的定义安全出现在多个翻译单元中

  1. 对于可修改的变量模板

  1. 多线程修改时仍然需要同步

模板模板参数

  1. 普通模板参数接收一个类型

  1. 模板模板参数接收的是“模板本身”

Stack 示例

成员模板类外定义的完整语法

  1. 如果把赋值函数移到类外

模板模板参数的匹配

  1. std::vector 的简化声明类似

  1. 它有两个类型模板参数

  1. C++17 开始,这里的 class 也可以写成 typename

为什么不能直接传 std::array

  1. 它的第二个参数是非类型模板参数std::size_t N

  1. 而模板模板参数要求所有参数都是类型

  1. 不过 std::array 没有 push_back(),因此我们的 Stack 实现仍然不能使用它
    1. 模板签名匹配不代表操作接口匹配

什么时候使用模板模板参数

  1. 适合:
    1. 内部需要用自己的 T 实例化另一个模板
    2. 容器模板注入
    3. Allocator/Policy 模板
    4. Trait 生成器
    5. 元编程中的模板组合

万能引用、引用折叠与完美转发

为什么普通引用不够

  1. 假设目标函数有三个重载

  1. 直接调用

  1. 现在增加包装函数

  1. 核心规则
    1. 任何有名字的变量表达式都是左值,即使变量声明类型是右值引用

std::move 也不能解决所有情况

  1. 如果改成

  1. 所以
    1. 始终当左值传递:不对
    2. 始终 std::move():也不对
    3. 需要根据调用者原本传入的是左值还是右值决定
  2. 这就是 std::forward<T>() 的作用

标准写法

什么是万能引用

  1. 必须满足两个关键条件
    1. 形式基本上是 T&&
    2. T 是本次函数调用中直接推导出来的模板参数
    3. 所以,不是所有 T&& 都是万能引用

  1. 类模板参数已经提前确定

  1. 添加了 const
    1. 这是 const 右值引用,不是万能引用

  1. 不是直接的模板参数
    1. 这里的类型依赖 T,但不是直接的 T&&,也不是转发引用

auto&& 也可以是万能引用

  1. 泛型 Lambda

  1. 范围 for 中也经常看到
    1. 它可以适应容器迭代器返回的引用或代理类型

万能引用的特殊推导规则

  1. 假设

  1. 传入非 const 左值

  1. 传入 const 左值

  1. 传入右值

  1. 传入 std::move

  1. 总结
调用实参 T 形参T&&最终类型
Message 左值 Message& Message&
const Message 左值 const Message& const Message&
Message{} Message Message&&
std::move(message) Message Message&&
const 右值 const Message const Message&&

引用折叠规则

  1. C++ 不允许直接声明引用的引用
  2. 但模板替换、类型别名、decltype 等过程可能间接形成引用的引用,因此需要引用折叠规则
组合 折叠结果
T& & T&
T& && T&
T&& & T&
T&& && T&&

形参类型和表达式值类别是两回事

  1. 如果传入右值

std::move 的本质

  1. std::move() 本身不移动任何资源
    1. 无条件把表达式转换成右值类别,表示“这个对象可以被移动”

  1. 真正是否发生移动,取决于后面调用的构造函数或赋值运算符
    1. 如果类型没有移动构造函数,可能仍会复制

std::forward 的本质

  1. 可以把 std::forward<T>() 简化理解为

  1. 原参数是左值

  1. 原参数是右值

const对象通常移动不了

  1. std::move(text) 的类型是

  1. 而常见移动构造函数是
    1. 它不能接收 const 右值,因为移动通常需要修改源对象

变参完美转发

异常和返回类型也应转发

  1. 更完整的 C++20 调用包装器
    1. requires:目标函数必须可调用
    2. decltype(auto):保留目标函数的精确返回类型,包括引用
    3. noexcept(...):目标调用不抛异常时,包装器也声明不抛异常
    4. std::forward:保留参数值类别

完美转发不等于自动安全

  1. “完美”只表示尽可能保持调用表达式的类型属性,不表示:
    1. 一定不会复制
    2. 一定会移动
    3. 生命周期安全
    4. 线程安全
    5. 所有权正确
    6. 不会发生隐式转换
    7. 不会产生悬空引用

不要对同一个参数重复转发

  1. 如果调用者传入右值,第一次转发可能已经把资源移走,第二次看到的是 moved-from 对象

  1. 更合理的是先决定所有权

花括号初始化列表不能直接推导

  1. 下面通常无法推导

  1. 需要专门重载

  1. 或者显式构造

万能引用构造函数抢占拷贝构造

  1. C++20 约束

异步任务中的生命周期问题

  1. 线程池接口通常写成

  1. 接口入口可以完美转发,但任务会延迟执行,不能简单保存

  1. 一种一次性任务的按值保存方式

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

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

发表评论

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