Logic
向着梦想进发
Logic's Blog

【C++11小记】decltype关键字、左值、右值和亡值

C++11 中加入了一个新的关键字 decltype,用于检查实例和表达式的类型。我们来看它的用法:

  • decltype(entity) 实例
  • decltype(exprssion) 表达式

这将产生实例或者表达式的类型 T。当然这里也分为几种情况:

  1. 实参为亡值(xvalue),那么将产生右值引用 T&&
  2. 实参为左值(lvalue),那么将产生左值引用 T&
  3. 实参为纯右值(rvalue),那么将产生原类型 T

当然,在这里 C++ 也做出了一些规定:对于一个标识符 x 来说,decltype(x)decltype((x)) 并不同。

亡值通常是通过移动语义(std::move)产生的。与 decltype 相同,std::move 也是 C++11 中引入的新特性。

简单讲述一下移动的用处。我们可以考虑一个场景,我们定义了一个 class X ,当我们需要初始化 X 时,我们可能定义了一个函数用于返回一个构造好的 X。但是这时问题就出现了,在 C++11 之前,我们只有拷贝构造函数和默认构造函数,这时在函数中构造的临时变量 X' 将被释放,然后重新产生一个新的实例 X。实际上,这里有一次多余的构析和构造,这是我们不希望看到的。我们希望这个临时的 X' 被直接移动X。而在 C++11 中,我们可以使用 std::move 将一个右值转换为右值引用(也就是亡值引用),这样就可以直接调用 X 的移动构造函数。

一般来说:

  • 移动的性能优于复制;
  • 移动过后的变量不保证可用性;
  • 使用移动之后的变量有可能产生 UB。

但是需要注意的是,实际上并没有一条规则保证这几点。这取决于移动构造函数的实现。假设移动构造函数的实现与拷贝构造函数相同,那么移动构造就与拷贝没有区别。

左值的概念我们相对熟悉。可以取地址的变量都是左值,当然这里指的是内存中的地址。对于表达式中产生的一些值,它们会被存入 CPU 的寄存器中而非内存中。所以:

https://cdn.jsdelivr.net/gh/LogicShao/PicBed/img/whyisnotlval.jpg

当然像一些返回左值引用的函数或者表达式自然也可以作为左值,例如 std::vector[] 运算符。不过并不是所有左值都可以被赋值,也就是被放在等号左边。例外是 const 修饰的类型,它无法修改。对其的修改是 UB。

https://cdn.jsdelivr.net/gh/LogicShao/PicBed/img/ubforconstval.jpg

对于一个左值来说,它可以转化为右值。这就是为什么变量可以出现在等号右边。

C 中只有左值有 CV 限定,也就就是 const, volatile 限定。而在 C++ 中,左值和类右值均有 CV 限定(内置类型的右值没有 CV 限定,比如 int)。所以内置类型的左值转换为右值之后,会失去 CV 限定,其类型就是 T 本身。当然这里不展开讨论关于 CV 的内容。

对于一个表达式,如果其不是左值,那么它就是右值。不过右值不能隐式转化为左值,但可以使用 * 解引用来让右值变为左值。例如:

当然,我们可以使用 & 取地址让一个左值变成一个右值。

在理解以上内容之后,我们来看一些 decltype 关键字的示例。在以下的例子中,我将解释“为什么数组名不是指针”

实际上这张图已经非常明显了,CLion 已经告诉我们其类型了。数组名就指代了数组本身,其类型也是数组(也就是 int[5]),而绝不是什么指针。实际上,数组名指代指针这种说法,完全是瞎扯。数组名不指代数组,去指代一个和它没有直接关系的类型,这算是什么逻辑?接下来我们看到第五行的代码,实际上这是由于编译器做了隐式转换,它会将数组名隐式转换为指针。我们加上 + 这一运算符,就完成了这个隐式转换的过程。

https://cdn.jsdelivr.net/gh/LogicShao/PicBed/img/whyarrisnotpointer.jpg

与之相似,函数名同样指代函数,而绝不是什么函数指针

https://cdn.jsdelivr.net/gh/LogicShao/PicBed/img/whyfuncisnotpointer.jpg

当然 decltype 常常在 lambda 表达式中使用。我们可以比较其与 auto 的区别。

auto 类似于模板匹配,其只会匹配 typename 而不会匹配引用(这里包括左值引用和右值引用)。如果需要声明引用则需要显示的写出 & 符号。而 decltype 则会保留引用。

所以在泛型编程中我们可以使用 auto -> decltype() 的方式简化我们的代码。

https://cdn.jsdelivr.net/gh/LogicShao/PicBed/img/autoanddecltype.jpg
当然,C++11只支持单语句推导,对于多语句的推导是在C++14引入的

使用 auto 声明的变量必须初始化,而使用 decltype 则未必(只有产生引用类型必须初始化)。

总结:

  • decltype 关键字类似于 auto 关键字
    • 都可以产生对应的类型
    • 都可以作为类型说明符和占位类型说明符。
  • 对于 CV 限定符的处理不同
  • 对于引用的处理不同
  • C++17auto 可用作声明结构化绑定
  • C++20auto 可作为模板参数

参考资料:

  • https://learn.microsoft.com/zh-cn/cpp/cpp/decltype-cpp?view=msvc-170
  • https://www.bilibili.com/video/BV1mw41177uK/
  • https://zh.cppreference.com/w/cpp/keyword/auto
  • https://learn.microsoft.com/zh-cn/cpp/c-language/l-value-and-r-value-expressions?view=msvc-170
  • https://nettee.github.io/posts/2018/Understanding-lvalues-and-rvalues-in-C-and-C/
  • https://zh.cppreference.com/w/cpp/language/decltype
  • https://www.bilibili.com/video/BV1jY4y1o7Dg/

以上,感谢阅读。

赞赏
本文作者: Logic
本文链接: https://i.needwe.top/decltype-lvalue-rvalue-and-xvalue/
本文采用 CC BY-NC-SA 4.0 Unported 协议进行许可
# # # #
首页      coding      【C++11小记】decltype关键字、左值、右值和亡值

发表回复

textsms
account_circle
email

Logic's Blog

【C++11小记】decltype关键字、左值、右值和亡值
在 C++11 中加入了一个新的关键字 decltype,用于检查实例和表达式的类型。我们来看它的用法: decltype(entity) 实例 decltype(exprssion) 表达式 这将产生实例或者表达式的…
扫描二维码继续阅读
2024-06-09