【问题标题】:Is using std::optional<int> as efficient as using int?使用 std::optional<int> 是否与使用 int 一样有效?
【发布时间】:2013-06-26 15:27:44
【问题描述】:

我有一个四/八叉树数据结构。我将一个单元格的子索引/ptrs 存储在一个数组中。数组中的每个位置都代表一个孩子相对于其父母的位置,例如二维:

// _____________
// |     |     |
// |  2  |  3  |
// |_____|_____|
// |     |     |
// |  0  |  1  |
// |_____|_____|
// for each cell, 4 children are always stored in row-major order
std::vector<std::array<Integer,4>> children;

我知道最大子节点数是 Integer 类型可以表示的值的子集。因此,我可以通过使用像-1 表示Integer = intstd::numeric_limits&lt;unsigned&gt;::max() 表示Integer = unsigned 的“魔法”值来识别单元格是否缺少孩子。这是std::optional&lt;Integer&gt; 无法假设的。

据我所知,魔法值的这种用法是std::optional 存在的理由之一。尽管如此,我还是担心std::vector&lt;std::optional&lt;int&gt;&gt; 在内部循环中的性能。

所以,

  • std::vector&lt;std::optional&lt;int&gt;&gt;的性能会比std::vector&lt;int&gt;差吗? (我已经在比较“不存在”的值了)。

  • 或者,可以优化std::optional 的实现以提供与原始int 相同的性能吗?以及如何?

在我的函数的返回类型和我的数据结构中的魔法值中混合std::optional 听起来是个非常糟糕的主意。我更喜欢保持一致并使用其中一个(至少在相同的上下文中)。虽然我可以重载执行与幻数比较的函数:

template<T> bool is_valid(const T& t) { 
  return /* comparison with magic value for t */; 
}

对于可选类型。

【问题讨论】:

  • 不确定两者之间的表现,这是您必须自己看到的。不过,问题是,使用比特信息对您来说可行吗?由于您正在存储索引,您可以在单个 int 中编码有关 4 个子索引的信息吗?由于您有 4 个孩子,您可以为每个孩子使用 4 个字节(或更多),这使得 16 个字节总共用于 indeces,而另外 16 个您可以使用它来编码它是否处于活动状态?创建一个类来处理这个问题,这样用户就不必担心内部问题
  • std::optional 的存在理由恰恰是您没有具有哨兵价值的情况。如果你有一个“明显”的哨兵,你为什么不使用它?
  • @user814628 每个索引都是一个无符号长长...我有很多单元格,无法执行您的建议。
  • @AndréCaron:这将使代码更清晰(更清晰地表达意图)。事实上,如果您可以为用户定义的类型声明一个标记值并告诉optional 使用它,那将是很好的。您甚至可以将 std::optional&lt;yourtype&gt; 设为好友,将该标记值的构造设为私有。
  • 如果您使用vector&lt;compact_optional&lt;int&gt;&gt;,您将获得类型安全和零性能开销。试试看:github.com/akrzemi1/compact_optional

标签: c++ performance optional c++14 boost-optional


【解决方案1】:

std::optional 将需要额外的存储空间并在缓存中放入更少的值(看来您已经知道原因了)。

我认为在数据结构内部存储与公共 API 公开的值不同的值并没有错,只要内部表示对用户完全隐藏即可。

此外,我建议您将幻数隔离为一对inline 转换函数。

编译器应该通过在您忘记时生成类型错误来帮助您记住始终如一地使用转换函数。您甚至可以在内部数据结构中为 int 使用瘦结构包装器,以确保不存在隐式转换(或定义用户定义的转换)。

class CompressedOptionalUInt
{
    static const unsigned SENTINEL_MISSING = std::numeric_limits<unsigned>::max();
    unsigned value;

public:
    CompressedOptionalUInt(std::optional<unsigned> val) : value(!val? SENTINEL_MISSING: *val) {}
    operator std::optional<unsigned>() const { ... }
};

然后使用std::array&lt;CompressedOptionalUInt&gt;

将其制作成模板,只需为每种类型定义哨兵,应该非常简单。

【讨论】:

  • 谢谢!使用瘦结构包装器听起来是个好主意!
【解决方案2】:

不,效率不高。从reference implementation 可以看出,它必须存储、更新和检查额外的值。

【讨论】:

    猜你喜欢
    • 2013-07-11
    • 2011-10-01
    • 2019-05-10
    • 2020-04-04
    • 1970-01-01
    • 1970-01-01
    • 2018-03-14
    • 2010-12-23
    • 1970-01-01
    相关资源
    最近更新 更多