• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2025-11-04 14:35 Aet 隐藏边栏 |   抢沙发  7 
文章评分 2 次,平均分 5.0

概述

  1. 首先,stack是一个容器适配器
  2. 理论上,任何提供了 push_back(), pop_back(), back()等操作的序列容器都可以作为 stack的底层容器

为什么默认是deque

  1. deque之所以成为默认选择,是因为它在以下几个方面取得了最佳平衡:
  2. 相对于vector
    1. deque的主要优势在于高效且低成本的动态扩容
    2. vector在尾部空间不足时,需要申请一块更大的内存,然后将所有元素整体搬迁过去,这个操作的时间复杂度是 O(n)
    3. deque采用分段连续的结构,只需要新增一个内存块(buffer)并将其指针添加到中控器(map)中即可,开销要小得多
  3. 相对于list
    1. deque的主要优势在于更高的内存利用率和缓存友好性
    2. list的每个元素都存储在两个指针(前驱和后继),内存开销较大,且节点在内存中不连续,容易造成内存碎片,CPU缓存命中率较低
    3. deque的元素存储在连续的内存块中,虽然块与块之间不连续,但块内是连续的,这在保证高效操作的同时,提供了更好的内存局部性

灵活性:其他容器

  1. 尽管 deque是默认选择,但STL的适配器设计给予了充分的灵活性

stack

设计

