作者:神奇先生
链接: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()的适用场景(转)的更多相关文章

  1. [转]C语言的那些秘密之---函数返回局部变量

    一般的来说,函数是可以返回局部变量的. 局部变量的作用域只在函数内部,在函数返回后,局部变量的内存已经释放了.因此,如果函数返回的是局部变量的值,不涉及地址,程序不会出错.但是如果返回的是局部变量的地 ...

  2. C语言的那些秘密之---函数返回局部变量[转]

    来源:http://blog.csdn.net/haiwil/article/details/6691854/ 一般的来说,函数是可以返回局部变量的. 局部变量的作用域只在函数内部,在函数返回后,局部 ...

  3. C-基础:函数返回局部变量

    一般的来说,函数是可以返回局部变量的. 局部变量的作用域只在函数内部,在函数返回后,局部变量的内存已经释放了.因此,如果函数返回的是局部变量的值,不涉及地址,程序不会出错.但是如果返回的是局部变量的地 ...

  4. C++函数返回局部变量

    函数不能返回指向栈内存的指针 原因:返回值是拷贝值,局部变量的作用域为函数内部,函数执行结束,栈上的局部变量会销毁,内存释放. 可返回的局部变量: 1. 返回局部变量本身 int sum(int a, ...

  5. CPP笔记_函数返回局部变量

    本篇笔记记录的是关于返回函数中的局部值. 我们知道,在函数中创建的局部变量会随着函数的调用过程的结束,也即其对应函数栈帧的清除,而结束其生命周期.那么,如果我们把这个局部变量返回,就有可能存在该变量对 ...

  6. python函数返回局部变量,局部&全局变量同名问题

    其实关于返回局部变量不只是python的问题,凡是使用堆栈结构处理函数的语言都会有这样的问题,切记不要返回局部变量.因为当创建函数的堆栈撤销,所有对局部变量的修改都灰飞烟灭.来看我的小例子 def h ...

  7. Item 25: 对右值引用使用std::move,对universal引用则使用std::forward

    本文翻译自<effective modern C++>,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 右值引用只能绑定那些有资格被move的对象上去.如 ...

  8. item 23: 理解std::move和std::forward

    本文翻译自<effective modern C++>,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 根据std::move和std::forward不 ...

  9. c++11 std::move()

    简单点理解,c++11 中的std::move() 函数,实际上就是将一个左值强制转换成一个右值引用数据类型,从而可以调用相应的对右值引用重载的函数. 如果使用std::move() 的返回值做为参数 ...

随机推荐

  1. MVC的Ajax异步请求

    @using (Ajax.BeginForm("GetTime","order",new AjaxOptions() { Confirm="你确认这么 ...

  2. [转]Linux系统下如何查看及修改文件读写权限

    转自 :http://www.cnblogs.com/CgenJ/archive/2011/07/28/2119454.html 查看文件权限的语句: 在终端输入:ls -l xxx.xxx (xxx ...

  3. [转]windows7X64环境下wamp开启PHP_Curl组件

    From : http://www.justintseng.com/windows7x64-environment-wamp-open-php_curl-in-components 装上64位的win ...

  4. PHP 5.4.17 发布!

    PHP 5.4.17发布.2013-07-04 经过1个RC 上个版本是2013-06-07的5.4.16.修正了大约20个Bug以及几个安全漏洞.尽管5.5.0正式版已经发布.但5.4还未停止更新. ...

  5. maskrcnn_benchmark代码分析(1)

    可以先参考:Faster-RCNN代码+理论——1/2 Object Detection and Classification using R-CNNs 使用ipdb调试 try: import ip ...

  6. Spring boot Unable to start embedded Tomcat报错 java.lang.NoSuchMethodError: javax.servlet.ServletContext.getVirtualServerName()

    Spring boot Unable to start embedded Tomcat 报错 java.lang.NoSuchMethodError: javax.servlet.ServletCon ...

  7. CSS3动画的回调处理

    我们在做js动画的时候,很多时候都需要做回调处理,如在一个动画完成后触发一个事件.一个动画完成后执行另外一个动画等等,但在使用CSS3动画时能不能捕获到运动的状态做回调处理呢? CSS3动画也是可以做 ...

  8. HTML5读取本地文件

    本文转自:转:http://hushicai.com/2014/03/29/html5-du-qu-ben-di-wen-jian.html感谢大神分享. 常见的语言比如php.shell等,是如何读 ...

  9. Ios开发之多线程编程——NSThread

    IOS程序在运行的时候是通过主线程来进行UI视图的更新和响应屏幕触摸事件,但是,在视图更新的时候,会有一些非常耗时的工作,这样我们会出现系统出现卡顿的现象,这是因为主线程堵塞造成的,这样会使用户体验非 ...

  10. Mahout初体验

    Mahout运行版本: mahout-0.5, mahout-0.6, mahout-0.7,是基于hadoop-0.20.2x的. mahout-0.8, mahout-0.9,是基于hadoop- ...