【问题标题】:How to use boost::atomic_store with shared_ptr<T> and shared_ptr<const T>?如何将 boost::atomic_store 与 shared_ptr<T> 和 shared_ptr<const T> 一起使用?
【发布时间】:2018-02-19 15:59:03
【问题描述】:

在我的情况下 Tpcl::PointCloud&lt;pcl::PointXYZ&gt;&gt; 但问题应该代表任何类型 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&lt;const pc&gt;(p) 也是如此,如果不再需要 p,则可以避免这种情况。

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

这仍然会创建一个额外的对象,但这并不重要,因为引用计数没有被修改,这是复制 shared_ptr 的昂贵部分,因为它是原子的。

碰巧这发生在我的代码的非关键部分,所以我对上述内容很好,但我想知道以供将来参考:如果std::move 不是一个选项,怎么办?以原子方式将boost::shared_ptr&lt;T&gt; 存储到boost::shared_ptr&lt;const T&gt;,而无需创建不必要的临时指针?应该可以,因为通过const T* 查看T 是安全的,但我想不出办法。

【问题讨论】:

    标签: c++ boost shared-ptr atomic


    【解决方案1】:

    我了解因为 pc 是模板参数,所以它被认为是 shared_ptr 类型的一部分,而不是 cv 限定。

    是的,这被称为“不可演绎的上下文”。

    接下来的工作

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

    但是,它创建了一个额外的不必要的boost::shared_ptr。这是我的 理解同样适用于 boost::const_pointer_cast&lt;const pc&gt;(p) 如果 p 为否,则可以避免这种情况 需要更长的时间。

    嗯,令人惊讶的是,你总是得到副本:

    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 );
    }
    

    注意第二个参数是按值。这一下子就解开了谜团:

    Live On 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 没有 创建不必要的临时指针的开销?

    你不能。以这种方式看待它:加载/存储意味着是适合原子无锁实现的琐碎操作。他们只做一件事,而且做得很好¹。

    执行隐式转换不是该函数的职责。

    我建议使用包装函数,甚至使用 ADL 来解决您自己命名空间中的重载问题。

    【讨论】:

    • 我不知道我在想什么。由于模板无论如何都按值获取r,因此它总是需要至少一个副本或一个移动,并且转换为 const 将添加一个移动而不是一个额外的副本。这些模板没有设计为通过引用获取r 是否有原因?是为了线程安全吗?
    • 好问题。我刚刚检查过,C++11 规范也是这么说的。 en.cppreference.com/w/cpp/memory/shared_ptr/atomic我会在the lounge寻求帮助
    • 复制 shared_ptr 是一个 deref 和一个或两个原子增量。此外,如果r 是一个参考,那么用户代码可能有pr 完全相同shared_ptr,我不知道这会产生什么后果。进行复制交换更简单、更安全。
    • 还有一个 C 兼容性问题。在保持 C++11 和 C11 原子操作兼容方面付出了相当多的努力。除其他外,这意味着他们完全避免引用(几乎?)。当一个函数需要修改某些东西时,它会传递一个指针。否则,它使用一个值。
    • 我花了一些时间才得到它,但是复制和交换并没有真正增加太多开销。交换是非原子的,因为它只影响指向对象和控制块的指针。无论如何,副本都是必需的,因为它需要存储在*p 中。无论哪种方式都需要锁,因为它是原子存储。并且像我之前所说的那样转换为 const 将增加一个动作,因为 shared_ptr&lt;const T&gt; 将是一个临时的。所以我认为这个带有 cmets 的答案已经涵盖了这个问题。
    猜你喜欢
    • 1970-01-01
    • 2018-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-21
    • 1970-01-01
    • 1970-01-01
    • 2010-11-27
    相关资源
    最近更新 更多