类型推导指引

  1. 上述cpp17推导指引1
    1. _Is_allocator<_Container>::value用于判断类型_Container是否为分配器(Allocator
    2. !_Is_allocator<_Container>::value表示:只有当_Container不是分配器类型时,这个推导指引才参与重载决议,有效
      为了避免混淆:用一个分配器对象来构造一个栈,还是想用这个分配器作为底层容器
      此约束明确排除了后一种情况
    3. -> stack<typename _Container::value_type, _Container>
      推导结果:
      告诉编译器:如果使用一个类型为_Container的对象来构造stack,那么最终stack的模板参数应该被推导为stack<typename _Container::value_type, _Container>
      typename _Container::value_type表示从传入的容器类型中提取其元素类型,作为stack的元素类型

  1. 上述cpp17推导指引2
    1. 这个指引用于处理同时传入容器和分配器对象的情况(例如,一个使用自定义分配器的容器)
    2. negation<_Is_allocator<_Container>>:确保第一个参数(_Container)不是分配器类型。这和第一个指引中的约束目的相同
    3. uses_allocator<_Container, _Alloc>:这是一个关键的检查,它判断提供的容器类型_Container是否能够使用提供的分配器类型_Alloc。这确保了容器与分配器是兼容的
    4. 只有同时满足这两个条件,该推导指引才有效
    5. 推导结果与第一个指引相同:stack<typename _Container::value_type, _Container>
      分配器类型_Alloc已经被包含在容器的类型之中(例如,std::vector<int, MyAlloc>本身就包含了分配器信息)

  1. 上述cpp23推导指引1

  1. 上述cpp23推导指引2

  1. 上述cpp23推导指引3

其他

enable_if

  1. enable_if

negation

  1. cpp17对类型特性的布尔值 ::value进行逻辑非运算
  2. std::bool_constant是一个辅助模板,它根据表达式 !bool(B::value)的结果,提供一个 static constexpr bool成员 value
    1. 如果 B::valuetrue,则 negation<B>::valuefalse,反之亦然

conjunction

  1. cpp17对多个类型特征的 ::value进行逻辑与运算
  2. 等价:T1::value && T2::value && ... && Tn::value
  3. 短路特性:一旦某个 Ti::valuefalse,便停止实例化后续类型特征
  4. 空参数包:std::conjunction_v<>结果为 true

is_same

  1. is_same

is_object

  1. cpp11用于在编译时判断一个类型是否属于对象类型
    1. 简单来说,它的核心逻辑是:只要一个类型不是函数、不是引用、也不是 void,那么它就是一个对象类型

is_empty

  1. cpp11引入,用于判断一个类类型是否为空类(empty class
    1. 简单来说,它在编译时告诉你一个类是否不占用任何非静态数据成员存储空间
  2. 一个类类型要被 std::is_empty_v判定为 true,必须同时满足以下严格条件
    1. 非联合体的类类型:必须是 classstruct,但不能是 union
    2. 无非静态数据成员:类中不能有任何非静态成员变量(静态数据成员是允许的)
    3. 无虚函数:类中不能声明或继承虚函数(即没有虚函数表)
    4. 无虚基类:在继承体系中,不能有虚继承(即没有虚基类表)
    5. 无非空基类:所有直接基类本身也必须都是空类

is_final

  1. cpp14引入,用于在编译时判断一个类型是否被声明为 final
    1. 如果类型 T是一个被 final修饰的类类型(包括结构体),则 std::is_final_v<T>的值为 true,否则为 false

空基类优化

标签分发

  1. 使用标签类型来区分不同的构造方式
    1. _Zero_then_variadic_args_t:默认构造 _Ty1,然后使用参数构造 _Ty2
    2. _One_then_variadic_args_t:使用第一个参数构造 _Ty1,剩余参数构造 _Ty2

  1. 标签

std::move

  1. 概述
    1. 源码实现巧妙地运用了C++的模板元编程技术,其核心在于类型转换,它本身并不移动任何数据,而是为真正的移动操作(由移动构造函数或移动赋值运算符执行)铺平道路
  2. remove_reference
    1. remove_reference模板,它的唯一作用就是剥去类型上的引用修饰符,暴露出最根本的类型 _Ty

  1. 这个模板通过特化技术,针对不同的输入类型(无引用、左值引用、右值引用)提供了统一的接口来获取其原始类型

  1. move函数本身
    1. _Ty:这是类型推导的关键
    2. _Ty&& _Arg:这是一个万能引用(universal reference
      注意,并非所有 T&&都是万能引用,但在模板函数参数这个特定语境下,它可以根据实参类型进行推导,绑定到左值或右值
    3. 返回remove_reference_t<_Ty>&&:这是精髓所在
      它使用 remove_reference_t先获取 _Ty的原始类型,然后明确地加上 &&,表示此函数总是返回一个该类型的右值引用
    4. static_cast<remove_reference_t<_Ty>&&>(_Arg):进行一个静态类型转换,将参数 _Arg强制转换为右值引用类型
    5. noexcept:声明此操作不会抛出异常,这对于移动操作的安全性和效率很重要

  1. 注意
    1. std::move本身不移动
      它只是一个高级的类型转换工具,告诉编译器:“这个对象可以被移动了”。真正的资源转移发生在类的移动构造函数或移动赋值运算符中
    2. 使用后的对象状态:
      std::move转换并移交资源后,原对象处于有效但未指定的状态。你不应再使用其值,但可以对其重新赋值或析构
    3. 不要移动临时对象(右值)
      对于临时对象(纯右值,如函数返回值 return obj;),编译器已经会优先尝试移动,无需再画蛇添足地使用 std::move
    4. 确保对象之后不再被使用:
      一旦对一个对象使用了 std::move,就意味着你放弃了对它资源的所有权,不应再依赖其值

std::forward

  1. 概述
    1. 核心作用是实现完美转发
    2. 简单来说,它能在函数模板中,将一个参数的原始值类别(是左值还是右值)“原封不动”地传递给另一个函数
  2. 示例
特性/场景 未使用 std::forward(func(arg)) 使用 std::forward(func(std::forward<T>(arg)))
核心问题 参数 arg在函数内部 始终是左值,导致右值属性丢失 保持参数的原始值类别(左值或右值)
传入左值 调用 func(int&),符合预期 调用 func(int&),符合预期
传入右值 调用 func(int&),但期望调用 func(int&&)
右值被当作左值处理,可能无法触发移动语义,造成不必要的拷贝
调用 func(int&&),符合预期
能正确利用移动语义,提升效率
  1. code

特性 第一个重载 (用于左值) 第二个重载 (用于右值)
参数类型 remove_reference_t<_Ty>& _Arg(左值引用) remove_reference_t<_Ty>&& _Arg(右值引用)
关键检查 static_assert断言 _Ty不能是左值引用类型
转换 static_cast<_Ty&&>(_Arg) static_cast<_Ty&&>(_Arg)
目的 将传入的左值恢复为原始的左值引用 确保传入的右值被正确保持为右值引用
  1. 第一个重载
    1. 这个版本接受一个左值引用参数
    2. remove_reference_t<_Ty>确保了即使 _Ty是引用类型,参数类型也是一个非引用的左值引用,这是为了匹配传入的左值
    3. 核心作用是:当外部传入一个左值时,通过 static_cast<_Ty&&>(_Arg)并结合引用折叠规则,将其恢复为左值引用
    4. 例如,如果 _Ty被推导为 MyClass&,那么 static_cast<MyClass& &&>(_Arg)会折叠成 static_cast<MyClass&>(_Arg),最终返回一个左值引用

  1. 第二个重载
    1. 这个版本接受一个右值引用参数,专门用于处理右值
    2. static_assert断言,这是非常重要的安全检查
      它确保模板参数 _Ty不是一个左值引用类型
      因为如果将一个右值引用参数(_Arg)转发成一个左值引用,这是危险且不符合逻辑的,所以这个断言能捕获此类编程错误
    3. 核心作用是:当外部传入一个右值时,通过 static_cast<_Ty&&>(_Arg),将其保持为右值引用
    4. 例如,如果 _Ty被推导为 MyClass(非引用),那么 static_cast<MyClass&&>(_Arg)将返回一个右值引用

关于类型推导

  1. 推导时机
    1. std::forward的模板参数 _Ty的类型信息,并非由 std::forward自身推导,而是来自于更外层的上下文——通常是那个使用了万能引用 的模板函数
    2. 用一个如 template<typename T> void func(T&& arg)的函数时,编译器会根据传入 func的实参来推导 T
      如果传入 func的是一个左值(比如一个变量),T会被推导为左值引用类型(例如 MyType&
      如果传入 func的是一个右值(比如临时对象或 std::move的结果),T会被推导为非引用类型(例如 MyType
    3. 这个推导过程在调用 func时就已完成
    4. 然后,当在 func内部调用 std::forward<T>(arg)时,T已经是一个确定的类型,std::forward只是接收并使用这个结果
  2. 结合forward
    1. _Arg的本质类型是 X&(左值引用)或 X&&(右值引用)
    2. _Arg的最终类型(左值引用还是右值引用)取决于调用 std::forward时传入的实参的值类别,这决定了匹配到下面哪个重载版本
    3. std::forward的核心操作是 static_cast<_Ty&&>(_Arg)
      这里运用了引用折叠规则 来还原原始的值类别

  1. 综合
    1. 类型推导发生在调用了使用了万能引用参数的函数时
    2. 左值:推导为T&T& &&折叠后为T&
    3. 左值引用:自身是左值,推导为T&T& &&折叠后为T&
    4. 右值:推导为TT&&T&&
    5. 右值引用:推导为T&&T&& &&折叠后为T&&

左值,右值,左值引用,右值引用

  1. 比较
概念 本质 核心特征与判断方法 主要用途
左值 (lvalue) 有身份、有存储空间的表达式 持久:生命周期超越单个表达式
可寻址:可使用 &取地址(如 &x
通常有标识符:如变量名、函数名
表示可重复使用的对象和数据
右值 (rvalue) 临时的、内容即将被使用的表达式 短暂:通常是表达式求值产生的临时结果
不可寻址:无法使用 &取地址(如 &(x+1)非法)
通常匿名
表示临时值、字面量、运算中间结果
左值引用 (&) 左值的别名(Type& 只能绑定到左值(非const版本)
绑定后与原左值共享地址,操作引用即操作原对象
函数参数传递,避免拷贝
为变量起别名,增强代码可读性
右值引用 (&&) 右值的别名(Type&& 只能绑定到右值
关键特性:延长临时对象的生命周期,使其在引用存在期间持续存活
移动语义:高效转移资源所有权,避免深拷贝
完美转发:在模板中保持参数原始值类别
  1. 示例

  1. 重要例外:
    1. const左值引用是个“万能”引用,它可以绑定到左值,也可以绑定到右值

  1. 重点
    1. 任何有名字的引用(包括右值引用变量)本身都是一个左值
    2. 因为它有标识符,可以取地址,符合左值的定义

引用折叠规则

  1. 当传入一个左值(如变量 i或左值引用 j)时,T被推导为左值引用类型(如 int&
    1. 然后 T&&根据折叠规则 int& &&折叠为 int&
  2. 当传入一个右值(如函数返回的临时对象或 std::move的结果)时,T被推导为对象的非引用类型(如 int
    1. 此时 T&&就是 int&&,是一个右值引用
  3. 具体详见 引用折叠相关

forwardmove

特性 std::move std::forward
行为 无条件地将任何表达式转换为右值引用 有条件地转换,旨在保持参数的原始值类别
目的 表示“我允许你移动这个对象的资源” 实现完美转发,在转发过程中保留值类别
信号 “请拿走我的资源,我不再需要它了。” “我只是个中间人,请根据原始信息处理它。”

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

bingliaolong
Bingliaolong 关注:0    粉丝:0 最后编辑于:2025-12-06
Everything will be better.

发表评论

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