现代 C++ 编程实战
吴咏炜
前 Intel 资深软件架构师
34199 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 51 讲
加餐 (1讲)
现代 C++ 编程实战
15
15
1.0x
00:00/00:00
登录|注册

41|对象传参和返回的最佳实践

重载解决二义性
C++17 新特性
性能瓶颈
基础库设计
性能测试结果
使用模板和 enable_if
现代化建议
适用于构造函数
缺点:可能不充分利用已分配内存
优点:简化代码、强异常安全性
示例:employee 类的 set_name 方法
移动构造和移动赋值
移动构造函数和移动赋值运算符
使用 unique_ptrmake_shared
移动代价成为衡量标准
堆上分配返回指针
const 引用传递
值传递
引用传递
出参方式
返回值
入参且保留一份
入参
入/出参
出参
复制代价高
复制代价中
复制代价低
C++98 对象传递方式
Herb Sutter 总结
[4] YouCompleteMe plugin for Vim
[3] LLVM project, clangd
[2] Copy-and-swap idiom on Stack Overflow
[1] Herb Sutter's CppCon 2014 Talk
noexcept 说明的写法
极致性能的优化方法
传统方式仍然适用
极致优化的考虑
对字符串字面量友好
string_view 的引入
适用场景
完美转发
构造函数中的应用
拷贝并交换惯用法
针对移动的优化
建议的调整
移动语义的引入
传递方式
函数参数类型
对象类型分类
基本出发点
参考资料
课后思考
内容小结
字符串的特殊处理
“不可教授”的极致性能传参方式
值传参的讨论
现代 C++ 的对象传递方式
传统的对象传递方式
对象传参和返回的最佳实践

该思维导图由 AI 生成,仅供参考

你好,我是吴咏炜。
第 10 讲我们讨论过,《C++ 核心指南》的 F.20 条款推荐我们优先使用返回对象的方式。在那里,我们简单地讨论了一些例外情况,并没有深入展开。同时,我们没有讨论传参形式的选择,而事实上,这两个问题是紧密相关的——尤其是考虑到出参和返回语句都是函数向外传递对象的方式。今天,我们就来深入探讨一下这个问题。

传统的对象传递方式

Herb Sutter 在 CppCon 2014 时早就总结过,传统的——即 C++98 的——对象传递方式应该是我们的基本出发点 [1]。可以用表格示意如下:
简单解释一下表格里的行列:
表格把对象的类型按复制代价分成三种,然后按出入参有四种不同的情况,分别进行讨论。
复制代价低指相当于拷贝几个整数的开销;复制代价中指大于几个整数的开销,直至约 1 KB 的连续内存,且不涉及内存分配;除此之外属于代价高的情况。
“出”指我们想要从函数中取得(返回)某个对象的情况;“入 / 出”指传递给函数且让函数修改该对象的情况;“入”指纯粹传递给函数作为参数且不修改该对象的情况;“入且保留一份”指函数会把参数指代的对象保存到某个地方,如类的成员变量或全局变量里。
当需要取得一个复制代价低到中的对象时,我们可以直接使用函数的返回值。由于 C++98 没有移动,复制代价高的对象只能使用出参的方式来返回,如容器。如果一个对象既是出参又是入参,那我们就只能使用按引用传递的出入参了。如果是纯粹的入参,那不管我们怎么使用,我们就只考虑复制代价:如果复制代价很低,比如小于等于两个指针的大小,那直接按值传递就好;否则,按 const 引用传递性能更高,明确表达了该函数不修改此入参的意图。对于入参的这两种方式,我们都无法修改调用方手里的对象。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

现代 C++ 中对象传参和返回的最佳实践涉及传统的对象传递方式和现代 C++ 的对象传递方式。传统方式建议优先使用返回对象的方式,但对于复制代价高的对象,推荐在堆上分配并返回指针。现代 C++ 中,移动的代价成为衡量标准,可以返回 `vector` 或 `unique_ptr` 等对象。针对移动的优化包括使用移动构造函数和移动赋值运算符。另外,值传参也是一种简化的写法,但在重复调用场景下可能不合适。总的来说,现代 C++ 中的对象传参和返回建议更加注重移动语义和性能优化。 文章还深入探讨了对象传递的各种方式,包括使用完美转发、`string_view` 的使用以及对于字符串特殊处理的讨论。对于追求极致性能的情况,可以使用重载、移动和完美转发来进行优化。对于大部分情况,较为传统的对象传递方式仍然是较为合理的默认值。而对于字符串字面量,使用 `string_view` 是一个对字符串字面量更为友好的选择。 总的来说,本文对于现代 C++ 中对象传参和返回的最佳实践进行了深入探讨,对于读者快速了解现代 C++ 中对象传参和返回的方式具有重要参考价值。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《现代 C++ 编程实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(5)

  • 最新
  • 精选
  • 李云龙
    老师,这个版本的set_name: void set_name(string_view name) { name_ = name; } 为什么给set_name函数传入一个右值字符串,会存在性能损失?string_view直接绑定到了一个右值上,除了生命周期陷阱,应该没有其他性能损失吧?

    作者回复: 如果使用 string&& 作为参数的话,是可以直接把字符串移动进去,不需要任何新内存分配,也不需要复制字符串内容。用 string_view 显然肯定会发生字符串内容复制,也有可能发生内存分配。 没有生命周期陷阱,如果 name_ 的类型是 string 的话。(string_view 不适合作为成员变量的类型,很危险。)

    2023-11-26归属地:北京
    1
  • Geek_1a7863
    在“不可教授”的极致性能传参方式小节中,模板参数 enable_if_t<is_assignable_v<string&, S>>>,为什么要用string的引用不用string呢??

    作者回复: 因为你写 x = y 的时候,x 的类型就应该是个左值引用。这里表达的就是允许从 S 类型(转发引用)到 string&(右值引用)的赋值。

    2023-01-19归属地:河北
    3
    1
  • 王小白白白
    当我们采用 string 的值传参时,对于左值参数,我们每次都必然会发生一次内存分配操作(通常还伴随着老的 name_ 的内存释放。 老师,这里的一次内存分配操作, 是指构造string参数吗

    作者回复: 是的,每次都构造了一个新的string。

    2022-09-02归属地:上海
    1
  • Ghostown
    吴老师请问,在Herb的那张图中,使用 const string& 且入参是xvalue时为什么性能也很好?(我理解既然是生成了string的临时对象,那么就需要拷贝或内存分配)但是看图上似乎并没有发生拷贝/内存分配?

    作者回复: 因为被赋值的对象本身有“容量”,所以(至少在若干次赋值之后)就不再有内存的分配,但会有内存的拷贝——这相比内存分配和释放来说就小多了。 如果使用 const string&、string&& 或 string 作为形参,那在实参为 const char* 且超过小字符串优化的允许范围时,都会临时构造一个 string,会多发生一次字符串复制和堆上内存分配,而只有完美转发的方式没有这种额外开销。

    2023-09-10归属地:上海
    3
  • 李聪磊
    这篇文章把各种情况梳理的太棒了,受益匪浅!
    2022-09-12归属地:北京
收起评论
显示
设置
留言
5
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部