【问题标题】:API design for retrieving object from container that may not exists用于从可能不存在的容器中检索对象的 API 设计
【发布时间】:2018-12-05 23:29:33
【问题描述】:

当相关对象可能不存在时,有哪些方法可以创建用于从索引自定义容器中检索对象的 API?

到目前为止,我想到了:

  1. 抛出异常

    T get(int index) const
    {
        if(not_exists(index)) throw std::out_of_range("Index is out of range");
        return get_base(index);
    }
    
  2. 构造 T 并返回它

    T get(int index) const
    {
        if(not_exists(index)) return T{};
        return get_base(index);
    }
    
  3. 返回 bool 并作为参考检索

    bool get(int index, T & obj) const
    {
        if(not_exists(index)) return false;
        obj = get_base(index); return true;
    }
    
  4. 如果找不到则使用默认参数

    T get(int index, T def_obj) const
    {
        if(not_exists(index)) return def_obj;
        return get_base(index);
    }
    
  5. 结合 4 + 2

    T get(int index, T def_obj = {}) const
    {
        if(not_exists(index)) return def_obj;
        return get_base(index);
    }
    
  6. 修改容器以添加此类对象(警告 - get 将不再是 const!)

    T get(int index, T def_obj = {})
    {
        if(not_exists(index)) set(index, def_obj);
        return get_base(index);
    }
    

每种解决方案的优缺点是什么?我错过了什么吗?

我特别担心在高并发环境中进行推理,我希望为客户端提供尽可能直观和安全的 API。

【问题讨论】:

  • boost::optional 应该是个不错的选择。
  • @seccpur:为什么不std::optional
  • 这两种解决方案都不是万能的。出于这个原因,许多 API 提供了多种选项。以标准库为例,这就是为什么你有container::findcontainer::atcontainer::operator[]
  • @seccpur 你能把这个作为答案发布吗? :)

标签: c++ containers api-design


【解决方案1】:

试试这个 sn-p(或 C++17 中的 std::optional)

boost::optional<T> get(int index, T& obj)
{
    if(not_exists(index))
        boost::none;
    else
        return get_base(index);
}

【讨论】:

    【解决方案2】:

    这里的基本问题是语义:#1 和#3 是唯一可以区分存在与不存在的问题。 #6 总是成功返回容器的一个元素;其他的总是成功返回一些。应用程序决定了您需要哪些。

    在这方面,#1 和#3 是完整的:任何一个都足以实现任何其他(考虑到添加元素以模拟#6 的其他一些方法)。如果可以避免来自其他线程的干扰,#4 和#5 同样强大:它们可以通过提供两个不同的默认值来检测缺席。或者,可以添加bool contains(int index) const; 以允许区分缺席(再次根据需要使用外部同步)。

    但是,这些模拟(#1/3 中的 #2/4/5 除外)涉及重复查找,可能性能不足。对于某些底层数据结构,可能还需要其他操作才能获得最佳性能:例如,将元素从一个索引移动到另一个索引而不重构它。

    同时,所有这些方法都有实际问题,至少在一般情况下是这样。

    1. 部分专家believe that logic_error is always a mistake;当然,在相当常见的情况下抛出异常是昂贵的。不过这里可以返回一个引用,非常有用。
    2. T 必须是值可构造的(类似于但不等同于默认-可构造)。
    3. T 必须是可分配的(并且客户端必须已经构造了一个,可能用于多次调用)。忽略标志可能会导致未定义的行为(所以标记它[[nodiscard]])。
    4. 每次调用必须构造两个T 对象。可以将默认设置为引用以允许引用返回(并支持检测缺失值的繁琐形式),但为了避免允许临时参数,则需要右值引用重载(或受约束的模板)。
    5. 与#4 一样,它构造了两个对象。在模板上下文中,对 #2 的改进在于仅当默认值使用时才需要值可构造性。
    6. 这当然可以有三种变体,如#2/4/5。如果它返回一个引用(如map::operator[])以允许对(可能的)新元素进行变异,那将更有用。

    如果T 的构建成本可能很高(即使来自{}),那么只有#1(map::at 使用)和optional suggestion 是有效的;方便地它们也很完整。也许最快的变体是返回const T*,使用空指针表示缺席。在它们之间进行选择是一个微调性能权衡的问题(除非您的商店有例外或一般指针)。对于便宜的T,如果语义足够,#5 很有吸引力;否则#3 可能是最好的(因为它与if(std::cin &gt;&gt; x) 相似)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-11-29
      • 1970-01-01
      • 1970-01-01
      • 2011-02-02
      • 2013-05-11
      • 1970-01-01
      • 2014-10-06
      相关资源
      最近更新 更多