将 shared_ptr 转换为常规指针

Convert a shared_ptr to regular a pointer

我们有一个函数,return将一个新分配的对象作为输出参数(指向指针)。

MyFunc(MyObject*& obj)
{
    obj = new MyObject();
}

这样称呼:

Object* obj;
MyFunc(obj);

该函数在内部做了很多工作,并使用 shared_ptr 进行内存管理。完成后,我们想要 return 的对象被 shared_ptr 引用。我正在努力研究如何 return 我们新分配的对象作为常规指针。

我们想继续在内部使用 shared_ptr 来降低风险,但是 return 和 shared_ptr 似乎没有意义,因为调用者完全拥有returned 值(被调用的函数或对象不再需要或保留对 returned 数据的引用)并且我们希望它们具有灵活性。

有没有人有任何建议允许我们在内部使用 shared_ptr 但有一个常规的指针界面?谢谢

最好的方法是在内部使用 unique_ptr 并在返回原始指针之前调用其 .release() 方法。

如果你坚持在内部使用 shared_ptr,一个选项是创建它指定一个自定义的 "noop" 删除器,当 shared_ptr 被销毁时它什么都不做(而不是在拥有的指针上调用 delete)。像往常一样从 shared_ptr 获取原始指针(.get() 方法)。 可以在 Boost 库 (null_deleter) 中找到此类删除器的示例。
但请注意,这样做有效 "disable" 拥有 shared_ptr 的用处...

这取决于谁 "owns" 指针,一旦它暴露给 'outside world.' 所有权基本上归结为:"who is responsible for freeing this memory, later?"

可以用一个简单的问题来回答:当MyFunc被调用时,调用者是否负责在完成时删除指针?

  1. 如果是,那么MyFunc需要'release'所有权,否则shared_ptr会自动删除指针,当它去超出范围。这实际上无法完成,使用shared_ptr。您需要改用 unique_ptr,并调用 unique_ptr::release()
  2. If not - if MyFunc 将简单地 使用 结果指针并在没有 delete 的情况下忘记它-ing 它 - 然后你可以简单地 return 使用 shared_ptr::get() 的 'raw' 指针。您必须小心,因为这意味着 shared_ptr 仍然存在于您的代码中的其他地方。

如果出于某种原因您 cannot/want 未使用 std::unique_ptrstd::auto_ptr(例如,如果您出于某种原因在创建过程中需要在内部拥有多个所有者,或者您的基础方法需要 std::shared_ptr 传递),您仍然可以使用自定义删除器使其与 std::shared_ptr 一起使用,如下所述:

原则上,在 return 之前完成后,将删除器切换为不实际删除实例(使删除器 "null"),然后 return 通过shared_ptr get()。即使在所有 shared_ptr 个对象被销毁后,内存也不会被删除(因为 nulled 删除器将跳过删除)。

评论里还有一个link不太明显,你可能会感兴趣: http://paste.ubuntu.com/23866812/ (不确定在所有情况下如果没有开关的共享所有权它是否真的有效,需要测试)


编辑

正如预期的那样,使用来自 pastebin 的 linked 简单可解除删除器你需要小心,因为删除器实际上被复制用于存储在 std::shared_ptr.

但您仍然可以通过使用 std::ref:

使其工作
MyFunc(MyObject*& obj)
{
    DisarmableDelete<MyObject> deleter;
    std::shared_ptr<MyObject> ptr(new MyObject(), std::ref(deleter));
    // do what is necessary to setup the object - protected by deleter
    // ...
    // disarm before return
    deleter._armed = false;
    obj = ptr.get();
    // deleter disarmed - object not freed
}

并且为了完整起见(并避免潜在的未来中断 link),这里是 http://paste.ubuntu.com/23866812/.

DisarmableDelete 的实现
template <typename T, typename Deleter = typename std::default_delete<T> >                                                                                                                              
    struct DisarmableDelete : private Deleter {                                                                                                                                                         
        void operator()(T* ptr) { if(_armed) Deleter::operator()(ptr); }                                                                                                                                
        bool _armed = true;                                                                                                                                                                             
    };        

