关于将boost共享指针转换为标准共享指针的问题

Question on converting boost shared pointer to standard shared pointer

这更像是 the second answer posted here 上的后续问题。此答案的代码如下所示:

template<typename T>
void do_release(typename boost::shared_ptr<T> const&, T*)
{
}

template<typename T>
typename std::shared_ptr<T> to_std(typename boost::shared_ptr<T> const& p)
{
    return
        std::shared_ptr<T>(
                p.get(),
                boost::bind(&do_release<T>, p, _1));

}

我对上面代码的理解是,一个仿函数是从 do_release 与我们试图转换的提升 shared_ptr 绑定并作为自定义删除器传入的。

我目前的想法是(可能是错误的):新标准 shared_ptr 不包含 boost shared_ptr 持有的任何现有引用计数,但在“转换”之后只有一个引用计数。当标准 shared_ptr 析构函数被调用时,它会调用自定义删除器,然后触发 boost shared_ptr 上的析构函数?所以堆资源的引用计数和生命周期仍然由boostshared_ptr有效维持。我的意思是,如果调用自定义删除器时 boost shared_ptr > 0(在 de-ref 之后)的引用计数,它仍然不会破坏堆内存。

但是如果标准 shared_ptr 被复制了怎么办?这种转换仍然有效吗?我认为这是因为当复制标准 share_ptr 时,它会增加引用计数,但它是由标准 shared_ptr 维护的引用计数,因此堆资源上的整体引用计数是仍然正确。但是现在引用计数由标准 + boost shared_ptr?

维护

我说的对吗?

创建的共享指针有一个有状态的销毁函数对象(deleter)。特别是它有一个 boost shared ptr 的副本。

销毁操作不执行任何操作,它是清理 boost 共享指针的 deleter 销毁。

不过有个问题:

C++ 标准是否完全指定删除器本身的清理?例如。当只剩下弱引用时它可能会留下来? 标准 确实 保证了删除器的最小生命周期,留下了比这更长的生命周期的可能性。

销毁销毁对象指定发生在std::shared_ptr的构造函数或std::shared_ptr的析构函数中。在它被调用之后或在引用计数块被销毁时销毁它是合理的——在第二种情况下,我们最终让它持续到最后一个弱指针消失。

来自标准草案:

[Note: It is unspecified whether the pointer remains valid longer than that. This can happen if the implementation doesn’t destroy the deleter until all weak_ptr instances that share ownership with p have been destroyed. — end note]

推迟销毁删除器的可能性很明显。因此,在那种情况下,我们最终会得到 shared_ptrs,当我们希望它被调用时,它不会破坏它们的元素实例,具体取决于您 运行 它所使用的 C++ 实现的未指定细节。这是个坏主意。

更好的选择

我个人会使用基于 aliasing constructor 的类似技巧。它同样简单,没有额外的开销,而且语义实际上是正确的:

template<class T>
std::shared_ptr<T>
as_std_shared_ptr(boost::shared_ptr<T> bp)
{
  if (!bp) return nullptr;
  // a std shared pointer to boost shared ptr.  Yes.
  auto pq = std::make_shared<boost::shared_ptr<T>>(std::move(bp));
  // aliasing ctor.  Hide the double shared ptr.  Sneaky.
  return std::shared_ptr<T>(pq, pq.get()->get());
}

这比问题中的代码更不像黑客,因为生命周期 boost shared ptr 不再与删除器实例的(未指定)生命周期相关联。

But what if the standard shared_ptr gets copied? Would this conversion still work? I think it would because when the standard share_ptr is copied

确实如此。

你自己看就好了

CAUTION

The remainder only anecdotally demonstrates behaviour in limited scenarios, on particular implementations.

As points out, there is implementation-defined behaviour (unspecified behaviour) will lead to problems. Therefore, I recommend his superior alternative.

我将使用更现代的 lambda 拼写而不是 bind:

template <typename T> auto to_std(boost::shared_ptr<T> const& p) {
    return std::shared_ptr<T>(p.get(), [p](auto) {});
}

这里有一个很好的演示:

Live On Coliru

#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>
#include <memory>

template <typename T> auto to_std(boost::shared_ptr<T> const& p) {
    return std::shared_ptr<T>(p.get(), [p](auto) {});
}

struct X {
    ~X() { std::cout << "X::~X()\n"; }
};

int main() {
    auto b = boost::make_shared<X>();
    auto s = to_std(b);

    auto report = [&] {
        std::cout << "boost: " << b.use_count() << "\tstd: " << s.use_count() << "\n";
    };

    report();

    auto b2 = b;
    report();

    std::vector bb(42, b2);
    report();

    auto s2 = s;
    report();

    std::vector ss(17, s2);
    report();

    bb.clear();
    b2.reset();
    report();

    ss.clear();
    report();

    b.reset();
    s.reset();
    report();

    std::cout << "but s2 is still there: " << s2.use_count() << "\n";
    s2.reset();
}

版画

boost: 2    std: 1
boost: 3    std: 1
boost: 45   std: 1
boost: 45   std: 2
boost: 45   std: 19
boost: 2    std: 19
boost: 2    std: 2
boost: 0    std: 0
but s2 is still there: 1
X::~X()