混合boost :: optional和std :: unique_ptr

时间:2017-09-19 11:45:53

标签: c++ c++11 optional unique-ptr

我承认:我爱上了可选的概念。自从我发现它以来,我的代码质量已经提高了很多。明确变量是否有效比明确错误代码和带内信令要好得多。它还让我不必担心必须阅读文档中的合同,或担心它是否是最新的:代码本身就是合同。

那就是说,有时我需要处理std::unique_ptr。此类型的对象可能为null或不为null;在代码中的给定点,不可能知道std::unique_ptr是否应该具有值;从代码中知道合同是不可能的。

我想以某种方式混合optional(可能与boost::optional)和std::unique_ptr,以便我有动态分配的对象,范围破坏和正确的复制/移动明确声明它可能没有值的行为。这样,我可以使用这种新类型来明确表示需要检查值,并避免对普通std::unique_ptr进行不必要的检查。

在C ++ 11标准boost或者足够受欢迎的库中是否有这样的工具?我可以接受为此定义我自己的类,但这将是最不受欢迎的方法(由于缺乏彻底的测试)。

4 个答案:

答案 0 :(得分:5)

所以回顾一下你的问题,你想要:

  1. 通过堆栈上的值/分配的非可选类型:您很高兴直接使用对象类型。
  2. 按值/在堆栈上分配的可选类型:您很高兴使用boost::optional(或者您可以使用C ++中的std::optional)。
  3. 在堆上分配并拥有指向对象的非可选类型。
  4. 在堆上分配并拥有指向对象的可选类型。
  5. 您不能表达1和2之间的差异,但3和4通常使用相同的类型(std::unique_ptr)。您建议使用std::unique_ptr表示3,不允许使用nullptr,以及使用4表示其他内容,但想知道可以使用的内容。 (在评论中,您还接受使用std::unique_ptrnullptr 4的可能性,如果可以找到3的其他内容。)

    您的问题的直译答案:您可以简单地使用boost::optional<std::unique_ptr<T>>代表4(根据建议使用裸unique_ptr代表3)。

    您问题的替代字面答案:正如@StoryTeller所说,您可以定义自己的智能指针类型,类似于unique_ptr但不允许nullptr,并将其用于3.更快(但非常脏)的替代方法是强制函数返回pair unique_ptr和对同一对象的引用。然后只能通过引用访问结果,但只有unique_ptr仍然存在时才会这样做:

    template<class T>
    using RefAndPtr = std::pair<T&, std::unique_ptr<T>>;
    
    RefAndPtr<Foo> getFoo()
    {
        std::unique_ptr<Foo> result = std::make_unique<Foo>();
        return RefAndPtr<Foo>(*result, std::move(result));
    }
    

    我的实际建议:只需将其吸取并使用std::unique_ptr同时使用3和4.澄清你在类型系统中的意图是一件好事,但太好了可能不好使用上述任一选项只会让任何读取代码的人感到困惑。即使你阻止人们错误地传递nullptr,有什么阻止他们将指针传递给错误的对象,或者已经释放的内存等等?在某些时候,你必须指定类型系统之外的东西。

答案 1 :(得分:0)

std::unique_ptr可以为空。无论何时移动,或默认构造时,它都变为null。

std::unique_ptr是您可以为空的堆分配对象。

可以写出不可为空的value_ptr。请注意,移动需要额外费用:

template<class T>
class value_ptr {
  struct ctor_key_token{ explicit ctor_key_token(int){} };
public:
  template<class A0, class...Args, class dA0 = std::decay_t<A0>,
    std::enable_if_t<!std::is_same<dA0, ctor_key_token>{} && !std::is_same<dA0, value_ptr>{}, int> = 0
  >
  value_ptr( A0&& a0, Args&&... args):
    value_ptr( ctor_key_token(0), std::forward<A0>(a0), std::forward<Args>(args)... )
  {}
  value_ptr(): value_ptr( ctor_key_token(0) ) {}

  template<class X, class...Args>
  value_ptr( std::initializer_list<X> il, Args&&... args ):
    value_ptr( ctor_key_token(0), il, std::forward<Args>(args)... )
  {}

  value_ptr( value_ptr const& o ):
    value_ptr( ctor_key_token(0), *o.state )
  {}
  value_ptr( value_ptr&& o ):
    value_ptr( ctor_key_token(0), std::move(*o.state) )
  {}

  value_ptr& operator=(value_ptr const& o) {
    *state = *o.state;
    return *this;
  }
  value_ptr& operator=(value_ptr && o) {
    *state = std::move(*o.state);
    return *this;
  }

  T* get() const { return state.get(); }
  T* operator->() const { return get(); }
  T& operator*() const { return *state; }

  template<class U,
    std::enable_if_t<std::is_convertible<T const&, U>{}, int> =0
  >
  operator value_ptr<U>() const& {
    return {*state};
  }
  template<class U,
    std::enable_if_t<std::is_convertible<T&&, U>{}, int> =0
  >
  operator value_ptr<U>() && {
    return {std::move(*state)};
  }
private:
  template<class...Args>
  value_ptr( ctor_key_token, Args&&... args):
    state( std::make_unique<T>(std::forward<Args>(args)...) )
  {}

  std::unique_ptr<T> state;
};

这是一个不可为空的堆分配值语义对象的粗略草图。

请注意,当您从中移动时,它不会释放旧内存。它在堆上没有T的唯一时间是在构造期间(只能通过抛出中止)和破坏期间(因为state被破坏)。

Fancier版本可以使用custrom驱逐程序,克隆程序和移动程序,允许存储多态值语义类型或不可复制类型。

使用几乎从不为null或很少为null的类型为never-null会导致错误。所以不要这样做。

Live example

答案 2 :(得分:0)

在C ++的类型系统中,编写一个不可为空的unique_ptr是不可能的。可以为空的unique_ptr只是约定。这是许多评论中遗漏的重点。移动构造函数会是什么样的?这一点在之前已经涉及:https://youtu.be/zgOF4NrQllo?t=38m45s。由于无法使用不可为空的unique_ptr类型,因此您也可以在任何一种情况下使用unique_ptr指针。

如果需要,可以创建一个类似unique_ptr的指针类型,但没有公共默认构造函数。每次移动时它仍会进入null状态。这并没有给你很多保证,但它给你一点点,它作为文档。我不认为这种类型足以证明其存在的合理性。

答案 3 :(得分:0)

我建议在代码库中简单地约定std::unique_ptr始终指向某个内容,除非它刚被取消引用并且即将超出范围(并且仅在此范围内)。可能包含null)