我可以看到四个备选方案,如下所示。它们都很糟糕,并且没有将您的所有权切换到 std::unique_ptr<T> 并通过 obj = ptr.release(); 返回我只能提供一个 hack,其中参数在销毁时分配给指针,但您仍然需要捕获异常并测试指针是否被赋值

#include <iostream>
#include <memory>
#include <exception>

struct foo {
  void bar() const { std::cout << this << " foo::bar()\n"; }
  ~foo() { std::cout << this << " deleted\n"; }
};

void f1(foo*& obj) {
  obj = new foo;
  // do stuff... if an exception is thrown before we return we are
  // left with a memory leak
}

void f2(foo*& obj) {
  auto holder = std::make_shared<foo>();
  // do stuff.. if an exception is thrown the pointer will be
  // correclty deleted.

  obj = holder.get(); // awesome, I have a raw pointer!
} // oops, the destructor gets called because holder went out of
  // scope... my pointer points to a deleted object.

void f3(foo*& obj) {
  auto holder = std::make_unique<foo>();
  // do stuff.. if an exception is thrown the pointer will be
  // correclty deleted.

  obj = holder.release(); // awesome, I have a raw pointer!
} // no problem whem holder goes out of scope because it does not own the pointer

void f4(foo*& obj) {
  // a super-weird hack that assigns obj upon deletion
  std::shared_ptr<foo> holder(new foo, [&obj](foo*& p){  obj = p; });
  throw std::exception();
} // no problem whem holder goes out of scope because it does not own
  // the pointer... but if an execption is throw we need to delete obj

int main() {

  foo* p1;
  f1(p1);
  p1->bar();

  foo* p2;
  f2(p2);
  // p2->bar(); // error

  foo* p3;
  f3(p3);
  p3->bar();

  foo* p4;
  try {
    f4(p4);
  } catch(...) {
    std::cout << "caught an exception... test whether p4 was assigned  it\n";
  }
  p4->bar(); // I still need to delete this thing
}

shared_ptr的重点是表达共享所有权。

任何不共享对象所有权的东西——它们无权延长对象的生命周期,而对象生命周期是共享所有者请求对象生命周期的联合——不应使用 shared_ptr.

在这里,你有一个 shared_ptr,你将要 return 它。那时,您违反了 shared_ptr 的假设;任何保留了 shared_ptr 副本的人都希望它的内容可以在它请求的时候持续下去。

与此同时,调用代码认为它拥有您传递给它的 MyObject* 原始指针。

这是一个误用 shared_ptr 的例子。

说 "we have memory management issues, use shared_ptr" 并不能解决内存管理问题。正确使用shared_ptr需要细心和设计,设计必须是当问题对象的生命周期结束时由2个或多个数据and/or代码共享。

内部代码,如果它不拥有指针,应该使用类似 observer_ptr<T> 或原始 T* 的东西(首先要明确它不拥有该对象).

所有权应该是明确的,并且在 unique_ptr 中。如果需要,它然后可以调用 .release() 将所有权传递给原始指针;实际上,我会将您的签名更改为 unique_ptr&,或者 return unique_ptr.

当调用者想要使用其他对象生命周期管理系统时,调用者将调用 .release(),或者该对象生命周期管理系统应该消耗 unique_ptrs(因此非常清楚获得所有权东西)。


使用 non-hack 解决方案。

std::shared_ptr<std::unique_ptr<T>>。在这种情况下,您拥有一个独特所有权的共享所有权。

unique_ptr 的所有权可以被剥夺(通过 .release())。当它这样做时,所有仍然存在的 shared_ptrunique_ptr 也将被清除。

这将共享的唯一所有权放在首位和中心位置,而不是将 non-deleter 黑客攻击成 shared_ptr 并悬而未决的 shared_ptr 认为他们对数据拥有所有权但实际上并不.

如果您使用 std::shared_ptr 在内部管理已分配对象的生命周期,并且 return 使用原始指针进行访问并且不希望该指针影响引用计数,您可以return 通过调用 shared_ptr.get() 的原始指针。

如果您使用 Swig 等工具为其他语言生成包装器,return 智能指针可能会出现问题。