C++面试八股文:什么是左值,什么是右值?
某日二师兄参加XXX科技公司的C++工程师开发岗位第16面:
面试官:什么是左值,什么是右值?
二师兄:简单来说,左值就是可以使用
&符号取地址的值,而右值一般不可以使用&符号取地址。
int a = 42; //a是左值,可以&a
int* p = &a;
int* p = &42; //42是右值,无法取地址
二师兄:一般左值存在内存中,而右值存在寄存器中。
int a = 42, b = 1024;
decltype(a+b); //类型为右值,a+b返回的值存在寄存器中
decltype(a+=b); //类型为左值,a+=b返回的值存储在内存中
二师兄:严格意义上分,右值分为纯右值(
pvalue)和将亡值(xvalue)。C++中,除了右值剩余的就是左值。
42; //纯右值
int a = 1024;
std::move(a); //将亡值
面试官:C++98/03中已经有了左值,为什么还要增加右值的概念?
二师兄:主要是为了效率。特别是
STL中的容器,当需要把容器当作参数传入函数时:
void function(std::vector<int> vi2)
{
vi2.push_back(6);
for(auto& i: vi2) { std:: cout < i << " " ;}
std::cout << std::endl;
}
int main(int argc, char* argv[])
{
std::vector<int> vi1{1,2,3,4,5};
function(vi1);
return 0;
}
二师兄:当我们要把
vi1传入函数时,在C++98/03时只能通过拷贝构造函数,把vi1中所有的元素全部拷贝一份给vi2,拷贝完成之后,当function函数返回时,vi2被析构,然后vi1被析构。二师兄:在C++11及之后,我们可以通过
std::move()把vi1强制转为右值,此时在初始化vi2时执行的不是拷贝构造而是移动构造:
void function(std::vector<int>&& vi2)
{
vi2.push_back(6);
for(auto& i: vi2) { std:: cout < i << " " ;}
std::cout << std::endl;
}
int main(int argc, char* argv[])
{
std::vector<int> vi1{1,2,3,4,5};
function(std::move(vi1));
return 0;
}
二师兄:这里只进行了一次构造。一次移动(当元素特别多时,移动的成本相对于拷贝基本可以忽略不记),一次析构。效率得到很大的提升。
二师兄:当然,移动过后的变量已经不能再使用(身体被掏空),在
std::move(vi1)之后使用vi1是未定义行为。面试官:好的。那你知道移动构造是如何实现的吗?
二师兄:移动构造是通过移动构造函数实现的,当类有资源需要管理时,拷贝构造会把资源复制一份,而移动构造偷走了原对象的资源。
struct Foo
{
int* data_;
//copy construct
Foo(const Foo& oth)
{
data_ = new int(*oth.data_);
}
//move construct
Foo(Foo&& oth) noexcept
{
data_ = oth.data_; //steal
oth.data_ = nullptr; //set to null
}
}
面试官:好的。你觉得移动构造函数的
noexcept关键字能省略吗?为什么?二师兄:应该不能吧,具体不清楚。
面试官:那你知道std::move是如何实现的吗?
二师兄:好像是
static_cast实现的吧。面试官:那你知道什么叫万能引用吗?
二师兄:万能引用主要用在模板中,模板参数是
T,形参是T&&,此时可以传入任何类型的参数,所以称之为万能引用。
template<typename T>
void function(T&& t) { ...}
面试官:那你知道万能引用是如何实现的吗?
二师兄:不太清楚。。
面试官:完美转发知道吗?
二师兄:
std::forward吗,了解过一些,不太熟悉。面试官:好的,回去等消息吧。
让我们来回顾以下二师兄今天的表现:
移动构造函数的
noexcept关键字能省略吗?为什么?
这里尽量不要省略。如果省略,编译器会推断是否会抛出异常。如果移动构造函数可能会抛出异常,则编译器不会将其标记为noexcept。当编译器不标记为noexcept时,为了保证程序的正确性,编译器可能会采用拷贝构造的方式实现移动构造,从而导致效率降低。
需要注意的是,如果标记了noexcept但在移动时抛出了异常,则程序会调用std::terminate()函数来终止运行。
知道std::move是如何实现的吗?
这里的确是通过static_cast实现的,讲左值强行转换成右值,用来匹配移动语义而非拷贝。
template<typename T>
typename std::remove_reference<T>::type&& move(T&& t) { return static_cast<typename std::remove_reference<T>::type&&>(t);}
万能引用是如何实现的?
万能引用主要使用了引用折叠技术,
template<typename T>
void function(T&& t) { ...}
当T类型为左值时,&& & 被折叠为&, 当T类型为右值时,&& &&被折叠称为&&。以下是折叠规则:
& & -> &
& && -> &
&& & -> &
&& && -> &&
完美转发知道吗?
当我们需要在function中传递t参数时,如何保证它的左值或右值语义呢?这时候完美转发就登场了:
template<typename T>
void function2(T&& t2) {}
template<typename T>
void function(T&& t)
{
function2(t);
}
当传入的参数t的类型时右值时,由于引用折叠还是右值,此时的t虽然时一个右值引用,但t本身却是一个左值!这里非常的不好理解。如果我们把t直接传入到function2,那么function2中的t2会被推导成左值,达不到我们的目标。如果在调用function2时传入std::move(t),当t是右值时没有问题,但当t是左值时,把t移动到t2,t在外部不在能用。这也不符合我们的预期。此时std::forward闪亮登场!
template<typename T>
void function2(T&& t2) {}
template<typename T>
void function(T&& t)
{
function2(std::forward<T&&>(t));
}
std::forward使用了编译时多态(SFINAE)技术,使得当参数t是左值是和右值是匹配不同的实现,完成返回不同类型引用的目的。以下是标准库的实现:
template <typename _Tp>
constexpr _Tp && forward(typename std::remove_reference<_Tp>::type &&__t) noexcept
{
return static_cast<_Tp &&>(__t);
}
template <typename _Tp>
constexpr typename std::remove_reference<_Tp>::type && move(_Tp &&__t) noexcept
{
return static_cast<typename std::remove_reference<_Tp>::type &&>(__t);
}
好了,今日份面试到这里就结束了。二师兄的表现如何呢?预知后事如何,且听下回分解。
关注我,带你21天“精通”C++!(狗头)
C++面试八股文:什么是左值,什么是右值?的更多相关文章
- 话说C++中的左值、纯右值、将亡值
写在前面 C++中有“左值”.“右值”的概念,C++11以后,又有了“左值”.“纯右值”.“将亡值”的概念.关于这些概念,许多资料上都有介绍,本文在拾人牙慧的基础上又加入了一些自己的一些理解,同时提出 ...
- c++ 11 移动语义、std::move 左值、右值、将亡值、纯右值、右值引用
为什么要用移动语义 先看看下面的代码 // rvalue_reference.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #includ ...
- C++ 11 左值,右值,左值引用,右值引用,std::move, std::foward
这篇文章要介绍的内容和标题一致,关于C++ 11中的这几个特性网上介绍的文章很多,看了一些之后想把几个比较关键的点总结记录一下,文章比较长.给出了很多代码示例,都是编译运行测试过的,希望能用这些帮助理 ...
- C++雾中风景10:聊聊左值,纯右值与将亡值
C++11的版本在类型系统上下了很大的功夫,添加了诸如auto,decltype,move等新的关键词来简化代码的编写与降低阅读代码的难度.为了更好的理解这些新的语义,笔者确定通过几篇文章来简单窥探一 ...
- 深入学习c++--左值引用和右值引用
#include <iostream> #include <string> #include <vector> using namespace std; int m ...
- c++11 左值引用、右值引用
c++11 左值引用.右值引用 #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #i ...
- 左值与右值,左值引用与右值引用(C++11)
右值引用是解决语义支持提出的 这篇文章要介绍的内容和标题一致,关于C++ 11中的这几个特性网上介绍的文章很多,看了一些之后想把几个比较关键的点总结记录一下,文章比较长.给出了很多代码示例,都是编译运 ...
- C++11的左值引用与右值引用总结
概念 在C++11中,区别表达式是左值或右值可以做这样的总结:当一个对象被用作右值的时候,用的是对象的值(内容):当对象被用作左值的时候,用的是对象的身份(在内存中的位置).左值有持久的状态,而右值要 ...
- C++11左值引用和右值引用
转载:https://www.cnblogs.com/golaxy/p/9212897.html C++11的左值引用与右值引用总结 概念 1.&与&& 对于在C++中,大家 ...
- 【C/C++开发】C++11:左值引用VS右值引用
左值引用VS右值引用 左值引用对于一般的C++程序员再熟悉不过,但对于右值引用(C++0X新特性),就稍微有点不知所云 左值VS右值 在定义变量的时候,经常会用到左值和右值,比如:int a = 1; ...
随机推荐
- JSTL标签fmt:formatDate格式化日期出错
现象&背景: 异常: "org.apache.jasper.JasperException: 在 [115] 行处理 [/WEB-INF/jsp/modules/receivedya ...
- Kubernetes(k8s)二进制高可用安装脚本
好久没写公众号了,昨天新写了一个v1.24版本的安装.写得不咋样,但是能用.最近不高产了,没灵感了 = . = 手动部署:https://github.com/cby-chen/Kubernetes ...
- [Linux]CentOS7:卸载、安装Java JDK
JDK(Java Development Kit)是Java语言的软件开发工具包,包括Java运行环境.Java开发工具.Java基础类库. JRE(Java Runtime Environment) ...
- 四月九号java知识
1.do{}while();和while(){}结构最主要区别就是前者后面要一个分号 2.System.out.print();与System.out.println();的区别后者输出换行, 前者不 ...
- 手写 HashSet的底层 和 迭代器
1 package Test.CollectionIterator; 2 import java.util.Iterator; 3 public class MyHashSet2<E> i ...
- oracle逻辑备份exp导出指定表名时需要加括号吗?
Oracle 的exp.imp.expdp.impdp命令用于数据库逻辑备份与恢复; exp命令用于把数据从远程数据库server导出至本地,生成dmp文件. 笔者在实操中遇到: $exp user/ ...
- Nginx配置https并监听80端口重定向到443
1.进入nginx安装目录,进入config文件夹编辑nginx.conf文件 vim nginx.conf 配置端口 443 listen 443 http2 ssl default_server ...
- RedisTemplate在拦截器前没有注入的问题
RedisTemplate为null的问题 最近在搭建一个项目,然后项目框架采用的是spring boot,然后登录我就使用新学习的JWT嘛,然后就想着在请求进来的时候使用拦截器先对传进来的token ...
- vscode使用git推送代码
下载vscode https://code.visualstudio.com/ 点击应用管理 搜素Chinese (Simplified) Language Pack for Visual Studi ...
- VUE旅程(2)
继续我的vue踩坑之旅... 电商网站都会有搜索栏,输入内容后在搜索结果页面要带入搜索的关键字,需求就从这里来.代码如下: <v-search :keyword="this.$rout ...