【问题标题】:Should I use std::shared pointer to pass a pointer?我应该使用 std::shared 指针来传递指针吗?
【发布时间】:2015-04-24 16:10:17
【问题描述】:

假设我有一个由std::unique_ptr 管理的对象。我的代码的其他部分需要访问这个对象。传递指针的正确解决方案是什么?我应该只通过std::unique_ptr::get 传递普通指针,还是应该使用并传递std::shared_ptr 而不是std::unique_ptr

我对@9​​87654326@ 有一些偏好,因为该指针的所有者实际上负责清理。如果我使用共享指针,即使对象实际上应该被销毁,也有可能由于共享指针而保持活动状态。

编辑:不幸的是,我忘了提到指针不仅是函数调用的参数,而且会存储在其他对象中以构建对象的网络结构。我不喜欢共享指针,因为这样就不再清楚,谁实际拥有该对象。

【问题讨论】:

  • 为什么不传递unique_ptr对象的引用呢?
  • 你不能让unique_ptrshared_ptr 都管理同一个对象。
  • 传递对被引用对象的引用是个好主意。
  • @Zaiborg 对unique_ptr 的引用仅在您想重新安装unique_ptr 时才有用。如果您只想观察指针,则可以使用原始指针或引用。对unique_ptr 的引用并不安全。见herbsutter.com/2013/06/05/…
  • 我看到你的更新 - 会在我的回答中回复

标签: c++ pointers c++11 smart-pointers


【解决方案1】:

如果托管对象的所有权没有被转移(并且因为它是unique_ptr,所有权不能被共享),那么将被调用函数中的逻辑与所有权的概念分开会更正确。我们通过引用调用来做到这一点。

这是一种复杂的说法:

给定:

std::unique_ptr<Thing> thing_ptr;

改变事物:

// declaration
void doSomethingWith(Thing& thing); 

// called like this
doSomethingWith(*thing_ptr);

在不修改的情况下使用事物。

// declaration
void doSomethingWith(const Thing& thing); 

// called like this
doSomethingWith(*thing_ptr);

您唯一想在函数签名中提及unique_ptr 的情况是您要转让所有权:

// declaration
void takeMyThing(std::unique_ptr<Thing> p);

// call site
takeMyThing(std::move(thing_ptr));

你永远不需要这样做:

void useMyThing(const std::unique_ptr<Thing>& p);

这是一个坏主意的原因是,如果混淆了useMyThing的逻辑和所有权的概念,从而缩小了重用的范围。

考虑:

useMyThing(const Thing& thing);

Thing x;
std::unique_ptr<Thing> thing_ptr = makeAThing();
useMyThing(x);
useMyThing(*thing_ptr);

更新:

注意问题的更新 - 存储(非拥有)对该对象的引用。

做到这一点的一种方法确实是存储一个指针。但是,指针可能会出现逻辑错误,因为它们可以合法地为空。指针的另一个问题是它们不能很好地与std:: algorithms 和容器一起使用——需要自定义比较函数等。

有一种std::-compliant 方法可以做到这一点 - std::reference_wrapper&lt;&gt;

所以不是这样:

std::vector<Thing*> my_thing_ptrs;

这样做:

std::vector<std::reference_wrapper<Thing>> my_thing_refs;

由于std::reference_wrapper&lt;T&gt; 定义了一个运算符T&amp;,您可以在任何期望T 的表达式中使用reference_wrapped 对象。

例如:

std::unique_ptr<Thing> t1 = make_thing();
std::unique_ptr<Thing> t2 = make_thing();
std::unique_ptr<Thing> t3 = make_thing();

std::vector<std::reference_wrapper<const Thing>> thing_cache;

store_thing(*t1);
store_thing(*t2);
store_thing(*t3);

int total = 0;
for(const auto& t : thing_cache) {
  total += value_of_thing(t);
}

地点:

void store_thing(const Thing& t) {
  thing_cache.push_back(std::cref(t));
}

int value_of_thing(const Thing& t) {
  return <some calculation on t>;
}

【讨论】:

  • 这是一个很好的答案,非常感谢!不幸的是,我忘记指出一个重要的事实,我现在将其编辑到我的帖子中。
  • @Michael 我认为即使代码的其他部分希望保留参考,这个建议仍然有效。只要您能确保在unique_ptr 被销毁后代码的其他部分不会使用它。如果您不能确保,那么 unique_ptr 是不合适的。
【解决方案2】:

通常,您只需将引用或普通指针传递给希望观察对象的代码的其他部分。

通过引用传递:

void func(const Foo& foo);

std::unique_ptr<Foo> ptr;

// allocate ptr...

if(ptr)
    func(*ptr);

通过原始指针传递:

void func(const Foo* foo);

std::unique_ptr<Foo> ptr;

// allocate ptr...

func(ptr.get());

选择取决于是否需要传递一个空指针。

您有责任按照设计确保观察者在unique_ptr 被销毁后不使用指针或引用。如果您不能保证,那么您必须使用shared_ptr 而不是unique_ptr。观察者可以持有weak_ptr 表示他们没有所有权。

编辑:即使观察者希望保留可以使用的指针或引用,但这确实使确保在 unique_ptr 被销毁后不会使用它变得更加困难。

【讨论】:

  • 我认为你在这里用“你的责任”来触及问题。对可能已故的对象持有 refs 是沟通不畅的标志(应通知数据结构一个元素已被删除)。事实上,weak_ptr 可能是一种廉价的出路。
【解决方案3】:

不要过分关注调用者的要求。函数需要传入什么?如果它只在调用期间使用该对象,则使用引用。

void func(const Foo& foo);

如果它需要保留对象的所有权超过其持续时间,则传入shared_ptr

void func(std::shared_ptr<Foo> foo);

【讨论】:

  • 考虑到问题以“假设我有一个由 std::unique_ptr 管理的对象”开头,最后一部分可能会更清楚。
【解决方案4】:

由于问题更新,我更喜欢:

  1. std::reference_wrapper&lt;&gt; 由 Richard Hodges 建议,只要您不需要 NULL 值选项。

    请注意,您的用例似乎确实需要一个默认构造函数,因此除非您有一些公共静态实例用作默认值,否则这是不可用的。

  2. 一些具有所需语义的自定义not_your_pointer&lt;T&gt; 类型:默认可构造、可复制和可分配、可从unique_ptr 转换和非拥有。不过,可能只有在您经常使用它时才值得,因为编写一个新的智能指针类需要一些思考。

  3. 如果您需要优雅地处理悬空引用,请使用 std::weak_ptr 并将所有权更改为 shared_ptr(也就是说,只有 一个 shared_ptr 存在:所有权和对象没有歧义寿命不受影响)


在问题更新之前,我更喜欢:

  1. 表示所有权不通过引用传递:

    void foo(T &obj); // no-one expects this to transfer ownership
    

    称为:

    foo(*ptr);
    

    不过,如果这很重要,你确实失去了传递 nullptr 的能力。

  2. 通过明确禁止表明所有权并未转让:

    void foo(std::unique_ptr<T> const &p) {
        // can't move or reset p to take ownership
        // still get non-const access to T
    }
    

    这很清楚,但确实向被调用者公开了您的内存管理策略。

  3. 传递一个原始指针并记录所有权不应转移。这是最弱和最容易出错的。不要这样做。

【讨论】:

  • const unique_ptr&amp; 提供了哪些原始指针不提供的功能?正如你所说,缺点是它暴露了你的内存管理策略?
  • const-ref-to-unique_ptr 使代码明确记录并保证合同。被调用者不太可能认为他们应该(或允许)拥有所有权,他们将不得不使用const_cast 或其他一些黑客来获得它。与原始参考不同,您仍然可以传递nullptr
  • @Useless:const unique_ptr&amp; 规范中的任何内容都不会阻止通过p.get() 获取底层原始指针,并使用它做任何想做的事情(存储在数据结构中是 OP 的意图)。传递原始指针或引用的默认假设是所有权不会被转移(考虑所有那些采用const char* 的函数),所以选项2 真的不会给你带来太多好处。要点是非常清楚地记录了指针在其存储位置不拥有。
  • 当然,但是如果你收到一个(const ref to)unique_ptr,那会非常有效地证明其他人已经拥有该对象。你可以规避它,但你必须变态才不会意识到你是故意违反合同的。相反,接收原始指针根本不会提供有关谁应该清理这件事的任何信息,并且会增加某人诚实地认为他们应该拥有所有权的风险。
  • 我在这里同意@Useless。任何功能都可以被规避,但这并不意味着我们应该停止使用它们。加 1。
【解决方案5】:

Herb Sutter 已经在GotW #91 - Smart Pointer Parameters 中报道了这片土地。 它也涉及性能方面,而其他答案虽然很好,但侧重于内存管理。

上面有人建议将智能指针作为 const 引用传递 - 在后来的编辑中改变了主意。我们有代码被滥用 - 它使签名“更智能” - 即更复杂但没有好处。最糟糕的是当初级开发人员接管时 - 他不会像@Michael那样质疑并从一个坏例子中“学习”如何做到这一点。它有效,但是...

对我来说,智能指针参数传递问题非常严重,以至于在解释什么是智能指针后立即在任何书籍/文章/帖子中显着提及。

现在想知道类似 lint 的程序是否应该将通过 const 引用传递作为样式元素进行标记。

【讨论】:

    猜你喜欢
    • 2012-07-02
    • 1970-01-01
    • 2021-12-04
    • 1970-01-01
    • 2020-03-16
    • 1970-01-01
    • 2013-01-13
    • 2014-12-09
    相关资源
    最近更新 更多