C++ shared_ptr 释放所有权

8
我们都知道在C++中可以很容易地将unique_ptr转换为shared_ptr。但如果我已经进行了这样的转换呢:
unique_ptr<X> u=make_unique<X>();    // X is some class
shared_ptr<X> s=move(u);      // this works of course

现在,我希望将指针在s中的所有权转移回u。遗憾的是,在shared_ptr中没有像unique_ptr中那样的release()函数,否则我可以这样做:

u.reset(s.release());

此外,这也无法正常工作:-
u.reset(s.get());

有人能否建议我如何将 shared_ptr 转换为 unique_ptr 或至少释放由 shared_ptr 持有的指针?


1
当你已经有一个shared_ptr时,unique_ptr没有任何优势,所以为什么要这样做呢? - Baum mit Augen
1
一旦指针被赋给shared_ptr,它可以进一步与更多的shared_ptr实例共享。释放其中一个是没有帮助的。我怀疑这就是为什么没有机制来做到这一点的原因。 - Igor Tandetnik
1
@AndreyNasonov 嗯,大概是因为那个方法接受 unique_ptr,因为它拥有该对象的所有权。但是如果你手头只有 shared_ptr,那么这个所有权不属于你;其他人也可能共享它,并且你无法从他们那里夺走它。 - Igor Tandetnik
2
@IgorTandetnik,我同意在设计良好的系统中不应该出现这种情况。但是如果shared_ptr此时只有一个所有者呢? - Andrey Nasonov
1
@Anwesha,当您对shared_ptr应用静态和动态转换时,您将创建一个新的shared_ptr对象并增加引用计数。但是,您可以直接对unique_ptr.get()应用转换。 - Andrey Nasonov
显示剩余6条评论
3个回答

18

不要这样做!

标准库并不真正支持这样的转换,从 std::unique_ptrstd::shared_ptr 的转换总是安全的(因为两者的语义不同)- 但相反的转换是危险的,因为可能会有更多的资源所有者比你想要移动的特定 std::shared_ptr 更多。


不,真的 - 不要这样做!

我应该再告诉你一次,但我会假设你已经长大成人,可以对自己的决定负责。


嘿,拜托了..肯定有什么方法吧?

如果你想要编写一个解决方案,而不会导致未定义行为,除非std::unique_ptr超出作用域,你仍然有std::shared_ptrs直接或间接地使用该资源..你可能会得到类似以下的东西:

#include <memory>

namespace hack {
  struct conditional_deleter {
    void  disable () { _do_delete = false; }

    template<class T>
    void operator()(T* p) {
      if (_do_delete)
        delete p;
    }   

    bool _do_delete = true;
  };  
}

int main () {
  std::unique_ptr<int> up (new int (123));
  std::shared_ptr<int> sp (up.release (), hack::conditional_deleter {});
  std::shared_ptr<int> sb (sp);

  std::get_deleter<hack::conditional_deleter> (sp)->disable (); 
  std::unique_ptr<int> ub (sp.get ()); // see the name of this variable?
}                                      // can I get a ticket to UB-land, please?

警告

上述做法远非推荐的最佳实践,如果你发现自己需要这样做,应该将工作站点火——或许还要整个房子——并重新设计程序。


3
唯一安全的方法是订阅静态删除通知器。通知器可以由共享指针的自定义删除器发出信号,然后通过订阅回调传递唯一指针... 这是徒劳无功的大量工作。 - Richard Hodges
@RichardHodges 当然,实际上,唯一安全的方法是实现一个结构,它可以在内部执行std::shared_ptr的操作,但也可以处理std::unique_ptr的操作(使用自定义删除器)- 或通过代理进行引用计数(即重新发明轮子)。你的评论得到了**+1**的支持。 - Filip Roséen - refp

