【问题标题】:Represent an optional attribute as a C++ class member将可选属性表示为 C++ 类成员
【发布时间】:2019-03-03 04:06:12
【问题描述】:

我正在生成基于模式的 C++ 代码。存在实体,每个实体都包含属性,每个属性都具有相应的数据类型。现在的问题是其中一些属性是“可选的”,这意味着它们不必是类声明的一部分。但是,在 C++ 中,某些东西要么是类的成员,要么不是类的成员,没有诸如“可选数据成员”之类的概念。

实体将是类名,属性将是类成员。我不确定如何表示现有 C++ 概念中标记为“可选”的属性。

【问题讨论】:

  • std::optional 还是指针?
  • std::optional 到底是做什么的?它是 C++17 规范的一部分。我不认为我会使用 C++17 规范。
  • @nmd_07 C++17 是最新发布的规范,应该用于新代码。 optional 完全按照您的想法行事。
  • @BartekBanachewicz 我会更准确地说“语义正是您的想法”。从名字上看,幕后的东西根本不明显。它是堆分配还是包含可选对象?它是使用哨兵值还是需要额外的空间?它是否可能依赖于T(甚至是可定制的)?从语义的角度来看,这并不重要,但您在答案中展示了由此产生的权衡。

标签: c++ class schema code-generation


【解决方案1】:

标准答案是std::optional。这是表达模型中可能存在或不存在的值的语义准确方式。

当创建这样一个模型时,对于架构中的每个可选字段,您都会生成一个对应的std::optional-wrapped 条目。然后,在反序列化的时候,可以用std::none标记一个缺失的入口,在访问的时候,客户端代码必须检查实际值是否存在1

如果您的对象很大,并且您希望避免不必要地存储空白空间2,则下一个选择是std::unique_ptr。它具有指针语义的缺点,但在其他情况下仍然是这种情况的有效工具。

如果成员是非常动态的,例如可能的集合有几十个或几百个,但典型的利用率只会看到几个,键值存储(例如std::map)可能是一个更好的主意;那么您只能存储一种类型的值。使用std::variant 作为映射值类型可能会稍微缓解这种情况,这为您提供多种可能性之一,或者std::any,它可以有效地保存任何内容,同时失去类型安全性。

最终决定将取决于您的确切型号和使用特性。


1 如果客户端代码需要T,并且对字段的访问为它们提供optional<T>,则解包步骤将/应检查是否存在实际值。这是使用optional 的主要原因。

2sizeof(optional<S>) 最常见的是sizeof(S) + 1,但由于对齐规则可以变得更大。本文的Performance&Memory 部分很好地展示了它。

【讨论】:

  • @user463035818 - 使用std::vector 可能是一个不错的技巧,但它是错误的抽象。它最终迫使维护者跟踪哪些向量是真正的向量,哪些只是“可选”字段,同时使用两者。这不是一个好的代码库。唯一指针作为抽象更接近可选。
  • @StoryTeller 当然,我会将其包装在fake_std17_because_i_have_only_std11::optional 中,其界面与std::optional 类似;)
  • @user463035818 也可以包裹std::unique_ptr,也是C++11。
  • fwiw boost docs 甚至提到了一个应该考虑不使用可选成员的场景
  • @Eljay - 只表明你永远不能真正扼杀一个坏主意
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-11
  • 1970-01-01
相关资源
最近更新 更多