4

如何std::optional适合我的代码?

我的代码包含许多看起来大致如下的函数:

bool GetName(_Out_ string& strRes)
{
   // try to get/calculate the value
   // value may also be empty
   // return true on success, false otherwise.
}

现在我已经找到了std::optional,我只需编写代码:

std::optional<std::string> GetName()

为什么或何时不使用std::optional

  1. 我知道使用std::optional会带来性能价格,为了争论,让我们假设我愿意支付它。
  2. 我也明白这样的编码: std::optional<int> iVal = 9;是没有意义的。

我是不是太着迷了std::optional

我发现std::optional这很自然,以至于我得出一个结论,默认情况下,我的许多本机类型 , bool,int应该string被声明为std::optional.

因此,而不是遵循以下前提:

std::optional 在绝对需要时使用

我倾向于:

std::optional始终使用,除非您确定现在或将来不需要它。

问题 1

以下规则是否有效:

在以下情况下使用std::optional

  1. 在运行时计算一个值
  2. 计算可能失败
  3. 你想知道计算是否失败

问题2

如果使用std::optional变得丰富,我会冒代码可读性问题的风险吗?


更新

答案和评论使讨论偏离了我最初关心的问题,即试图定义何时使用的经验法则std::optional

我将解决我得到的一些常见反馈:

您正在编写 C 风格的错误检查函数。

计算失败时使用异常。

提供一个不太好的例子来说明我的动机可能是我的错。GetName()只是我的常见用例的一个味道。我不想关注错误是如何反映的(如果有的话),我想关注的是名字是可选的好候选人吗?

我没有说如果GetName返回 false 意味着我的流程中有某种错误。当GetName返回 false 时,我可以生成一个随机名称或忽略当前迭代。无论调用者如何对返回值做出反应,nameoptional某种意义上它都是may not be present.

我可以提供一个更好的例子:

void GetValuesFromDB(vector<string> vecKeys, map<string,string> mapKeyValue);

在这个函数中,我“被迫”提供两个容器,因为我想区分:

  1. 密钥在数据库中找到,它是空的。这把钥匙在mapKeyValue
  2. 在数据库中找不到密钥。. 这个钥匙不会在mapKeyValue

optional我可以去:

void GetValuesFromDB(map<string,std::optional<string> > mapKeyValue);

我相信我们optional太从字面上理解这个词了。

如果您想很好地表示可空类型,我真的支持可以使用的概念。std::optional 而不是使用唯一值(如 -1、nullptr、NO_VALUE 等)

c++ 标准委员会很容易决定将 , 命名为std::optional, std::nullable

4

4 回答 4

6

我是否在滥用 std::optional

也许。

的预期语义std::optional是表明某事是可选的。也就是说,如果该事物存在,那是可以的,而不是错误。

如果您的方法失败,它会返回一个空的可选项以指示失败。但是这个失败是否也是一个错误取决于调用代码所期望的语义,你没有显示或描述:如果可选结果为空,你会怎么做?

例如,

auto name = GetName();
if (!name) { diediedie(FATAL_ERROR); }

表明该名称不是真正可选的。如果您不能(如您所说)在此处使用异常,则该variant<string, error>方法似乎更清晰,并且还允许您的失败函数传达否则会出现异常的失败原因。

反过来

auto name = GetName();
if (name) {
  cout << "User is called " << *name << '\n';
} else {
  cout << "User is anonymous\n";
}

似乎是合理的,因为未能获取名称不是错误。


为什么……使用std::optional

当您不想传达某些东西是可选的时。

显然,您可以像其他任何事情一样滥用它,但它可能会造成混淆、误导、意外和其他不好的做法。

我是不是太着迷了……?

也许。如果您的错误处理污染了您的所有代码,您可能应该考虑提前失败,而不是绊倒永远不会成功的逻辑。

如果你真的有很多可能合法缺失的变量——就你的程序逻辑而言——那么你可能正确地使用了它。

问题 1

同样,这取决于在您的程序上下文中失败意味着什么。

问题2

