创建非线程安全的shared_ptr

时间:2011-07-06 08:48:59

标签: c++ boost stl

我正在研究一个多线程程序,但是有一个UI组件,它广泛使用std :: shared_ptr来管理元素。我可以保证只有一个线程会使用这些shared_ptrs。

有没有办法定义一个不会导致线程安全引用计数开销的shared_ptr?

它可以基于boost :: shared_ptr或std :: shared_ptr。

编辑:感谢您提及intrusive_ptr的答案。我忽略了提到我还需要weak_ptr功能,以便将其排除在外。

更新:我的答案是使用Boost的local_shared_ptr。请参阅'他漫步'的评论

6 个答案:

答案 0 :(得分:4)

Andrei Alexandrescu在CppCon 2014上讨论了实现自己的单线程共享指针类(带有一些额外的优化)

观看视频here

幻灯片here

我真的认为标准或者提升应该提供一个模板参数,用于在共享的ptrs中使用原子引用计数...

答案 1 :(得分:3)

您可以使用intrusive_ptr,因为它允许您提供自己的引用计数。如果该引用计数是变量的简单增量/减量,则可能不会获得比该变量更好的性能。

答案 2 :(得分:1)

我有代码,其中复制shared_ptr的开销已经成为问题,并且在那时使用了替代技术。让我首先证明其他评论是正确的,shared_ptr的开销非常低。我对此进行了分析,实际上找到了我的一个麻烦点。在我的AMD64 Phenom上调用复制shared_ptr的函数大约需要12ns而不是用普通指针调用相同的函数大约1ns。

考虑到这些数字,很难想象你会在原始指针和shared_ptr之间得到任何类型的“安全”变体。所以我在这种情况下做的是将实际指针或const &传递给shared_ptr。通常我会在代码的整个部分放置一个互斥锁,这样我就可以保证在整个持续时间内保持shared_ptr。您可以手动滚动单线程引用计数,但如果您知道它没有被共享,那将是什么意思?

但要仔细考虑时间安排。除非您每秒复制shared_ptr数千甚至数万次,否则您将不会注意到shared_ptr的开销。

在同一项目的GUI代码中,我总是使用shared_ptr,只有服务器代码在几个关键区域中避免使用它。 GUI中还有很多其他东西可以减慢速度:避免使用shared_ptr会产生明显的差异。

答案 3 :(得分:1)

我建议使用Boost侵入式智能指针。

Scott Meyer(此处:http://www.aristeia.com/BookErrata/M29Source.html)的实施也发布在“More Effective C++

但是,如果它有帮助,我猛拉了一个简单的引用计数指针(支持多​​态分配和自定义deletors)。 这个是决定不感知的

  

注意:我错误地记错了。对于其他项目,多态异常是一种变异。我也有,但它不支持自定义deletor :)让我知道是否有人感兴趣;当然,它附带了功能

的单独单元测试

它附带单元测试(例如,检查着名的remove linked list node排序错误)。所以你知道你得到了什么:)

/*
 * counted_ptr - simple reference counted pointer.
 *
 * The is a non-intrusive implementation that allocates an additional
 * int and pointer for every counted object.
 */
#ifndef COUNTED_PTR_H
#define COUNTED_PTR_H

#include <stdlib.h>

extern "C" bool mtx_unit_test_countedptr();

