概述
- 首先,
stack是一个容器适配器 - 理论上,任何提供了
push_back(),pop_back(),back()等操作的序列容器都可以作为stack的底层容器
为什么默认是deque
deque之所以成为默认选择,是因为它在以下几个方面取得了最佳平衡:- 相对于
vectordeque的主要优势在于高效且低成本的动态扩容vector在尾部空间不足时,需要申请一块更大的内存,然后将所有元素整体搬迁过去,这个操作的时间复杂度是O(n)deque采用分段连续的结构,只需要新增一个内存块(buffer)并将其指针添加到中控器(map)中即可,开销要小得多
- 相对于
listdeque的主要优势在于更高的内存利用率和缓存友好性list的每个元素都存储在两个指针(前驱和后继),内存开销较大,且节点在内存中不连续,容易造成内存碎片,CPU缓存命中率较低deque的元素存储在连续的内存块中,虽然块与块之间不连续,但块内是连续的,这在保证高效操作的同时,提供了更好的内存局部性
灵活性:其他容器
- 尽管
deque是默认选择,但STL的适配器设计给予了充分的灵活性
|
1 2 3 4 5 6 7 8 9 |
#include <stack> #include <vector> #include <list> // 如果你需要极致的遍历速度,且栈大小相对固定,可以考虑vector std::stack<int, std::vector<int>> stack_using_vector; // 如果你需要频繁地在栈的中间进行插入删除(这其实比较少见,不符合栈的典型用法),可以考虑list std::stack<int, std::list<int>> stack_using_list; |
stack
设计
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
_EXPORT_STD template <class _Ty, class _Container = deque<_Ty>> class stack { public: // 类型 static_assert(is_same_v<_Ty, value_type>, "container adaptors require consistent types"); static_assert(is_object_v<_Ty>, "The C++ Standard forbids container adaptors of non-object types " "because of [container.requirements]."); // 不同版本构造 // empty 使用_Container功能 // size 使用_Container功能 // top 使用_Container功能 // push 使用_Container功能 // emplace 使用_Container功能 // pop 使用_Container功能 // swap 使用std::swap protected: _Container c{}; }; // C++17 类型推导指引 // C++23 类型推导指引 // 一些操作符处理 |
类型推导指引
- 上述
cpp17推导指引1_Is_allocator<_Container>::value用于判断类型_Container是否为分配器(Allocator)!_Is_allocator<_Container>::value表示:只有当_Container不是分配器类型时,这个推导指引才参与重载决议,有效
为了避免混淆:用一个分配器对象来构造一个栈,还是想用这个分配器作为底层容器
此约束明确排除了后一种情况-> stack<typename _Container::value_type, _Container>
推导结果:
告诉编译器:如果使用一个类型为_Container的对象来构造stack,那么最终stack的模板参数应该被推导为stack<typename _Container::value_type, _Container>
typename _Container::value_type表示从传入的容器类型中提取其元素类型,作为stack的元素类型
|
1 2 3 4 |
#if _HAS_CXX17 template <class _Container, enable_if_t<!_Is_allocator<_Container>::value, int> = 0> stack(_Container) -> stack<typename _Container::value_type, _Container>; #endif // _HAS_CXX17 |
- 上述
cpp17推导指引2- 这个指引用于处理同时传入容器和分配器对象的情况(例如,一个使用自定义分配器的容器)
negation<_Is_allocator<_Container>>:确保第一个参数(_Container)不是分配器类型。这和第一个指引中的约束目的相同uses_allocator<_Container, _Alloc>:这是一个关键的检查,它判断提供的容器类型_Container是否能够使用提供的分配器类型_Alloc。这确保了容器与分配器是兼容的- 只有同时满足这两个条件,该推导指引才有效
- 推导结果与第一个指引相同:
stack<typename _Container::value_type, _Container>
分配器类型_Alloc已经被包含在容器的类型之中(例如,std::vector<int, MyAlloc>本身就包含了分配器信息)
|
1 2 3 4 5 6 7 8 |
// C++17 之前 std::vector<int, MyAllocator> my_vec; std::stack<int, std::vector<int, MyAllocator>> my_stack(my_vec); // 需要完整写出类型 // C++17 之后 std::vector<int, MyAllocator> my_vec; std::stack my_stack(my_vec); // 编译器自动推导为 stack<int, vector<int, MyAllocator>> std::stack my_stack2(my_vec, my_allocator); // 使用第二个推导指引 |
|
1 2 3 4 5 |
#if _HAS_CXX17 template <class _Container, class _Alloc, enable_if_t<conjunction_v<negation<_Is_allocator<_Container>>, uses_allocator<_Container, _Alloc>>, int> = 0> stack(_Container, _Alloc) -> stack<typename _Container::value_type, _Container>; #endif // _HAS_CXX17 |
- 上述
cpp23推导指引1
|
1 2 3 4 |
#if _HAS_CXX23 template <_Iterator_for_container _InIt, _Allocator_for_container _Alloc = allocator<_Iter_value_t<_InIt>>> stack(_InIt, _InIt, _Alloc = _Alloc()) -> stack<_Iter_value_t<_InIt>, deque<_Iter_value_t<_InIt>, _Alloc>>; #endif // _HAS_CXX23 |
- 上述
cpp23推导指引2
|
1 2 3 4 |
#if _HAS_CXX23 template <_RANGES input_range _Rng> stack(from_range_t, _Rng&&) -> stack<_RANGES range_value_t<_Rng>>; #endif // _HAS_CXX23 |
- 上述
cpp23推导指引3
|
1 2 3 4 |
#if _HAS_CXX23 template <_RANGES input_range _Rng, _Allocator_for_container _Alloc> stack(from_range_t, _Rng&&, _Alloc) -> stack<_RANGES range_value_t<_Rng>, deque<_RANGES range_value_t<_Rng>, _Alloc>>; #endif // _HAS_CXX23 |
其他
enable_if
negation
cpp17对类型特性的布尔值::value进行逻辑非运算std::bool_constant是一个辅助模板,它根据表达式!bool(B::value)的结果,提供一个static constexpr bool成员value- 如果
B::value为true,则negation<B>::value为false,反之亦然
- 如果
|
1 2 |
template<class B> struct negation : std::bool_constant<!bool(B::value)> { }; |
conjunction
cpp17对多个类型特征的::value进行逻辑与运算- 等价:
T1::value && T2::value && ... && Tn::value - 短路特性:一旦某个
Ti::value为false,便停止实例化后续类型特征 - 空参数包:
std::conjunction_v<>结果为true
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// 基础案例:空参数包,结果为 true template<class...> struct conjunction : std::true_type {}; // 递归案例:只有一个类型时,直接继承该类型 template<class B1> struct conjunction<B1> : B1 {}; // 递归案例:多个类型时,通过 std::conditional_t 进行短路判断 template<class B1, class... Bn> struct conjunction<B1, Bn...> : std::conditional_t<bool(B1::value), conjunction<Bn...>, B1> {}; template<class... B> constexpr bool conjunction_v = conjunction<B...>::value; |
is_same
is_object
cpp11用于在编译时判断一个类型是否属于对象类型- 简单来说,它的核心逻辑是:只要一个类型不是函数、不是引用、也不是
void,那么它就是一个对象类型
- 简单来说,它的核心逻辑是:只要一个类型不是函数、不是引用、也不是
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
template<typename T> void process() { static_assert(std::is_object_v<T>, "T must be an object type."); // ... 你的代码逻辑 } class MyClass {}; int main() { process<int>(); // 通过:int 是对象类型 process<MyClass>(); // 通过:MyClass 是对象类型 // process<int&>(); // 编译错误:int& 是引用类型,不是对象类型 return 0; } |
is_empty
cpp11引入,用于判断一个类类型是否为空类(empty class)- 简单来说,它在编译时告诉你一个类是否不占用任何非静态数据成员存储空间
- 一个类类型要被
std::is_empty_v判定为true,必须同时满足以下严格条件- 非联合体的类类型:必须是
class或struct,但不能是union - 无非静态数据成员:类中不能有任何非静态成员变量(静态数据成员是允许的)
- 无虚函数:类中不能声明或继承虚函数(即没有虚函数表)
- 无虚基类:在继承体系中,不能有虚继承(即没有虚基类表)
- 无非空基类:所有直接基类本身也必须都是空类
- 非联合体的类类型:必须是
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include <iostream> #include <type_traits> struct EmptyStruct {}; // 空结构体 struct NonEmpty { int data; // 有非静态数据成员 }; struct WithVirtual { virtual ~WithVirtual() {} // 有虚函数 }; int main() { std::cout << std::boolalpha; std::cout << "std::is_empty_v<EmptyStruct>: " << std::is_empty_v<EmptyStruct> << std::endl; // 输出 true std::cout << "std::is_empty_v<NonEmpty>: " << std::is_empty_v<NonEmpty> << std::endl; // 输出 false std::cout << "std::is_empty_v<WithVirtual>: " << std::is_empty_v<WithVirtual> << std::endl; // 输出 false std::cout << "std::is_empty_v<int>: " << std::is_empty_v<int> << std::endl; // 输出 false (非类类型) return 0; } |
is_final
cpp14引入,用于在编译时判断一个类型是否被声明为final- 如果类型
T是一个被final修饰的类类型(包括结构体),则std::is_final_v<T>的值为true,否则为false
- 如果类型
|
1 2 3 4 5 |
template<typename Base> class MyDerived : public Base { // 希望 Base 不是 final 的 static_assert(!std::is_final_v<Base>, "Base class must not be final."); // ... 其他实现 }; |
空基类优化
|
1 2 3 4 5 6 7 |
class _Compressed_pair final : private _Ty1 { // 私有继承空基类 public: _Ty2 _Myval2; // 直接存储第二个值 ... constexpr _Ty1& _Get_first() noexcept { return *this; // 通过this指针获取基类部分 } |
|
1 2 3 4 5 6 7 8 9 |
template <class _Ty1, class _Ty2> class _Compressed_pair<_Ty1, _Ty2, false> final { // 存储两个成员变量 public: _Ty1 _Myval1; // 显式存储第一个值 _Ty2 _Myval2; ... constexpr _Ty1& _Get_first() noexcept { return _Myval1; // 返回成员变量 _Myval1 } |
标签分发
- 使用标签类型来区分不同的构造方式
_Zero_then_variadic_args_t:默认构造_Ty1,然后使用参数构造_Ty2_One_then_variadic_args_t:使用第一个参数构造_Ty1,剩余参数构造_Ty2
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
template <class _Ty1, class _Ty2, bool = is_empty_v<_Ty1> && !is_final_v<_Ty1>> class _Compressed_pair final : private _Ty1 { // store a pair of values, deriving from empty first public: _Ty2 _Myval2; using _Mybase = _Ty1; // for visualization template <class... _Other2> constexpr explicit _Compressed_pair(_Zero_then_variadic_args_t, _Other2&&... _Val2) noexcept(conjunction_v<is_nothrow_default_constructible<_Ty1>, is_nothrow_constructible<_Ty2, _Other2...>>) : _Ty1(), _Myval2(_STD forward<_Other2>(_Val2)...) {} template <class _Other1, class... _Other2> constexpr _Compressed_pair(_One_then_variadic_args_t, _Other1&& _Val1, _Other2&&... _Val2) noexcept(conjunction_v<is_nothrow_constructible<_Ty1, _Other1>, is_nothrow_constructible<_Ty2, _Other2...>>) : _Ty1(_STD forward<_Other1>(_Val1)), _Myval2(_STD forward<_Other2>(_Val2)...) {} constexpr _Ty1& _Get_first() noexcept { return *this; } constexpr const _Ty1& _Get_first() const noexcept { return *this; } }; |
- 标签
|
1 2 3 4 5 6 7 |
struct _Zero_then_variadic_args_t { explicit _Zero_then_variadic_args_t() = default; }; // tag type for value-initializing first, constructing second from remaining args struct _One_then_variadic_args_t { explicit _One_then_variadic_args_t() = default; }; // tag type for constructing first from one arg, constructing second from remaining args |
std::move
- 概述
- 源码实现巧妙地运用了
C++的模板元编程技术,其核心在于类型转换,它本身并不移动任何数据,而是为真正的移动操作(由移动构造函数或移动赋值运算符执行)铺平道路
- 源码实现巧妙地运用了
remove_referenceremove_reference模板,它的唯一作用就是剥去类型上的引用修饰符,暴露出最根本的类型_Ty
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// 主模板:处理非引用类型。直接定义 `type` 为 _Ty。 template <class _Ty> struct remove_reference { using type = _Ty; // 原始类型 using _Const_thru_ref_type = const _Ty; // 常量版本的原类型 }; // 特化版本1:处理左值引用 (_Ty&)。剥去`&`,`type` 为 _Ty。 template <class _Ty> struct remove_reference<_Ty&> { using type = _Ty; // 剥去左值引用,得到原始类型 using _Const_thru_ref_type = const _Ty&; // 保持左值引用,并为底层类型添加const }; // 特化版本2:处理右值引用 (_Ty&&)。剥去`&&`,`type` 同样为 _Ty。 template <class _Ty> struct remove_reference<_Ty&&> { using type = _Ty; // 剥去右值引用,得到原始类型 using _Const_thru_ref_type = const _Ty&&; // 保持右值引用,并为底层类型添加const }; // 便捷别名模板,允许直接使用 remove_reference_t<T> 代替 typename remove_reference<T>::type template <class _Ty> using remove_reference_t = typename remove_reference<_Ty>::type; |
- 这个模板通过特化技术,针对不同的输入类型(无引用、左值引用、右值引用)提供了统一的接口来获取其原始类型
|
1 2 3 4 |
T remove_reference<T>::type remove_reference_t<T> int int int int& int int int&& int int |
move函数本身_Ty:这是类型推导的关键_Ty&& _Arg:这是一个万能引用(universal reference)
注意,并非所有T&&都是万能引用,但在模板函数参数这个特定语境下,它可以根据实参类型进行推导,绑定到左值或右值- 返回
remove_reference_t<_Ty>&&:这是精髓所在
它使用remove_reference_t先获取_Ty的原始类型,然后明确地加上&&,表示此函数总是返回一个该类型的右值引用 static_cast<remove_reference_t<_Ty>&&>(_Arg):进行一个静态类型转换,将参数_Arg强制转换为右值引用类型noexcept:声明此操作不会抛出异常,这对于移动操作的安全性和效率很重要
|
1 2 3 4 |
template <class _Ty> _NODISCARD _MSVC_INTRINSIC constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept { return static_cast<remove_reference_t<_Ty>&&>(_Arg); } |
- 注意
std::move本身不移动
它只是一个高级的类型转换工具,告诉编译器:“这个对象可以被移动了”。真正的资源转移发生在类的移动构造函数或移动赋值运算符中- 使用后的对象状态:
被std::move转换并移交资源后,原对象处于有效但未指定的状态。你不应再使用其值,但可以对其重新赋值或析构 - 不要移动临时对象(右值)
对于临时对象(纯右值,如函数返回值return obj;),编译器已经会优先尝试移动,无需再画蛇添足地使用std::move - 确保对象之后不再被使用:
一旦对一个对象使用了std::move,就意味着你放弃了对它资源的所有权,不应再依赖其值
std::forward
- 概述
- 核心作用是实现
完美转发 - 简单来说,它能在函数模板中,将一个参数的
原始值类别(是左值还是右值)“原封不动”地传递给另一个函数
- 核心作用是实现
- 示例
| 特性/场景 | 未使用 std::forward(func(arg)) |
使用 std::forward(func(std::forward<T>(arg))) |
| 核心问题 | 参数 arg在函数内部 始终是左值,导致右值属性丢失 |
保持参数的原始值类别(左值或右值) |
| 传入左值 | 调用 func(int&),符合预期 |
调用 func(int&),符合预期 |
| 传入右值 | 调用 func(int&),但期望调用 func(int&&)右值被当作左值处理,可能无法触发移动语义,造成不必要的拷贝 |
调用 func(int&&),符合预期能正确利用移动语义,提升效率 |
code
|
1 2 3 4 5 6 7 8 9 10 |
_EXPORT_STD template <class _Ty> _NODISCARD _MSVC_INTRINSIC constexpr _Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept { return static_cast<_Ty&&>(_Arg); } _EXPORT_STD template <class _Ty> _NODISCARD _MSVC_INTRINSIC constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept { static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call"); return static_cast<_Ty&&>(_Arg); } |
| 特性 | 第一个重载 (用于左值) | 第二个重载 (用于右值) |
| 参数类型 | remove_reference_t<_Ty>& _Arg(左值引用) |
remove_reference_t<_Ty>&& _Arg(右值引用) |
| 关键检查 | 无 | static_assert断言 _Ty不能是左值引用类型 |
| 转换 | static_cast<_Ty&&>(_Arg) |
static_cast<_Ty&&>(_Arg) |
| 目的 | 将传入的左值恢复为原始的左值引用 | 确保传入的右值被正确保持为右值引用 |
- 第一个重载
- 这个版本接受一个左值引用参数
remove_reference_t<_Ty>确保了即使_Ty是引用类型,参数类型也是一个非引用的左值引用,这是为了匹配传入的左值- 核心作用是:当外部传入一个左值时,通过
static_cast<_Ty&&>(_Arg)并结合引用折叠规则,将其恢复为左值引用 - 例如,如果
_Ty被推导为MyClass&,那么static_cast<MyClass& &&>(_Arg)会折叠成static_cast<MyClass&>(_Arg),最终返回一个左值引用
|
1 2 3 4 |
template <class _Ty> constexpr _Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept { return static_cast<_Ty&&>(_Arg); } |
- 第二个重载
- 这个版本接受一个右值引用参数,专门用于处理右值
static_assert断言,这是非常重要的安全检查
它确保模板参数_Ty不是一个左值引用类型
因为如果将一个右值引用参数(_Arg)转发成一个左值引用,这是危险且不符合逻辑的,所以这个断言能捕获此类编程错误- 核心作用是:当外部传入一个右值时,通过
static_cast<_Ty&&>(_Arg),将其保持为右值引用 - 例如,如果
_Ty被推导为MyClass(非引用),那么static_cast<MyClass&&>(_Arg)将返回一个右值引用
|
1 2 3 4 5 |
template <class _Ty> constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept { static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call"); return static_cast<_Ty&&>(_Arg); } |
关于类型推导
- 推导时机
std::forward的模板参数_Ty的类型信息,并非由std::forward自身推导,而是来自于更外层的上下文——通常是那个使用了万能引用 的模板函数- 用一个如
template<typename T> void func(T&& arg)的函数时,编译器会根据传入func的实参来推导T:
如果传入func的是一个左值(比如一个变量),T会被推导为左值引用类型(例如MyType&)
如果传入func的是一个右值(比如临时对象或std::move的结果),T会被推导为非引用类型(例如MyType) - 这个推导过程在调用
func时就已完成 - 然后,当在
func内部调用std::forward<T>(arg)时,T已经是一个确定的类型,std::forward只是接收并使用这个结果
- 结合
forward_Arg的本质类型是X&(左值引用)或X&&(右值引用)_Arg的最终类型(左值引用还是右值引用)取决于调用std::forward时传入的实参的值类别,这决定了匹配到下面哪个重载版本std::forward的核心操作是static_cast<_Ty&&>(_Arg)
这里运用了引用折叠规则来还原原始的值类别
|
1 2 3 4 5 6 7 8 9 10 |
_EXPORT_STD template <class _Ty> _NODISCARD _MSVC_INTRINSIC constexpr _Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept { return static_cast<_Ty&&>(_Arg); } _EXPORT_STD template <class _Ty> _NODISCARD _MSVC_INTRINSIC constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept { static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call"); return static_cast<_Ty&&>(_Arg); } |
- 综合
- 类型推导发生在调用了使用了万能引用参数的函数时
- 左值:推导为
T&,T& &&折叠后为T& - 左值引用:自身是左值,推导为
T&,T& &&折叠后为T& - 右值:推导为
T,T&&为T&& - 右值引用:推导为
T&&,T&& &&折叠后为T&&
|
1 |
template<typename T> void func(T&& arg) {} |
左值,右值,左值引用,右值引用
- 比较
| 概念 | 本质 | 核心特征与判断方法 | 主要用途 |
左值 (lvalue) |
有身份、有存储空间的表达式 | 持久:生命周期超越单个表达式 可寻址:可使用 &取地址(如 &x)通常有标识符:如变量名、函数名 |
表示可重复使用的对象和数据 |
右值 (rvalue) |
临时的、内容即将被使用的表达式 | 短暂:通常是表达式求值产生的临时结果 不可寻址:无法使用 &取地址(如 &(x+1)非法)通常匿名 |
表示临时值、字面量、运算中间结果 |
左值引用 (&) |
左值的别名(Type&) |
只能绑定到左值(非const版本)绑定后与原左值共享地址,操作引用即操作原对象 |
函数参数传递,避免拷贝 为变量起别名,增强代码可读性 |
右值引用 (&&) |
右值的别名(Type&&) |
只能绑定到右值 关键特性:延长临时对象的生命周期,使其在引用存在期间持续存活 |
移动语义:高效转移资源所有权,避免深拷贝 完美转发:在模板中保持参数原始值类别 |
- 示例
|
1 2 3 4 5 6 7 8 |
// 左值 int a = 10; // 'a' 是左值 int* ptr = &a; // 可以对 'a' 取地址,合法 *ptr = 20; // 解引用操作 '*ptr' 也是左值 std::vector<int> vec = {1, 2, 3}; vec[1] = 5; // 下标操作 'vec[1]' 返回左值引用,是左值 |
|
1 2 3 4 5 6 7 8 9 |
// 右值 int x = 5 + 3; // '5', '3', '5+3' 的结果都是右值 // &(5+3); // 错误!无法对右值取地址 std::string getString() { return "Hello"; } std::string s = getString(); // 函数返回值是右值 int result = std::sqrt(9.0); // std::sqrt(9.0) 的结果是右值 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 引用本质上是为已存在对象起的一个别名,通过不同的引用类型,我们可以控制如何访问这些对象 // 左值引用 // 左值引用主要用于避免对象拷贝,尤其在函数参数传递和返回值时 int i = 42; int& lref_i = i; // 正确:左值引用绑定到左值 // int& bad_ref1 = 42; // 错误:非const左值引用不能绑定到右值 void processBigData(const std::vector<int>& data) { // 常左值引用作参数,避免拷贝 // 读取 data,但不会修改它 } std::vector<int> huge_data(1000000, 1); processBigData(huge_data); // 高效,不会拷贝整个huge_data |
|
1 2 3 4 5 6 7 8 9 |
// 右值引用 // 右值引用是C++11引入的强大工具,它开启了移动语义和完美转发的大门 int&& rref1 = 100; // 正确:右值引用绑定到字面量(右值) int&& rref2 = x + y; // 正确:绑定到算术表达式结果(右值) // int&& rref3 = i; // 错误:右值引用不能直接绑定到左值i std::string getTempString(); std::string&& s_ref = getTempString(); // 正确:绑定函数返回的临时对象(右值),延长其生命周期 |
- 重要例外:
const左值引用是个“万能”引用,它可以绑定到左值,也可以绑定到右值
|
1 2 |
const int& const_ref1 = i; // 正确:绑定到左值 const int& const_ref2 = 100; // 正确:const左值引用可以绑定到右值 |
- 重点
- 任何有名字的引用(包括右值引用变量)本身都是一个左值
- 因为它有标识符,可以取地址,符合左值的定义
|
1 2 3 |
int&& rref = 100; // rref 本身是个有名字的变量,它是左值! // int&& rref2 = rref; // 错误!rref是左值,不能直接用右值引用绑定 int&& rref3 = std::move(rref); // 正确:使用std::move将左值rref转换为右值 |
引用折叠规则
- 当传入一个左值(如变量
i或左值引用j)时,T被推导为左值引用类型(如int&)- 然后
T&&根据折叠规则int& &&折叠为int&
- 然后
- 当传入一个右值(如函数返回的临时对象或
std::move的结果)时,T被推导为对象的非引用类型(如int)- 此时
T&&就是int&&,是一个右值引用
- 此时
- 具体详见 引用折叠相关
|
1 2 3 4 |
T& &-> T& T& &&-> T& T&& &-> T& T&& &&-> T&& |
forward和move
| 特性 | std::move |
std::forward |
| 行为 | 无条件地将任何表达式转换为右值引用 |
有条件地转换,旨在保持参数的原始值类别 |
| 目的 | 表示“我允许你移动这个对象的资源” | 实现完美转发,在转发过程中保留值类别 |
| 信号 | “请拿走我的资源,我不再需要它了。” | “我只是个中间人,请根据原始信息处理它。” |
声明:本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ Soui八06/20
- ♥ C++并发编程 _管理线程05/07
- ♥ Spdlog记述:二07/09
- ♥ Objective-C 解析plist04/28
- ♥ C++20_第一篇06/30
- ♥ Effective C++_第二篇07/01