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

时间:2015-02-23 10:52:06

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

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

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

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

5 个答案:

答案 0 :(得分:18)

如果未传输托管对象的所有权(并且因为它是unique_ptr,则无法共享所有权)那么将被调用函数中的逻辑与所有权概念分开更为正确。我们通过引用来调用。

这是一种令人费解的说法:

假设:

std::unique_ptr<Thing> thing_ptr;

改变事情:

// declaration
void doSomethingWith(Thing& thing); 

// called like this
doSomethingWith(*thing_ptr);

使用Thing而不修改它。

// 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&,因此您可以在任何期望reference_wrapped的表达式中使用T对象。

例如:

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 :(得分:6)

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

通过引用传递:

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被销毁后不会使用它。

答案 2 :(得分:4)

不要过分关注来电者的要求。该功能需要传递什么?如果它只在呼叫期间使用该对象,则使用引用。

void func(const Foo& foo);

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

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

答案 3 :(得分:3)

自问题更新以来,我更喜欢:

    理查德·霍奇斯建议
  1. std::reference_wrapper<>,只要你不要求选择NULL值。

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

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

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


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

    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. 传递一个原始指针和文档,不应转移。这是最弱的,最容易出错的。不要这样做。

答案 4 :(得分:0)

Herb Sutter已在GotW #91 - Smart Pointer Parameters中介绍了这一理由。 它也触及了性能方面,而另一方面则很好地关注内存管理。

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

对我而言,智能指针参数传递问题非常严重,可以在解释智能指针后立即在任何书籍/文章/帖子中突出显示。

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