namespace MtxChess {

/* For ANSI-challenged compilers, you may want to #define
 * NO_MEMBER_TEMPLATES or explicit */

template <class X>
    struct FreeMallocPolicy
    {
        static void do_free(X* p) { if (p) ::free(p); p = 0; }
    };

template <class X>
    struct ScalarDeletePolicy
    {
        static void do_free(X* p) { if (p) delete p; p = 0; }
    };

template <class X>
    struct ArrayDeletePolicy
    {
        static void do_free(X* p) { if (p) delete[] p; p = 0; }
    };

template <class X,class _P=ScalarDeletePolicy<X> > class counted_ptr
{
public:
    typedef X element_type;

    explicit counted_ptr(X* p = 0) // allocate a new counter
        : itsCounter(0) {if (p) itsCounter = new counter(p);}
    ~counted_ptr()
        {release();}
    counted_ptr(const counted_ptr& r) throw()
        {acquire(r.itsCounter);}
    operator bool() const { return 0!=get(); }
    void clear() { (*this) = counted_ptr<X>(0); }
    counted_ptr& operator=(const counted_ptr& r)
    {
        if (this != &r) {
            auto_release keep(itsCounter);
            acquire(r.itsCounter);
        }
        return *this;
    }
    bool operator<(const counted_ptr& r) const
    {
        return get()<r.get();
    }
    bool operator==(const counted_ptr& r) const
    {
        return get()==r.get();
    }
    bool operator!=(const counted_ptr& r) const
    {
        return get()!=r.get();
    }

#ifndef NO_MEMBER_TEMPLATES
//  template <class Y> friend class counted_ptr<Y>;
    template <class Y> counted_ptr(const counted_ptr<Y>& r) throw()
        {acquire(r.itsCounter);}
    template <class Y> counted_ptr& operator=(const counted_ptr<Y>& r)
    {
        if (this != &r) {
            auto_release keep(itsCounter);
            acquire(r.itsCounter);
        }
        return *this;
    }
    template <class Y> bool operator<(const counted_ptr<Y>& r) const
    {
        return get()<r.get();
    }
    template <class Y> bool operator==(const counted_ptr<Y>& r) const
    {
        return get()==r.get();
    }
    template <class Y> bool operator!=(const counted_ptr<Y>& r) const
    {
        return get()!=r.get();
    }
#endif // NO_MEMBER_TEMPLATES

    X& operator*()  const throw()   {return *itsCounter->ptr;}
    X* operator->() const throw()   {return itsCounter->ptr;}
    X* get()        const throw()   {return itsCounter ? itsCounter->ptr : 0;}
    bool unique()   const throw()
        {return (itsCounter ? itsCounter->count == 1 : true);}

private:
    struct counter {
        counter(X* p = 0, unsigned c = 1) : ptr(p), count(c) {}
        X*          ptr;
        unsigned    count;
    }* itsCounter;

    void acquire(counter* c) throw()
    {   
        // increment the count
        itsCounter = c;
        if (c) ++c->count;
    }

    void release()
    {   
        dorelease(itsCounter);
    }

    struct auto_release
    {
        auto_release(counter* c) : _c(c) {}
       ~auto_release() { dorelease(_c); }
        counter* _c;
    };

    void static dorelease(counter* itsCounter)
    {
        // decrement the count, delete if it is 0
        if (itsCounter) {
            if (--itsCounter->count == 0) {
                _P::do_free(itsCounter->ptr);
                delete itsCounter;
            }
            itsCounter = 0;
        }
    }
};

} // EON

#endif // COUNTED_PTR_H

单元测试(单独编译)

/*
 * counted_ptr (cpp) - simple reference counted pointer.
 *
 * The is a non-intrusive implementation that allocates an additional
 * int and pointer for every counted object.
 */

#include "counted_ptr.hpp"
#include "internal.hpp"
#include <map>
#include <string>

namespace MtxChess { 

    namespace /*anon*/
    {
        // sensed events
        typedef std::map<std::string, int> Events;
        static Events constructions, destructions;

        struct Trackable
        {
            Trackable(const std::string& id) : _id(id) {    constructions[_id]++; }
            ~Trackable()                               {    destructions[_id]++; }
            const std::string _id;
        };

        typedef counted_ptr<Trackable> target_t;

