10 | 到底应不应该返回对象?
该思维导图由 AI 生成,仅供参考
F.20
调用者负责管理内存,接口负责生成
- 深入了解
- 翻译
- 解释
- 总结
C++中返回对象的问题一直备受关注。本文首先介绍了传统的C风格做法,即调用者负责管理内存,接口负责生成对象,并分析了这种做法的不足之处。接着提出了另一种可能的做法,即接口负责对象的堆上生成和内存管理,但同样存在繁琐的问题。最后,作者提出了直接返回对象的做法,并通过实际代码展示了这种做法的简洁性和性能优势。文章还介绍了返回对象的构造、赋值和移动支持。通过对比不同的做法,强调了直接返回对象的简洁性和性能优势。文章还讨论了返回值优化的相关内容,以及C++17对返回对象的优化。总的来说,本文为读者提供了在C++中返回对象时的参考和建议,强调了直接返回对象的简洁性和性能优势。
《现代 C++ 编程实战》,新⼈⾸单¥59
全部留言(25)
- 最新
- 精选
- 小一日一我认为老师应该讲一下NRVO/RVO与std::move()的区别,这个问题曾经困扰过我,从stackoverflow的问题来看,学习c++11时大多数人都思考过这个问题:https://stackoverflow.com/questions/4986673/c11-rvalues-and-move-semantics-confusion-return-statement
作者回复: 简单来说,在对本地变量进行返回时,不用 std::move。实际上,我在第 3 讲就写了: “有一种常见的 C++ 编程错误,是在函数里返回一个本地对象的引用。由于在函数结束时本地对象即被销毁,返回一个指向本地对象的引用属于未定义行为。理论上来说,程序出任何奇怪的行为都是正常的。 “在 C++11 之前,返回一个本地对象意味着这个对象会被拷贝,除非编译器发现可以做返回值优化(named return value optimization,或 NRVO),能把对象直接构造到调用者的栈上。从 C++11 开始,返回值优化仍可以发生,但在没有返回值优化的情况下,编译器将试图把本地对象移动出去,而不是拷贝出去。这一行为不需要程序员手工用 `std::move` 进行干预——使用`std::move` 对于移动行为没有帮助,反而会影响返回值优化。”
2019-12-18550 - 小白兔纸白又白请问有何方法可以测试编译器是否为一个类提供了右值引用的版本的成员函数
作者回复: 好问题。利用第 14 讲的 SFINAE 技巧,是可以写出来的。比如,你希望检测是不是有下面第一个重载: class Obj { public: void foo() &&; void foo() const &; }; 可以定义下面这样的 type trait: template <typename T, typename = void_t<>> struct has_rvalue_ref_foo : false_type {}; template <typename T> struct has_rvalue_ref_foo< T, void_t<decltype(static_cast<void (T::*)() &&>(&T::foo))>> : true_type {}; 上面第一个 foo 的重载在的话,使用 has_rvalue_ref_foo<Obj>::value 就能得到编译期常量 true。
2019-12-31213 - 怪兽吴老师,求助,我把getA_duang函数修改为: A getA_duang() { A a1; A a2; return (rand() > 42 ? a1 : a2); } 得到的结果是: Create A Create A Copy A Destroy A Destroy A Destroy A 请问为什么用条件表达式,得到的结果是 Copy A,而用你范例中的 if 语句判断就能得到 Move A呢?
作者回复: 因为 (rand() > 42 ? a1 : a2) 的结果是一个 A 的左值引用,return 语句返回的是左值引用,不是变量,所以就不行了。要移动需要自己加上 std::move。
2021-08-247 - MilittleArmadillo这个库我用过,挺好用。语法可以和Matlab互转,如果有用Matlab的小伙伴 可以推荐使用 性能杠杠的
作者回复: 对,好用非常重要!
2020-02-257 - 阿白老师看到你和别的同学讨论的问题,return :?中:?表达式返回的是左值引用,所以调用的是拷贝构造函数。但是return a1,a1是个标识表达式是个左值为什么就是调用移动构造。我不明白在函数声明返回值为值类型,但是:?返回一个左值引用的时候为什么调用的是拷贝构造? A getA_duang() { A a1; A a2; return (rand() > 42 ? a1 : a2); } Create A Create A Copy A Destroy A Destroy A Destroy A auto a = getA_duang();
作者回复: 函数返回一个本地变量是有特殊规则的。请在 cppreference 上查阅 return 和 copy elision 的文档。 可以类比一下 decltype(规则还是不一样的)来获得一些直观的感受。如果你有 int a; 和 int b; 两个变量,decltype(a) 会得到 int,decltype(a > b ? a : b) 则得到 int&。
2021-11-036 - 空气我在工作中使用引用出参的场景之一是同时返回多个对象,如果使用返回值就要封装很多不同结构体。请问老师这种场景建议怎么实现?
作者回复: 如果都是返回而非修改的话,可以使用 pair、tuple、tie 和第 8 讲讨论的结构化绑定。
2020-01-046 - 木瓜777项目中一直使用您说的老方法,目前看编译器有优化的话,后面会逐步考虑采用返回对象的方法! 有个问题问下,如果要返回空对象,该如何做? 是直接采用空的构造函数?
作者回复: 用默认构造函数代表空,或者用 optional<对象> (不构造)代表空,或者抛异常代表不正常(视是否不正常而定)。 optional 会在第 22 讲里讨论。
2019-12-186 - 泰伦卢请问老师这个C++20什么时候发布编译器之类的啊?还是说已经有了?
作者回复: 看这个页面吧: https://en.cppreference.com/w/cpp/compiler_support 目前 GCC 领先一些(可以用 -std=c++2a 启用 20 的功能),但还没有哪家完整支持 C++20。
2019-12-1834 - 怪兽老师,请教2个疑惑: 1. 在返回值优化(拷贝消除)小节里,A(const A&) = delete;了,但A(A&&)只是注释掉,不是说编译器会提供默认的移动构造函数吗?为什么getA_named也不行了?不是优先匹配移动构造吗? A getA_named() { A a; return a; } 2. 哪种情况下移动的代价高?我理解移动的代价都很低,至少比拷贝低吧?
作者回复: 1. 可以参考cppreference.com 网站,或者第 9 讲,里面提到,“用户如果没有自己声明拷贝构造函数、拷贝赋值函数、移动赋值函数和析构函数,编译器会隐式声明一个移动构造函数”。“= default”和“= delete”这种也算声明。 2. 移动的代价是看实现的,也就是我们例子里的的 A(A&&)。像我们的玩具例子,移动和拷贝性能就没啥区别。说移动代价高,也不是指比拷贝高,而是指拷贝代价高且没有对移动做优化(如没有移动构造函数),或对象太大,没法做移动优化(如 sizeof 达到数百个字节以上)。
2021-05-183 - 花晨少年我们继续变形一下: #include <stdlib.h> A getA_duang() { A a1; A a2; if (rand() > 42) { return a1; } else { return a2; } } int main() { auto a = getA_duang(); } 这回所有的编译器都被难倒了,输出是: Create A Create A Move A Destroy A Destroy A Destroy A ——————— 老师这个结果应该还是会有优化在的吧?如果完全没有优化应该是两个移动才对,a1或者a2移动给返回值是一次,返回值移动给a又是一次,如果真是这样,哪次被优化掉了?第二次吗
作者回复: C++编译器哪会做这么不必要的事……就是一次移动。如果有返回值优化的话,一次移动都不会有。
2019-12-2143