【问题标题】:shared_ptr and unique_ptr conversionshared_ptr 和 unique_ptr 转换
【发布时间】:2016-09-09 12:47:38
【问题描述】:

我不确定要使用哪种类型的智能指针,因为我不确定我的班级的所有用例。我可以只使用共享指针,但是当我不一定需要共享所有权时,我不喜欢在我的代码中到处传递共享指针的想法。 Herb Sutter 的This article 说,如果有疑问,请使用 unique_ptr 并在必要时转换为 shared_ptr。这是我想做的,但我不清楚应该如何做,请考虑以下示例:

class Example
{
    public:
        Example(): _ptr(std::make_unique<Node>()) {}

        std::unique_ptr<Node>& getPtr() 
        {
            return _ptr;
        }

    private:
        // I am unsure if I will eventually need shared ownership or not
        std::unique_ptr<Node> _ptr;
};

Example* example = new Example();


// Some function somewhere
void f()
{
    // I've decided I need shared ownership, converting
    std::shared_ptr<Node> ptr(std::move(example->getPtr()));

    // Oops, example is no longer valid...
}

如果有人对如何处理此类情况有更好的了解,我会很高兴听到。

【问题讨论】:

  • unique_ptr 和 shared_ptr 是为了拥有所涉及的内存,而不是简单地使用它。您不会经常传递 shared_ptr ——因为与普通指针/引用相比,它们的制作成本相对较高。
  • Example总是是否需要维护其节点的所有权,有时其他对象也需要该节点的所有权?
  • 从上面的示例中,您的指针似乎应该是shared_ptr,因为您希望(可能)共享所有权。工厂可能会发生疑问。
  • 我不确定您的问题的意图。你是问如何让example参与其转换后的unique_ptr的分享?
  • getPtr 是一个坏接口。你不应该像那样传递unique_ptr。如果有人想访问指针,他们应该得到一个Node*

标签: c++


【解决方案1】:

我认为您在问一种优化问题。您希望Example 使用unique_ptr,因为它具有更简单和更有效的语义(解释您引用的文章)。但是,当需要时,您希望允许将指针转换为shared_ptr

Example 应该简单地为此提供一个接口,并且当其用户调用该接口时,它本身需要从unique_ptr 转换为shared_ptr。您可以使用状态模式来捕获实例是处于unique_ptr 模式还是shared_ptr 模式。

class Example
{
    struct StateUnique;
    struct StateShared;
    struct State {
        State (std::unique_ptr<State> &s) : _state(s) {}
        virtual ~State () = default;
        virtual Node & getPtr () = 0;
        virtual std::shared_ptr<Node> & getShared() = 0;
        std::unique_ptr<State> &_state;
    };
    struct StateUnique : State {
        StateUnique (std::unique_ptr<State> &s)
            : State(s), _ptr(std::make_unique<Node>()) {}
        Node & getPtr () { return *_ptr.get(); }
        std::shared_ptr<Node> & getShared() {
            _state = std::make_unique<StateShared>(*this);
            return _state->getShared();
        }
        std::unique_ptr<Node> _ptr;
    };
    struct StateShared : State {
        StateShared (StateUnique &u)
            : State(u._state), _ptr(std::move(u._ptr)) {}
        Node & getPtr () { return *_ptr.get(); }
        std::shared_ptr<Node> & getShared() { return _ptr; }
        std::shared_ptr<Node> _ptr;
    };
public:
    Example(): _state(std::make_unique<StateUnique>(_state)) {}
    Node & getNode() { return _state->getPtr(); }
    std::shared_ptr<Node> & getShared() { return _state->getShared(); }
private:
    std::unique_ptr<State> _state;
};

如果状态机看起来很吓人(它应该是这样,因为它被过度设计了),那么你可以只在Example 中维护两个指针,以及需要测试它需要使用哪一个的方法。

class Example
{
public:
    Example(): _u_node(std::make_unique<Node>()) {}
    Node & getNode() { return _u_node ? *_u_node.get() : *_s_node.get(); }
    std::shared_ptr<Node> & getShared() {
        if (_u_node) _s_node = std::move(_u_node);
        return _s_node;
    }
private:
    std::unique_ptr<Node> _u_node;
    std::shared_ptr<Node> _s_node;
};