        bool testBehaviour()
        {   
            static const counted_ptr<Trackable> Nil = target_t(0);
            bool ok = true;

            constructions.clear();
            destructions.clear();

            MTXASSERT_EQ(ok, 0ul,  constructions.size());
            MTXASSERT_EQ(ok, 0ul,  destructions.size());

            target_t a = target_t(new Trackable("aap"));

            MTXASSERT_EQ(ok, 1ul,  constructions.size());
            MTXASSERT_EQ(ok, 1,    constructions["aap"]);
            MTXASSERT_EQ(ok, 0ul,  destructions.size());

            MTXASSERT_EQ(ok, 0,    constructions["noot"]);
            MTXASSERT_EQ(ok, 2ul,  constructions.size());
            MTXASSERT_EQ(ok, 0ul,  destructions.size());

            target_t hold;
            {
                target_t b = target_t(new Trackable("noot")),
                         c = target_t(new Trackable("mies")),
                         nil = Nil,
                         a2 = a;

                MTXASSERT(ok, a2==a);
                MTXASSERT(ok, nil!=a);

                MTXASSERT_EQ(ok, 3ul,  constructions.size());
                MTXASSERT_EQ(ok, 1,    constructions["aap"]);
                MTXASSERT_EQ(ok, 1,    constructions["noot"]);
                MTXASSERT_EQ(ok, 1,    constructions["mies"]);
                MTXASSERT_EQ(ok, 0,    constructions["broer"]);
                MTXASSERT_EQ(ok, 4ul,  constructions.size());

                MTXASSERT_EQ(ok, 0ul,  destructions.size());

                hold = b;
            }

            MTXASSERT_EQ(ok, 1ul,  destructions.size());
            MTXASSERT_EQ(ok, 0,    destructions["aap"]);
            MTXASSERT_EQ(ok, 0,    destructions["noot"]);
            MTXASSERT_EQ(ok, 1,    destructions["mies"]);
            MTXASSERT_EQ(ok, 3ul,  destructions.size());

            hold = Nil;
            MTXASSERT_EQ(ok, 3ul,  destructions.size());
            MTXASSERT_EQ(ok, 0,    destructions["aap"]);
            MTXASSERT_EQ(ok, 1,    destructions["noot"]);
            MTXASSERT_EQ(ok, 1,    destructions["mies"]);
            MTXASSERT_EQ(ok, 4ul,  constructions.size());

            // ok, enuf for now
            return ok;
        }

        struct Linked : Trackable
        {
            Linked(const std::string&t):Trackable(t){}
            counted_ptr<Linked> next;
        };

        bool testLinked()
        {   
            bool ok = true;

            constructions.clear();
            destructions.clear();
            MTXASSERT_EQ(ok, 0ul,  constructions.size());
            MTXASSERT_EQ(ok, 0ul,  destructions.size());

            counted_ptr<Linked> node(new Linked("parent"));
            MTXASSERT(ok, node.get()); 
            node->next = counted_ptr<Linked>(new Linked("child"));

            MTXASSERT_EQ(ok, 2ul,  constructions.size());
            MTXASSERT_EQ(ok, 0ul,  destructions.size());

            node = node->next;
            MTXASSERT(ok, node.get()); 

            MTXASSERT_EQ(ok, 2ul,  constructions.size());
            MTXASSERT_EQ(ok, 1ul,  destructions.size());

            node = node->next;
            MTXASSERT(ok,!node.get()); 

            MTXASSERT_EQ(ok, 2ul,  constructions.size());
            MTXASSERT_EQ(ok, 2ul,  destructions.size());

            return ok;
        }

    }

} // EON

int main()
{   
    using namespace MtxChess;

    bool ok = true;
    ok = testBehaviour() && ok;
    ok = testLinked() && ok;

    return ok?0:1;
}

答案 4 :(得分:0)

Boost提供了一个可以定义的宏,它不会使用线程安全引用计数。

答案 5 :(得分:0)

现在将其添加为接受的答案。 Boost local_shared_ptr是一个单线程引用计数的智能指针,该指针使用非原子操作来提高速度:

https://www.boost.org/doc/libs/1_65_0/libs/smart_ptr/doc/html/smart_ptr.html#local_shared_ptr