每周技巧 #163:传递 `std::optional` 参数
本节阅读量:本文翻译自 Abseil 官网的 Tip of the Week #163: Passing std::optional parameters。
原文最初作为 TotW #163 发布于 2019 年 7 月 11 日。
更新于 2020 年 4 月 6 日。
快捷链接:abseil.io/tips/163
null 真的是十亿美元错误吗?
问题
假设你需要实现一个函数,它有一个可能存在也可能不存在的参数。你可能会想用现代、花哨的 std::optional 来做。不过,如果对象大到应该按引用传递,那么 std::optional 可能不是你想要的。考虑下面两个声明:
|
|
第一个选项很可能不会做你想要的事。如果有人把 std::optional<Foo> 传给 MyFunc,它会按引用传递;但如果有人把 Foo 传给 MyFunc(例如作为返回值),这个 Foo 会被按值复制到一个临时 std::optional<Foo> 中,然后这个临时对象再按引用传给函数。如果你的目标是避免复制 Foo,那你并没有做到。
第二个选项本来很好,但遗憾的是 std::optional 不支持它。
建议
避免形如 const std::optional& 的函数参数。
如果对象小到不需要按引用传递,应该按值接收包在 std::optional 中的对象。例如:
|
|
如果你是有意复制实参,也应该按值接收 std::optional,以明确表达这一点:
|
|
否则,完全跳过 std::optional。
可以按 absl::Nullable<const Foo*> 传递,并让 nullptr 表示“不存在”。
|
|
这会和按 const Foo& 传递一样高效,但支持 null 值。何时用指针而不是 const 引用,更多内容见 技巧 #116。
那 std::optional 到底是干什么用的???
当你拥有那个可选的东西时,可以使用 std::optional。例如,类成员和函数返回值通常很适合用 std::optional。
例外
如果你预计函数的所有调用方都已经有 std::optional<Foo>,并且永远不会传入 Foo,那么可以接收 const std::optional<Foo>&。不过这很少见,通常只出现在你自己文件/库内部的私有函数中。
std::reference_wrapper 怎么办?
std::optional 的文档指出,可以使用 std::reference_wrapper 来绕过 optional reference 不受支持这一事实:
|
|
不过,我们不推荐这样做:
std::reference_wrapper的语义出人意料地微妙,很难理解并安全使用。例如,标准库中的各种工具会对它做特殊处理,让它表现得不同于普通值或引用。- 与
absl::Nullable<const Foo*>相比,std::optional<std::reference_wrapper<const Foo>>笨重又啰嗦。