【讨论】:

  • 谢谢,这基本上是我想做的,但是我想在我的代码中大量使用这种方法(不仅仅是这一类),这种模式太复杂了。然而,对于那些想要这样做的人来说,这是正确的答案。
  • @navark:我在答案的底部添加了一个简化版本。
  • 为什么你会在课堂上同时拥有 unique_ptr 和 shared_ptr ? shared_ptr 相对于普通指针的唯一增量成本是在创建时,因此没有理由不只是预先创建它——不要通过来回转换使代码复杂化。如果它曾经是一个 shared_ptr,只需创建一个 shared_ptr。而且从理解所有权的角度来看,你永远不能把它当作一个 unique_ptr,你总是不得不假设它从静态分析的角度使用 shared_ptr 版本。您实际上只是添加了复杂的失败案例,几乎没有任何好处
  • @xaxxon 答案基于问题中引用的 Herb Sutter 的文章。文章认为unique_ptrshared_ptr 更有效。因此,如果大多数时候底层类型只需要唯一性,那么保持这种方式会更好。
  • 效率更高,是的。在处理非大量指针创建时效率显着提高?不太可能。足以将这种复杂程度添加到您的应用程序中吗?除非您有个人资料数据来支持它,否则几乎可以肯定不会。请记住,取消引用唯一和共享的 ptr 与哑指针相同。
【解决方案2】:

如果你有一个类,比如UniqueResourceHolder,它可能实现如下:

class UniqueResourceHolder{
public:
  std::unique_ptr<Widget> ptr;
};

稍后,如果您想从UniqueResourceHolder 获取所述资源并将其放入SharedResourceHolder 中,如下所示:

class SharedResourceHolder{
public:
  std::shared_ptr<Widget> ptr;
};

执行此操作的代码可能如下所示:

{
  UniqueResourceHolder urh;
  SharedResourceHolder srh;
  //initialization of urh.ptr

  srh.ptr = std::shared_ptr<Widget>(urh.release());
}

UniqueResourceHolder 将不再拥有曾经的Widget,因此任何使用urh 获取小部件的尝试都将无效(除非您用新的小部件重新填充它),但现在srh将拥有该小部件并愿意分享。无论如何,这就是我理解您提供的链接中问题的答案的方式。我自己的两分钱是用std::shared_ptr替换出现的std::unique_ptr也是一件小事;您的程序遵循的任何业务逻辑以确保资源的唯一性仍然有效,但是从您利用std::shared_ptr 的共享性质的业务逻辑出发需要一些时间并专注于重新设计独特的思维方式。

【讨论】:

  • 你可以简单地做srh.ptr = std::move(urh.ptr);
  • 这不是我想要做的,但我认为你是正确的,在需要时用 shared_ptr 替换 unique_ptr 是微不足道的,可能是最好的解决方案
【解决方案3】:

共享指针用于共享资源。如果你想共享资源,我会从一开始就声明所有共享的东西,而不是经过特殊的转换。

如果您想使用唯一指针,您是在说“必须”只存在一个指针用于您分配的资源。这不会限制您使用这些对象的方式。为什么不使用唯一指针并通过类实例和函数“移动”您的对象...如果您必须转换为指针,为什么不将其作为唯一指针移动...

关于性能:据说,与原始指针相比,共享指针大约慢了 6 倍。唯一指针的速度大约是原来的两倍。当然,我说的是访问。如果您确实有代码的性能关键部分,您仍然可以获得这些类的原始指针并进行操作。它甚至不必是指针,仍然有 C++ 引用。

我认为你应该决定你的对象是“共享”还是“移动”并继续使用范式,而不是一直转换和更改。

std::unique_ptr<Node>& getPtr() 
{
   return _ptr;
}

不,不。如果您从不同的地方调用它,您将传递多个引用。请改用“移动”运算符(例如使用 type&amp; operator=(type&amp;&amp; other) 之类的声明)并传递您的唯一指针。

【讨论】:

  • 你确定这就是应该使用唯一指针的方式吗,因为我的理解是“唯一性”在于它的所有权,但要引用它,你可以传递原始指针(或引用的 unique_ptr)随心所欲地添加到 unique_ptr
  • 不确定,我们说的不一样吗? “动”不叫“变”,但终究还是一个指针。我想象它就像一个在程序中移动的对象/指针,也许是我自己的幻想。如果您传递原始指针,您实际上是在“共享”而没有使用这些类的优势。我认为获取原始指针更多是一种选择,因为无论如何您还有其他方法可以获取此指针。毕竟你使用的是模板类。
  • unique_ptr 并不比原始指针慢,只要您至少启用了一点优化。
  • 取决于可以优化多少:stackoverflow.com/questions/22295665/…
  • @Gerstrong && 有很多东西,但 从不 是“移动运算符”
猜你喜欢
  • 2016-06-05
  • 2021-08-28
  • 2016-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-16
  • 2018-12-15
  • 1970-01-01
相关资源
最近更新 更多