每周技巧 #163:传递 `std::optional` 参数

本节阅读量:

本文翻译自 Abseil 官网的 Tip of the Week #163: Passing std::optional parameters

原文最初作为 TotW #163 发布于 2019 年 7 月 11 日。

作者:Ian Eldred Pudney

更新于 2020 年 4 月 6 日。

快捷链接:abseil.io/tips/163

null 真的是十亿美元错误吗?

问题

假设你需要实现一个函数,它有一个可能存在也可能不存在的参数。你可能会想用现代、花哨的 std::optional 来做。不过,如果对象大到应该按引用传递,那么 std::optional 可能不是你想要的。考虑下面两个声明:

1
2
void MyFunc(const std::optional<Foo>& foo);  // 可能按值复制
void MyFunc(std::optional<const Foo&> foo);  // 无法编译

第一个选项很可能不会做你想要的事。如果有人把 std::optional<Foo> 传给 MyFunc,它会按引用传递;但如果有人把 Foo 传给 MyFunc(例如作为返回值),这个 Foo 会被按值复制到一个临时 std::optional<Foo> 中,然后这个临时对象再按引用传给函数。如果你的目标是避免复制 Foo,那你并没有做到。

第二个选项本来很好,但遗憾的是 std::optional 不支持它。

建议

避免形如 const std::optional& 的函数参数。

如果对象小到不需要按引用传递,应该按值接收包在 std::optional 中的对象。例如:

1
2
void MyFunc(std::optional<int> bar);
void MyFunc(std::optional<absl::string_view> baz);

如果你是有意复制实参,也应该按值接收 std::optional,以明确表达这一点:

1
void MyFunc(std::optional<Foo> foo);

否则,完全跳过 std::optional

可以按 absl::Nullable<const Foo*> 传递,并让 nullptr 表示“不存在”。

1
void MyFunc(absl::Nullable<const Foo*> foo);

这会和按 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 不受支持这一事实:

1
void MyFunc(std::optional<std::reference_wrapper<const Foo>> foo);

不过,我们不推荐这样做:

  • std::reference_wrapper 的语义出人意料地微妙,很难理解并安全使用。例如,标准库中的各种工具会对它做特殊处理,让它表现得不同于普通值或引用。
  • absl::Nullable<const Foo*> 相比,std::optional<std::reference_wrapper<const Foo>> 笨重又啰嗦。

每周技巧 #161:好的局部变量和坏的局部变量

上一节

每周技巧 #165:带初始化器的 `if` 和 `switch` 语句

下一节