如何使用boost::atomic_store与shared_ptr<T>和shared_ptr<const T>?

3
在我的情况下,Tpcl::PointCloud<pcl::PointXYZ>>,但是问题应该适用于任何类型的 T。以下示例会产生错误:
using pc = pcl::PointCloud<pcl::PointXYZ> >;
boost::shared_ptr<pc> p(new pc);
boost::shared_ptr<const pc> const_p(new pc);

// This is legal
const_p = p;

// The atomic equivalent is not
boost::atomic_store(&const_p, p);

问题在于boost::atomic_store期望两个参数都是T*T,但是尽管将p赋值给const_p是完全安全的,但它们被认为是不同的类型。以下方法也无法解决此问题。
boost::atomic_store(&const_p, const_cast<boost::shared_ptr<const pc> > (p));

尽管上述代码将pc*转换为完全安全的const pc*,但它会产生一个关于const_cast无法转换到不同类型的错误。我理解这是因为pc是模板参数,被视为shared_ptr类型的一部分而不是cv限定符。以下代码可行。
boost::atomic_store(&const_p, boost::shared_ptr<const pc>(p));

但是,它会创建一个额外的不必要的 boost::shared_ptr。据我所知,boost::const_pointer_cast<const pc>(p) 也是如此。如果不再需要 p,则可以避免这种情况。

boost::atomic_store(&const_p, boost::shared_ptr<const pc>(std::move(p));

这仍然会创建一个额外的对象,但由于没有修改引用计数,所以不应该有影响,因为这是复制 shared_ptr 的昂贵部分,原因在于它是原子的。

碰巧这发生在我的代码中非关键的部分,所以我对上述内容感到满意,但出于将来的参考,我想知道:如果没有选项使用 std::move,如何在不创建不必要的临时指针的情况下原子地存储 boost::shared_ptr<const T>boost::shared_ptr<T>?这应该是可能的,因为通过 const T* 安全地查看 T,但我无法想出一种方法。

1个回答

2
我理解由于pc是一个模板参数,它被视为shared_ptr类型的一部分而不是cv限定符。是的,这被称为“非可推导上下文”。

The following work

boost::atomic_store(&const_p, boost::shared_ptr<const pc>(p));

However, it creates an extra unecessary boost::shared_ptr. It is my understanding that the same is true for boost::const_pointer_cast<const pc>(p) This can be avoided if p is no longer needed.

好的,惊喜的是,你总是得到复制品:

template<class T> void atomic_store( shared_ptr<T> * p, shared_ptr<T> r ) BOOST_SP_NOEXCEPT
{
    boost::detail::spinlock_pool<2>::scoped_lock lock( p );
    p->swap( r );
}

请注意第二个参数是按值传递的。这一点立即解决了谜团:

在Coliru上实时查看

#include <boost/shared_ptr.hpp> 
#include <boost/make_shared.hpp> 
#include <boost/atomic.hpp>

namespace pcl {
    struct PointXYZ {};
    template <typename P> struct PointCloud {
    };
}

int main() {
    using pc = pcl::PointCloud<pcl::PointXYZ>;
    boost::shared_ptr<pc> p             = boost::make_shared<pc>();
    boost::shared_ptr<const pc> const_p = boost::make_shared<pc>();

    // This is legal
    const_p = p;

    // The atomic equivalent is too
    boost::atomic_store<pc const>(&const_p, p);
}

如果不能使用std::move,如何在不创建不必要的临时指针的情况下原子地存储boost::shared_ptr到另一个boost::shared_ptr中?
你无法这样做。可以这样看待:load/store应该是可适用于原子无锁实现的简单操作。它们只做一件事,并且做得很好¹。
进行隐式转换并不是该函数的职责。
我建议使用包装函数,甚至使用ADL从您自己的命名空间解析出您自己的重载。

我不知道当时我在想什么。既然模板无论如何都会按值传递 r,那么它总是需要至少一次复制或移动,并且转换为 const 会添加一个移动但不会额外复制。这些模板没有设计为按引用传递 r,难道是出于线程安全的考虑吗? - patatahooligan
好问题。我刚刚查了一下,C++11规范也是这么说的。http://en.cppreference.com/w/cpp/memory/shared_ptr/atomic 我会在休息室寻求帮助。 - sehe
2
复制一个shared_ptr就是解引用和原子递增(或两者都有)。此外,如果r是一个引用,那么用户代码可能会使pr成为完全相同的shared_ptr,我不知道这会产生什么后果。使用复制交换更简单、更安全。 - ratchet freak
1
还有一个关于C兼容性的问题。为了保持C++11和C11原子操作的兼容性,已经付出了相当多的努力。其中包括避免使用引用(几乎全部都是这样)。如果函数需要修改某些东西,它会传递一个指针。否则,它使用一个值。 - Jerry Coffin
1
我花了一些时间才明白,但复制和交换并没有真正增加太多开销。交换是非原子性的,因为它只影响对象和控制块的指针。复制无论如何都是必要的,因为它需要存储在 *p 中。锁定将被两种方式所需,因为它是一个原子存储。就像我之前说的那样,转换为 const 将添加一个单独的移动,因为 shared_ptr<const T> 将是一个临时的。所以我认为这个答案及其评论已经回答了这个问题。 - patatahooligan

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