CppGuide社区 CppGuide社区
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
🔥C++面试
  • 第1章 C++ 惯用法与Modern C++篇
  • 第2章 C++开发工具与调试进阶
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 网络通信协议设计
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 服务其他模块设计
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 🔥C++游戏编程入门(零基础学C++)
  • 🔥使用C++17从零开发一个调试器 (opens new window)
  • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
  • 🔥交易系统开发岗位求职与面试指南统 (opens new window)
  • 🔥使用C++从零写一个C语言编译器 (opens new window)
  • 🔥从零用C语言写一个Redis
  • leveldb源码分析
  • libevent源码分析
  • Memcached源码分析
  • TeamTalk源码分析
  • 优质源码分享 (opens new window)
  • 🔥远程控制软件gh0st源码分析
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go系统接口编程
  • 高效Go并发编程
  • Go性能调优
  • Go项目架构设计
  • 🔥使用Go从零开发一个数据库
  • 🔥使用Go从零开发一个编译器 (opens new window)
  • 🔥使用Go从零开发一个解释器 (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
🔥C++面试
  • 第1章 C++ 惯用法与Modern C++篇
  • 第2章 C++开发工具与调试进阶
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 网络通信协议设计
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 服务其他模块设计
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 🔥C++游戏编程入门(零基础学C++)
  • 🔥使用C++17从零开发一个调试器 (opens new window)
  • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
  • 🔥交易系统开发岗位求职与面试指南统 (opens new window)
  • 🔥使用C++从零写一个C语言编译器 (opens new window)
  • 🔥从零用C语言写一个Redis
  • leveldb源码分析
  • libevent源码分析
  • Memcached源码分析
  • TeamTalk源码分析
  • 优质源码分享 (opens new window)
  • 🔥远程控制软件gh0st源码分析
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go系统接口编程
  • 高效Go并发编程
  • Go性能调优
  • Go项目架构设计
  • 🔥使用Go从零开发一个数据库
  • 🔥使用Go从零开发一个编译器 (opens new window)
  • 🔥使用Go从零开发一个解释器 (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
  • C++语言面试问题集锦 目录与说明
  • 第一章 auto与类型推导
  • 第二章 关键字static及其不同用法
  • 第三章 多态、继承和虚函数
  • 第四章 Lambda函数
  • 第五章 C++中如何使用 const 限定符
  • 第六章 Modern C++的一些最佳实践
  • 第七章 智能指针
  • 第八章 引用、万能引用等
    • 问题58:std::move移动了什么?
    • 问题59:std::forward转发了什么?
    • 问题60:万能引用和右值引用有什么区别?
    • 问题61:什么是引用折叠(reference collapsing)?
    • 问题62:constexpr函数在何时求值?
    • 问题63:何时应该将函数声明为noexcept?
  • 第九章 C++20相关问题
  • 第十章 特殊函数及数量规则
  • 第十一章 C++面向对象设计
  • 第十二章 程序质量
  • 第十三章 标准模板库
  • 第十四章 杂项
  • cppinterviewmostaskedquestions
zhangxf
2025-03-27
目录

第八章 引用、万能引用等

# 第八章 引用、万能引用等

接下来的几个问题将涉及引用、万能引用(universal references),甚至还会提到noexcept。

# 问题58:std::move移动了什么?

std::move什么都没移动。在运行时,它根本不会执行任何操作,甚至不会生成一个字节的可执行代码。

实际上,std::move只是一个工具,用于将其输入的任何内容转换为右值引用(rvalue reference)。

因此,std::move这个名字不太恰当,也许叫rvalue_cast会更好,但它就叫这个名字,我们只要记住它并不会移动任何东西就行。它返回一个右值引用,而右值引用是移动操作的候选对象。对一个对象使用std::move,是在告诉编译器这个对象可以被移动。这就是std::move名字的由来:便于指定哪些对象可以被移动。

值得注意的是,从常量变量进行移动是不可能的,因为移动构造函数(move constructor)和移动赋值(move assignment)会改变执行移动操作的对象。然而,如果你尝试从常量对象进行移动,编译器不会报错,甚至不会给出警告。对常量对象的移动请求会被悄悄地转换为复制操作。

# 问题59:std::forward转发了什么?

就像std::move什么都没移动一样,std::forward也什么都没转发。同样地,它在运行时也不会执行任何操作,甚至不会生成一个字节的可执行代码。

std::forward和std::move一样,也是一种类型转换。但它是如何使用的呢?

std::forward最常见的使用场景是在函数模板中,该函数模板接受一个万能引用参数,并将其传递给另一个函数:

void  process(MyClass&& rvalueArgument); 

template<typename  T>
void  logAndProcess(T&& param) {
    auto  now = std::chrono::system_clock::now();
    makeLogEntry("Calling 'process'", now);
    process(std::forward(param));
}
1
2
3
4
5
6
7
8

这也被称为完美转发(perfect forwarding)。它有两个重载版本。

一个版本将左值(lvalues)作为左值转发,将右值作为右值转发;另一个版本是条件类型转换,它将右值作为右值转发,禁止将右值作为左值转发。试图将右值作为左值转发会导致编译时错误。

# 问题60:万能引用和右值引用有什么区别?

如果函数模板参数的类型为T&&(T为推导类型),或者对象使用auto&&声明,那么这个参数或对象就是一个万能引用。

template<typename  T> 
void  f(T&& param);

// 万能引用
auto&& v2 = v; 
1
2
3
4
5

你可能会问,什么是万能引用呢?当万能引用用右值初始化时,它就相当于右值引用;当用左值初始化时,它就相当于左值引用。它到底是什么取决于传入的内容。

如果类型声明的形式不是精确的type&&,或者没有发生类型推导(即没有使用auto),那么就是右值引用。

void  f(MyClass&& param); 
MyClass&& var1 = MyClass(); 
template<typename  T>
void  f(std::vector<T>&& param); 
1
2
3
4

需要记住的是,了解右值引用和万能引用的区别,有助于你更准确地阅读源代码。这是一个只能绑定到右值的右值类型,还是一个既可以绑定到右值也可以绑定到左值的万能引用呢?理解这一点也能避免与同事交流时产生歧义(“我这里用的是万能引用,不是右值引用……”)。

# 问题61:什么是引用折叠(reference collapsing)?

引用折叠可能发生在以下四种不同的场景中:

  • 模板实例化(template instantiation)
  • auto类型生成
  • 类型定义(typedefs)和别名声明(alias declarations)的创建与使用
  • 使用decltype

不允许声明引用的引用,但编译器可能会在上述场景中生成引用的引用。当编译器生成引用的引用时,引用折叠规则决定接下来会发生什么。

int  x;
auto& & rx = x; 
typedef  int& T;
// a具有int&类型
T&& a; 
template  <typename  T> void  func(T&& a);
auto  fp = func<int&&>; 
1
2
3
4
5
6
7

引用分为两种(左值引用和右值引用),所以引用与引用的组合可能有四种:

  • 左值引用到左值引用
  • 左值引用到右值引用
  • 右值引用到左值引用
  • 右值引用到右值引用

如果在上述四种场景中出现了引用的引用,根据以下规则,这些引用会折叠为单个引用:

如果其中一个引用是左值引用,结果就是左值引用。否则(即两个都是右值引用),结果就是右值引用。

在类型推导区分左值和右值且发生引用折叠的场景中,万能引用被视为右值引用。

# 问题62:constexpr函数在何时求值?

constexpr函数可能在编译时求值,但这不是绝对的。它们既可以在运行时执行,也可以在编译时执行。这通常取决于编译器版本和优化级别。

如果在编译时使用constexpr变量请求constexpr函数的值,那么它将在编译时执行,例如:constexpr auto foo = bar(42),这里的bar是一个constexpr函数。

此外,如果constexpr函数在C数组初始化或静态断言(static assertion)的上下文中执行,它也会在编译时求值。

如果需要一个常量,但你提供的只是一个运行时函数,编译器会提示你。

把所有函数都声明为constexpr并不是一个好主意,因为大多数计算最好在运行时进行。同时,值得注意的是,constexpr函数总是线程安全的,并且会被内联(inlined)。

# 问题63:何时应该将函数声明为noexcept?

对于完全用C语言或其他不支持异常的语言编写的函数,绝对应该加上noexcept。C++标准库对C标准库中的所有函数都隐式地做了这样的处理。

除此之外,对于不会抛出异常的函数,或者即使抛出异常你也不介意程序崩溃的函数,应该使用noexcept。

下面是一段小代码示例,展示如何使用它:

void  func1() noexcept ; 
void  func2() noexcept(true); 
void  func3() throw(); 
void  func4() noexcept(false); 
1
2
3
4

但函数不会抛出异常是什么意思呢?这意味着它不能使用任何会抛出异常的其他函数,它自身被声明为noexcept,并且不使用dynamic_cast转换为引用类型。

六个生成的特殊函数是隐式的noexcept函数。

如果在函数声明了noexcept的情况下仍然抛出异常,就会调用std::terminate。

正如C++核心准则所指出的,当程序崩溃比实际处理异常更好时,你可以使用noexcept。

使用noexcept可以给编译器提供执行某些优化的提示,也能让开发者知道无需处理可能的异常。

上次更新: 2025/03/27, 20:29:48
第七章 智能指针
第九章 C++20相关问题

← 第七章 智能指针 第九章 C++20相关问题→

最近更新
01
第一章 auto与类型推导
03-27
02
第二章 关键字static及其不同用法
03-27
03
C++语言面试问题集锦 目录与说明
03-27
更多文章>
Copyright © 2024-2025 沪ICP备2023015129号 张小方 版权所有
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式