是的,如果一切都是可选的,你的代码将是一个难以阅读的混乱,部分原因是视觉噪音,但主要是因为你永远无法确定哪些变量甚至存在于你的逻辑中的给定点。


如果您的数据库相对非结构化且没有约束,您的GetValuesFromDB编辑可能可以使用 optional 。您不能依赖任何特定的列被填充,一切都是可选的,您需要手动检查每个字段的存在。它们确实是可选的。

如果您的数据库是高度结构化的,则可能适合使用可选的主键。没有主键就不能有一行,因此将其设为可选似乎有点奇怪。不过,也许不值得为少数强制性列设置单独的界面。

我相信我们从字面上理解“可选”这个词。

我真的无法判断您是否阅读了我的答案-至少您的评论表明您不理解它。

假设我们将其称为可为空的。我仍然建议返回nullable<T>可能合法不存在的东西是可以的,但返回相同类型的东西表示错误是错误的。

必须决定什么构成代码错误。我们不能为您做这件事,尤其是在提供信息的情况下。那么你应该对所有事情都使用 optional 吗?也许。这就是我所说的。确定你自己代码的语义和前提条件,你就会知道哪些东西是可选的,哪些东西是强制性的,哪些东西的缺失应该作为错误处理,而不是常规的代码路径。

于 2019-06-20T14:00:12.823 回答
1

std::optional是一个类,代表“可选”值。如果你有一个案例,你可能会产生一个值,也可能不会,那么 optional 是一种方法。性能价格可以忽略不计(附加条件,如果值在这里实际上可能会减慢速度,但由于值是可选的,你总是需要在某个地方检查,如果值仍然存在),真正的价格是内存:

printf("%d\n", (int)sizeof(std::string));
printf("%d\n", (int)sizeof(std::optional<std::string>));

印刷:

32
40

在铿锵声 64 位。

所以如果你这样做 - 让我们说一个结构:

struct A {
    std::optional<std::string> a, b, c, d, e, f, g, h;
};

struct B {
    std::string a, b, c, d, e, f, g, h;
    unsigned char has_a : 1,
        has_b : 1,
        has_c : 1,
        has_d : 1,
        has_e : 1,
        has_f : 1,
        has_g : 1,
        has_h : 1;
};

然后你会得到sizeofs:

A - 320
B - 264

这实际上可能会受到伤害。但通常不会。

所以你在滥用可选吗?如果您使用 optional 来表示错误,那么可能稍微是的,具体取决于错误的上下文。但这总是取决于。如果更重要的是代码的清晰性,那个函数没有产生价值,而不是为什么它没有产生价值,那么当然,去吧。

于 2019-06-20T06:55:00.897 回答
0

我将尝试做一个简短的回答:

它的核心 astd::optional只是一个带有 bool 的值(ala. std::pair<T, bool>)。请记住,原始指针(在大多数情况下)也是可选的。

通常使用以下经验法则:

  1. 对错误/失败状态/异常情况/等使用异常。
  2. 仅在需要时使用可选!除非您是“第 2 方或第 3 方”图书馆作者,否则不要计划使用可选项来保证未来的发展。
  3. 如果您期望结果,请不要使用可选的。
  4. 当某些东西实际上是可选的时使用可选 - 例如。一个最终可能会找到 0 或 1 个结果的函数,返回一个可选项是有意义的。一个可以找到多个的函数自然应该倾向于返回一个结果容器。
  5. 关于字符串,有时更喜欢使用空字符串(主要是因为简单),而不是std::optional<std::string>有时反之亦然。
于 2020-02-28T14:09:27.237 回答
-4

我会说这是一种滥用。

我会重写std::string getName() const(依赖于现在需要的 RVO),如果发生了一些不愉快的事情(例如某种获取错误),然后抛出一个异常(从 派生的自定义类型std::exception)。空白名称是否错误取决于您的应用程序。

如果不允许使用异常,则依赖 C 风格的错误传播形式

int getName(std::string*) const

因为 usingstd::optional使实际错误变得不透明(除非您将错误灌输struct到当前线程上)。

于 2019-06-20T07:31:28.753 回答