简体   繁体   English

混合 boost::optional 和 std::unique_ptr

[英]Mix boost::optional and std::unique_ptr

I admit it: I'm in love with the concept of optional.我承认:我爱上了可选的概念。 The quality of my code has improved so much ever since I discovered it.自从我发现它以来,我的代码质量有了很大的提高。 Making it explicit whether a variable may or may not be valid is so much better than plain error codes and in-band signaling.明确一个变量是否有效比普通错误代码和带内信令要好得多。 It also allows me to not worry about having to read the contract in the documentation, or worrying about whether it's up-to-date: the code itself is the contract.它还让我不必担心必须阅读文档中的合同,或者担心它是否是最新的:代码本身就是合同。

That said, sometimes I need to deal with std::unique_ptr .也就是说,有时我需要处理std::unique_ptr Objects of this type might be null or not;这种类型的对象可能为空或不为空; at a given point in the code is impossible to know whether the std::unique_ptr is supposed to have a value or not;在代码中的给定点,不可能知道std::unique_ptr是否应该有一个值; it's impossible to know the contract from the code.从代码中不可能知道合约。

I would like to somehow mix optional (maybe with boost::optional ) and std::unique_ptr , so that I have a dynamically allocated object with scope-destruction and proper copy/move behaviour that explicitly states that it may not have a value .我想以某种方式混合optional (也许与boost::optional )和std::unique_ptr ,以便我有一个动态分配的对象,它具有范围破坏和正确的复制/移动行为,明确指出它可能没有 value That way, I can use this new type to make it explicit that a check for value is necessary and avoid unnecessary checks for plain std::unique_ptr .这样,我可以使用这种新类型明确表示需要检查值并避免对普通std::unique_ptr进行不必要的检查。

Is there a tool for this inside the C++11 standard, boost or a popular enough library?在 C++11 标准、 boost或足够流行的库中是否有用于此的工具? I could accept defining my own class for this, but that would be the least preferred method (due to lack of thorough testing).我可以接受为此定义我自己的类,但这将是最不受欢迎的方法(由于缺乏彻底的测试)。

So to recap your question, you want:因此,回顾一下您的问题,您需要:

  1. A non-optional type that is allocated by value/on the stack: You are happy directly using the object type for this.按值/在堆栈上分配的非可选类型:您很高兴直接为此使用对象类型。
  2. An optional type that is allocated by value/on the stack: You are happy using boost::optional for this (or you can use std::optional from C++17).按值/在堆栈上分配的可选类型:您很高兴为此使用boost::optional (或者您可以使用 C++17 中的std::optional )。
  3. A non-optional type that is allocated on the heap and owns the pointed-to object.在堆上分配并拥有指向对象的非可选类型。
  4. An optional type that is allocated on the heap and owns the pointed-to object.在堆上分配并拥有指向对象的可选类型。

You are unhappy that you can express the difference between 1 and 2, but both 3 and 4 usually use the same type ( std::unique_ptr ).你很不高兴你可以表达 1 和 2 之间的差异,但 3 和 4 通常使用相同的类型( std::unique_ptr )。 You suggest using std::unique_ptr for 3, never allowing nullptr , and some other thing for 4, but want to know what you can use.您建议将std::unique_ptr用于 3,从不允许nullptr和其他一些用于 4 的东西,但想知道您可以使用什么。 (In the comments you also accept the possibility of using std::unique_ptr with nullptr for 4 if something else can be found for 3.) (在评论中,如果可以为 3 找到其他内容,您也接受将std::unique_ptrnullptr用于 4 的可能性。)

Literal answer to your question: you can simply use boost::optional<std::unique_ptr<T>> for 4 (while using a bare unique_ptr for 3 as you suggested).您的问题的字面答案:您可以简单地将boost::optional<std::unique_ptr<T>>用于 4(同时按照您的建议使用裸unique_ptr用于 3)。

