我应该使用 std::shared 指针来传递指针吗?

Should I use std::shared pointer to pass a pointer?

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

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

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

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

通过引用:

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 被销毁后它不会被使用变得更加困难。

如果托管对象的所有权未被转移(并且因为它是一个 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<>

所以而不是这样:

std::vector<Thing*> my_thing_ptrs;

这样做:

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

由于 std::reference_wrapper<T> 定义了运算符 T&,您可以在任何需要 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>;
}

问题更新后,我更喜欢:

  1. std::reference_wrapper<> 正如 Richard Hodges 所建议的,只要您不需要 NULL 值的选项。

    请注意,尽管您的用例似乎确实需要默认构造函数,所以除非您有一些 public 静态实例用作默认构造函数,否则这是不可行的。

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

  3. 如果您需要优雅地处理悬挂引用,请使用 std::weak_ptr 并将所有权更改为 shared_ptr(即只有 one 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. 传递原始指针和文档,表明所有权不应转移。这是最弱和最容易出错的。别这样。

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

void func(const Foo& foo);

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

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

Herb Sutter 在 GotW #91 - Smart Pointer Parameters 中已经涵盖了这一领域。 它也涉及性能方面,而其他答案虽然很棒,但侧重于内存管理。

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

对我来说,智能指针参数传递问题非常严重,可以在解释什么是智能指针后立即在任何 book/article/post 中突出提及。

现在想知道 lint-like 程序是否应该将 const 引用传递标记为样式元素。