moh*_*uje 10 c++ c++11 c++17 stdoptional
我正在将代码移植到 C++17,尝试尽可能使用新功能。我喜欢的一件事是使用std::optional在某些情况下可能会失败的函数中返回或不返回值的想法。
我很好奇这个新特性的可能用途,我正在考虑开始使用它来替换函数中的可选参数,所以:
void compute_something(int a, int b, const Object& c = Object(whatever)) {
// ...
}
Run Code Online (Sandbox Code Playgroud)
变成:
void compute_something(int a, int b, std::optional<Object> c) {
auto tmp = c.value_or(Object(whatever));
// ...
}
Run Code Online (Sandbox Code Playgroud)
根据官方文档:
如果一个可选项包含一个值,则保证该值作为可选对象占用空间的一部分进行分配,即永远不会发生动态内存分配。因此,即使定义了 operator*() 和 operator->() ,可选对象也建模对象,而不是指针。
因此,每次我们使用 std::optional 传递参数时,它意味着创建副本,如果对象很大,这可能会导致性能下降。
我喜欢这个想法,因为它使代码更简单易懂,但是有什么好处吗?
Astd::optional不是函数参数默认值的替代品:
void compute_something(int a, int b, const Object& c = Object(whatever))
Run Code Online (Sandbox Code Playgroud)
这可以调用 compute_something(0, 0);
void compute_something(int a, int b, std::optional<Object> c)
Run Code Online (Sandbox Code Playgroud)
这是无法编译的。compute_something(0, 0);不会编译。至少,你必须做一个compute_something(0, 0, std::nullopt);.
因此,每次我们使用 std::optional 传递参数时,它意味着创建副本,如果对象很大,这可能会导致性能下降。
正确的。但请注意,还需要构造一个默认的函数参数。
但是,可以通过组合做了一些小技巧std::optional用的std ::的reference_wrapper:
#include <optional>
#include <utility>
#include <functional>
#include <iostream>
class X {
public:
X()
{
std::cout << "Constructor" << std::endl;
}
~X()
{
std::cout << "Destructor" << std::endl;
}
void foo() const
{
std::cout << "Foo" << std::endl;
}
X(const X &x)
{
std::cout << "Copy constructor" << std::endl;
}
X &operator=(const X &)
{
std::cout << "operator=" << std::endl;
}
};
void bar(std::optional<std::reference_wrapper<const X>> arg)
{
if (arg)
arg->get().foo();
}
int main()
{
X x;
bar(std::nullopt);
bar(x);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
使用 gcc 7.2.1,唯一的输出是:
Constructor
Foo
Destructor
Run Code Online (Sandbox Code Playgroud)
这确实增加了一些语法,并且可能很麻烦。但是,一些额外的语法糖可以减轻额外的绒毛。例如:
if (arg)
{
const X &x=arg->get();
// Going forward, just use x, such as:
x.foo();
}
Run Code Online (Sandbox Code Playgroud)
现在,让我们再迈出一步:
void bar(std::optional<std::reference_wrapper<const X>> arg=std::nullopt)
Run Code Online (Sandbox Code Playgroud)
有了这个,两个函数调用可以简单地是:
bar();
bar(x);
Run Code Online (Sandbox Code Playgroud)
你可以吃蛋糕,也可以吃。您不必显式提供 a std::nullopt,由默认参数值提供;您不必构建整个默认对象,并且在显式传递对象时,它仍然通过引用传递。您只有自己的开销std::optional,在大多数 C++ 实现中,它只是一些额外的字节。
在不知道您的函数具体在做什么的情况下,很难给出一个好的通用答案,但是是的,使用optional. 没有特定的顺序:
首先,在包装函数时如何传播默认参数?使用标准语言默认参数,您只需要知道所有默认值是什么:
int foo(int i = 4);
int bar(int i = /* foo's default that I have to know here */) { return foo(i); }
Run Code Online (Sandbox Code Playgroud)
现在,如果我将foo的默认值更改为5,我必须知道要更改bar- 通常它们最终会不同步。用optional,只有执行foo需要知道默认值:
int foo(optional<int> );
int bar(optional<int> o) { return foo(o); }
Run Code Online (Sandbox Code Playgroud)
所以这不是问题。
其次,存在您提供参数或回退到默认值的情况。但也有一种情况,简单地没有参数也具有语义意义。比如,如果我给你这个参数,就使用它,否则什么都不做。使用默认参数,这必须用标记表示:
// you just have to know that -1 means no fd
int foo(int fd = -1);
Run Code Online (Sandbox Code Playgroud)
但是使用optional,这在签名和类型中清楚地表达了 - 您不必知道哨兵是什么:
int foo(std::optional<int> fd);
Run Code Online (Sandbox Code Playgroud)
对于较大尺寸的对象,缺少哨兵也会对性能产生积极影响,因为不必构造一个具有该哨兵值的对象,您只需使用nullopt.
第三,如果optional开始支持引用(许多 3rd 方库都支持),optional<T const&>对于默认的、不可修改的参数来说,这是一个极好的选择。确实没有等效于默认参数。