Alternative literal answer to your question: As @StoryTeller said, you could define your own smart pointer type that is like unique_ptr but disallows nullptr , and use that for 3. A quicker (but very dirty) alternative is to force functions to return a pair of both a unique_ptr and a reference to that same object.您问题的替代文字答案:正如@StoryTeller 所说,您可以定义自己的智能指针类型,类似于unique_ptr但不允许nullptr ,并将其用于 3. 一个更快(但非常脏)的替代方法是强制函数返回一pair unique_ptr和对同一对象的引用。 Then only access the result through the reference, but only do so while the unique_ptr still exists:然后只通过引用访问结果,但只在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));
}

My actual suggestion: Just suck it up and use std::unique_ptr for both 3 and 4. Clarifying your intentions in the type system is a good thing, but too much of a good thing can be bad.我的实际建议是:把它std::unique_ptr ,对 3 和 4 都使用std::unique_ptr 。在类型系统中阐明你的意图是一件好事,但太多的好事可能是坏事。 Using either of the above options is just going to confuse the hell out of anyone that reads your code.使用上述任一选项只会让阅读您代码的任何人感到困惑。 And even if you stop people from incorrectly passing around nullptr , what's to stop them passing a pointer around to the wrong object, or already-freed memory, etc.?即使你阻止人们错误地传递nullptr ,有什么可以阻止他们传递指向错误对象或已经释放的内存等的指针? At some point you have to specify things outside of the type system.在某些时候,您必须指定类型系统之外的内容。

std::unique_ptr is nullable. std::unique_ptr为空。 It becomes null whenever moved-from, or when default constructed.每当移出或默认构造时,它都会变为空。

std::unique_ptr is your nullable heap allocated object. std::unique_ptr是您的可为空堆分配的对象。

A value_ptr can be written that is not nullable.可以写入不可为空的value_ptr Note that there are extra costs at move:请注意,搬家时会产生额外费用:

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;
};

that is a rough sketch of a non-nullable heap-allocated value semantics object.这是一个不可为空的堆分配值语义对象的粗略草图。

Note that when you move-from it, it doesn't free the old memory.请注意,当您移出它时,它不会释放旧内存。 The only time it doesn't own a T on the heap is during construction (which can only abort via a throw) and during destruction (as state is destroyed).它在堆上不拥有T的唯一时间是在构造期间(只能通过抛出中止)和销毁期间(因为state被销毁)。

Fancier versions can have custrom destroyers, cloners and movers, permitting polymorphic value semantic types or non-copyable types to be stored.更高级的版本可以有 custrom 破坏者、克隆者和移动者,允许存储多态值语义类型或不可复制的类型。

Using types that are almost-never-null or rarely-null as never-null leads to bugs.使用几乎从不为空或很少为空的类型作为从不为空会导致错误。 So don't do it.所以不要这样做。

Live example .活生生的例子

It's not possible, in C++'s type system, to write a non-nullable unique_ptr .在 C++ 的类型系统中,不可能编写不可为空的unique_ptr unique_ptr being nullable is not just convention. unique_ptr是可空只是约定。 This is the point that is badly being missed in many of the comments.这是许多评论中严重遗漏的一点。 What would the move constructor look like?移动构造函数会是什么样子? This point has been covered before: https://youtu.be/zgOF4NrQllo?t=38m45s .这一点之前已经介绍过: https : //youtu.be/zgOF4NrQllo?t=38m45s Since a non-nullable unique_ptr type is not possible, you may as well use unique_ptr pointer in either case.由于不可空的unique_ptr类型是不可能的,因此您最好在任何一种情况下都使用unique_ptr指针。

If you want, you could create a pointer type that is just like unique_ptr , but doesn't have a public default constructor.如果需要,您可以创建一个类似于unique_ptr的指针类型,但没有公共默认构造函数。 It would still enter the null state every time it was moved from.每次移出它时它仍然会进入空状态。 This doesn't give you much in the way of guarantees, but it gives you a little, and it serves as documentation.这并没有给你太多的保证,但它给了你一点,它作为文档。 I don't think this type is worth enough to justify its existence.我认为这种类型的价值不足以证明其存在的合理性。

我建议简单地将它作为代码库中的约定,即std::unique_ptr始终指向某个东西,除非它刚刚被取消引用并且即将超出范围(并且只有在这种情况下它才可能包含 null)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM