C++ 函数返回局部变量的std::move()的适用场景(转)
链接:https://www.zhihu.com/question/57048704/answer/151446405
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
编程时经常会写的一种函数叫做named constructor,这种函数的返回值是某个类的实例,其实本质上就是一种构造函数,但是因为可能需要在构建时执行一些其他的步骤,所以没有写成constructor的形式。比如:
User create_user(const std::string &username, const std::string &password) {
User user(username, password);
validate_and_save_to_db(user);
return user;
}
void signup(const std::string &username, const std::string &password) {
auto new_user = create_user(username, password);
login(user);
}
这里create_user就是一个named constructor。
一些c++初学者可能会觉得这个代码不够优化,因为按照这个代码字面意思来理解,create_user创建先创建了一个user,然后返回时又把user赋值给new_user,这个赋值会copy user里面的内容,如果user很大的话(很有可能user里面存了很多信息,比如username这种string的类型),这样太慢(copy string可能还需要多做一次malloc)。
这样难免一些人会想用c++11引入的move来优化,因为create_user里面的user return了之后就没用了,我可以把user move到new_user这样不就省掉了copy时很大的开销了么(比如user里面的username就不需要malloc新内存也不需要一个个字符copy了)。
但事实上,在这种简单的情况下编译器比你更聪明,编译器可以直接把user创建在new_user里,所以user只被创建一次,没有任何copy开销,user和new_user经过编译器优化之后其实是同一个variable!这种优化就叫做copy elision。但是很不幸的是,如果用户想自己用move优化的话,编译器就不用做copy elision了,只能乖乖地按照用户说的来,先创建一个user,然后在调用User的move constructor来创建new_user。这样肯定比前一种开销大很多。这就是为什么clang非常“聪明”地给题主的例子给了一个warning。
接下来我们再说说我们怎么能知道编译器会不会对我写的函数做copy elision的优化呢?有没有可能我写的函数逻辑特别复杂,编译器没法优化呢?如果有的话,我如果写return move(a)不就会比copy更快了吗?
这个逻辑是正确的,编译器其实很傻,一旦create_user里面的逻辑太复杂,编译器可能就没办法分析出你能不能用一个变量取代两个(user和new_user),那它就不做copy elision了。这时候用move就合情合理。
那到底什么时候应该move,什么时候应该依靠copy elision呢?通常主流的编译器都会100% copy elision以下两种情况:
1. URVO(Unnamed Return Value Optimization):函数的所有执行路径都返回同一个类型的匿名变量,比如
User create_user(const std::string &username, const std::string &password) {
if (find(username)) return get_user(username);
else if (validate(username) == false) return create_invalid_user();
else User{username, password};
}
这里所有的return都返回一个User类型,且每个返回的都是一个匿名变量。那编译器100%会执行copy elision。
2. NRVO(Named Return Value Optimization):函数的所有路径都返回同一个非匿名变量,比如
User create_user(const std::string &username, const std::string &password) {
User user{username, password};
if (find(username)) {
user = get_user(username);
return user;
} else if (user.is_valid() == false) {
user = create_invalid_user();
return user;
} else {
return user;
}
}
这里因为所有路径都返回同一个变量user。编译器100%会执行copy elision。
其他的情况编译器可能都不会使用copy elision的优化。
首先 URVO 在 C++17 是强制的。不过 NRVO 不是强制,意味着有时不这么优化也是允许的。
其次,若 return 的表达式是符合返回类型的左值,且编译器没有进行复制省略,那么标准(C++11 开始)也要求编译器先试图把表达式当右值,优先匹配移动构造函数(再匹配通常的复制构造函数),若失败的话则再将其当左值,匹配接受非 const 引用的复制构造函数。
所以按照标准,上面的 std::move(a) 是不必要的(除非你希望强制调用移动构造函数),编译器在必要时会做同样的处理。
作者:神奇先生 &暮无井见铃
链接:https://www.zhihu.com/question/57048704/answer/151453824
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
C++ 函数返回局部变量的std::move()的适用场景(转)的更多相关文章
- [转]C语言的那些秘密之---函数返回局部变量
一般的来说,函数是可以返回局部变量的. 局部变量的作用域只在函数内部,在函数返回后,局部变量的内存已经释放了.因此,如果函数返回的是局部变量的值,不涉及地址,程序不会出错.但是如果返回的是局部变量的地 ...
- C语言的那些秘密之---函数返回局部变量[转]
来源:http://blog.csdn.net/haiwil/article/details/6691854/ 一般的来说,函数是可以返回局部变量的. 局部变量的作用域只在函数内部,在函数返回后,局部 ...
- C-基础:函数返回局部变量
一般的来说,函数是可以返回局部变量的. 局部变量的作用域只在函数内部,在函数返回后,局部变量的内存已经释放了.因此,如果函数返回的是局部变量的值,不涉及地址,程序不会出错.但是如果返回的是局部变量的地 ...
- C++函数返回局部变量
函数不能返回指向栈内存的指针 原因:返回值是拷贝值,局部变量的作用域为函数内部,函数执行结束,栈上的局部变量会销毁,内存释放. 可返回的局部变量: 1. 返回局部变量本身 int sum(int a, ...
- CPP笔记_函数返回局部变量
本篇笔记记录的是关于返回函数中的局部值. 我们知道,在函数中创建的局部变量会随着函数的调用过程的结束,也即其对应函数栈帧的清除,而结束其生命周期.那么,如果我们把这个局部变量返回,就有可能存在该变量对 ...
- python函数返回局部变量,局部&全局变量同名问题
其实关于返回局部变量不只是python的问题,凡是使用堆栈结构处理函数的语言都会有这样的问题,切记不要返回局部变量.因为当创建函数的堆栈撤销,所有对局部变量的修改都灰飞烟灭.来看我的小例子 def h ...
- Item 25: 对右值引用使用std::move,对universal引用则使用std::forward
本文翻译自<effective modern C++>,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 右值引用只能绑定那些有资格被move的对象上去.如 ...
- item 23: 理解std::move和std::forward
本文翻译自<effective modern C++>,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 根据std::move和std::forward不 ...
- c++11 std::move()
简单点理解,c++11 中的std::move() 函数,实际上就是将一个左值强制转换成一个右值引用数据类型,从而可以调用相应的对右值引用重载的函数. 如果使用std::move() 的返回值做为参数 ...
随机推荐
- An internal error occurred during: "Building workspace". GC overhead limit exceeded
1 错误描写叙述 2 错误原因 3 解决的方法
- Java Date 时分秒置0
Date now = new Date(); Calendar cal1 = Calendar.getInstance(); cal1.setTime(now); // 将时分秒,毫秒域清零 cal1 ...
- 拦截器通过Spring获取工厂类,注入bean对象
// 这里需要注意一点,我们在拦截器内无法通过SpringBean的方式注入LoggerJPA,我只能通过另外一种形式. /** * 根据传入的类型获取spring管理的对应dao * @param ...
- FreeMarker:Java模板引擎
本资源由 伯乐在线 - 黄余粮 整理 FreeMarker 是一个模板引擎,一个基于模板生成文本输出的通用工具,使用纯Java编写.FreeMarker被设计用来生成HTML Web页面,特别是基于M ...
- composer - No business network has been specified for this connection 解决方案
I have installed hyperledger composer locally. But on localhost it gives error : Error : Error tryin ...
- [转]PHP traits
From : http://www.php.net/manual/zh/language.oop5.traits.php 自 PHP 5.4.0 起,PHP 实现了代码复用的一个方法,称为 trait ...
- 回顾 Exchange 2007 SCC 安装-供需要的人参考!
最近可能会涉及到一个项目的升级,客户目前是基于SCC+SCR的一种工作模式,因为之前对SCR 了解很少,所以需要搭建一个SCC+SCR 平台来做一个整体的POC,来还原整个安装和升级过程. 首先我们先 ...
- go语言之进阶篇正则表达式
正则表达式是一种进行模式匹配和文本操纵的复杂而又强大的工具.虽然正则表达式比纯粹的文本匹配效率低,但是它却更灵活.按照它的语法规则,随需构造出的匹配模式就能够从原始文本中筛选出几乎任何你想要得到的字符 ...
- Poly2Tri介绍[转]
https://blog.csdn.net/xys206006/article/details/83002326 这是Poly2Tri介绍的翻译博文.原文链接:http://sites-final.u ...
- 前端基于jquery的UI框架
正在做的一个项目选择jquery作为前端js核心库.然后就想选一个基于jquery的ui库,然后悲催的事情发生了. 至于为什么使用jquery,一是因为不想为授权费用,而又不想引起可能法律纠纷:另一方 ...