5
众所周知,你无法将 shared_ptr 转换为 unique_ptr ,因为可能会有多个 shared_ptr 拥有该对象。 因此,shared_ptr没有release()函数。 如果有一个shared_ptr对象上有release(),并且多个这样的对象共享资源,则其他shared_ptr指针就会产生歧义,因为它们不确定是否拥有该资源。
然而,由于您实际的问题涉及到unique_ptr静态和动态转换(正如您在评论中提到的),因此我想补充说明一下,您不必实际使用shared_ptr来进行unique_ptr的类型转换,以下是如何简单地完成:
void dynamic_unique_cast (unique_ptr<Parent> &pa)
{
    unique_ptr<Child> pb;    
    Parent *p=pa.release();   // release ownership
    try
    {
       pb.reset(dynamic_cast<Child*>(p));  // perform casting
       if (pb==nullptr)
       throw runtime_error {"nullptr exception"};
       cout<<"dynamic_cast successful\n\n";
       pa.reset(pb.release());  // if casting is successful then `pb` returns ownership back to `pa`
    }
    catch (exception &e)
    {
       cout<<"dynamic_cast unsuccessful: "<<e.what()<<"\n\n";
       pa.reset(p);  // if casting fails then `p` returns ownership to `pa`
    }
}
void static_unique_cast (unique_ptr<Parent> &pa)
{
    unique_ptr<Child> pb;
    Parent *p=pa.release();
    try
    {
       pb.reset(static_cast<Child*>(p));
       if (pb==nullptr)
       throw runtime_error {"nullptr exception"};
       show(pb);
       cout<<"static_cast successful\n\n";
       pa.reset(pb.release());
    }
    catch (exception &e)
    {
       cout<<"static_cast unsuccessful: "<<e.what()<<"\n\n";
       pa.reset(p);
    }
}

上述代码运行良好,一定会解决你的问题。如果有任何疑问,请随时发表评论。


如果动态转换失败,此代码会泄漏资源,并且不考虑 unique pointer 中的自定义删除器。 - Richard Hodges
这并没有回答问题,如果这是OP正在寻找的内容,应该创建一个新问题,并在那里发布此回答。(标记为不是答案 - Filip Roséen - refp
2
@FilipRoséen-refp:确实,这并没有回答原问题。另一方面,从评论中可以看出,OP只是遇到了一个XY问题。这篇文章确实回答了OP试图解决的真正问题(X),但未提及它。我同意,如果不阅读最后一条评论(甚至默认情况下也没有展开),几乎无法理解这个答案与问题的关系,但我赞赏这个答案因为它解决了X而不是Y。 - Andy Prowl
1
@FilipRoséen-refp 已经回答过的问题应该被标记为重复问题,而不是再次回答。Ankit 仔细了解了 OP 的问题,我只是说我欣赏他的回答。 - Andy Prowl
1
@AnkitAcharya 你说得对。catch子句可以解决这个问题,我的错。现在只剩下自定义删除器需要解决了。请参见下面的内容。 - Richard Hodges
显示剩余5条评论

2

好的,经过查看评论后我现在意识到你需要一个适用于unique_ptrdynamic_pointer_cast版本。

记住,unique_ptrs是独一无二的,所以这里是答案:

请注意,这个答案乍一看可能显得不必要地复杂,但我认为重要的是要记住unique_ptr可以有自定义删除器。如果我们将unique_ptr动态转换成新的unique_ptr,那么删除器也必须跟随转移,但是如果没有插入一个翻译层,传递给新unique_ptr删除器的指针类型会错误。

这段代码不仅动态地将所有权转移到了一个新的指针类型,还连接了正确的反向指针转换,使得当移动到的unique_ptr最终超出作用域时,该对象可以通过正确的接口由正确的删除器进行删除。

#include <iostream>
#include <memory>

template<class Dest, class Source, class Deleter>
auto
make_proxy_deleter(std::unique_ptr<Source, Deleter>& source)
{
    return [original = source.get_deleter()](Dest* p) {
        original(dynamic_cast<Source*>(p));
    };
}

template<class Dest, class T, class Deleter>
auto
dynamic_cast_unique(std::unique_ptr<T, Deleter>&& source)
{
    auto proxy_deleter = make_proxy_deleter<Dest>(source);
    auto p = dynamic_cast<Dest*>(source.get());
    if (!p) {
        return std::unique_ptr<Dest, decltype(proxy_deleter)>(nullptr,
                                                              std::move(proxy_deleter));
        // or... throw std::invalid_argument("not convertible");
    }

    return std::unique_ptr<Dest, decltype(proxy_deleter)>(dynamic_cast<Dest*>(source.release()),
                                                          std::move(proxy_deleter));
}

struct A {
    virtual ~A() {};
};
struct B {
    virtual ~B() {};
};
struct C: A, B {};


using namespace std;

auto main() -> int
{
    auto pa = make_unique<C>();
    auto pb = dynamic_cast_unique<B>(std::move(pa));
    return 0;
}

不错!但是你似乎到处都在使用auto(甚至是int main())!!!11 - Anwesha
尾返回类型在许多地方都非常有用,以至于我只是出于习惯而使用它们。在模板代码中使用auto可以将输入量减少一个数量级。 - Richard Hodges

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接