2

出于培训目的,我正在尝试编写自己的智能指针,模仿std::shared_ptr. 我有一个static std::map<void *, int> ref_track跟踪内存中是否还有引用某个块的共享指针。

我的概念是这样的:

template <typename PType>
class shared_ptr
{
    public:
        shared_ptr()
        : value_(nullptr), ptr_(nullptr)
        {}

        template <typename T>
        explicit shared_ptr(T * ptr)
        : shared_ptr()
        {
            reset(ptr);
        }

        template <typename T>
        shared_ptr(shared_ptr<T> const & other)
        : shared_ptr()
        {       
            reset(other.get());
        }

        ~shared_ptr()
        {
            reset();
        }

        void reset()
        {
            if(value_)
            {
                delete value_; // Segmentation fault here!
                value_ = 0;
                ptr_ = 0;
            }
        }

        template <typename T>
        void reset(T * ptr)
        {   
            reset();
            if(ptr)
            {
                value_ = new shared_ptr_internal::storage_impl<
                    T
                >(ptr);
                ptr_ = ptr;
            }
        }

        PType * get() const
        {
            return ptr_;
        }

        typename shared_ptr_internal::ptr_trait<PType>::type operator *()
        {
            return *ptr_;
        }

    private:
        shared_ptr_internal::storage_base * value_;
        PType * ptr_;
};

在运行我的测试套件时,我注意到

shared_ptr<int> a(new int(42));
a.reset(new int(13));

工作正常,但是

shared_ptr<int> a(new int(42));
a = shared_ptr<int>(new int(13));

导致问题:*ais0而不是13,并且由于 .delete value_的析构函数中的分段错误而崩溃a。我在源代码中用注释标记了崩溃。

使用的内部类是

namespace shared_ptr_internal
{

    typedef std::map<void *, int> ref_tracker;
    typedef std::map<void *, int>::iterator ref_tracker_iterator;
    typedef std::pair<void *, int> ref_tracker_entry;

    static ref_tracker ref_track;

    struct storage_base
    {
        virtual ~storage_base() {}
    };

    template <typename PType>
    struct storage_impl : storage_base
    {
        storage_impl(PType * ptr)
        : ptr_(ptr)
        {
            ref_tracker_iterator pos = ref_track.find(ptr);
            if(pos == ref_track.end())
            {
                ref_track.insert(
                    ref_tracker_entry(ptr, 1)
                );
            }
            else
            {
                ++pos->second;
            }
        }

        ~storage_impl()
        {
            ref_tracker_iterator pos = ref_track.find(ptr_);
            if(pos->second == 1)
            {
                ref_track.erase(pos);
                delete ptr_;
            }
            else
            {
                --pos->second;
            }
        }

        private:
            PType * ptr_;
    };

    template <typename PType>
    struct ptr_trait
    {
        typedef PType & type;
    };

    template <>
    struct ptr_trait<void>
    {
        typedef void type;
    };
}

对于大量源代码感到抱歉,但我真的不知道如何进一步缩小范围。我将不胜感激任何可能导致段错误的想法,以及为什么在手动使用重置时不会发生这种情况。

更新

我的(不工作的)赋值运算符:

template <typename T>
shared_ptr<PType> & operator =(shared_ptr<T> const & other)
{
    if(this != &other)
    {
        value_ = nullptr;
        ptr_ = nullptr;
        reset(other.get());
    }
    return *this;
}
4

3 回答 3

2

您缺少赋值运算符。

这意味着在以下代码中:

a = shared_ptr<int>(new int(13));

创建一个临时共享指针;然后默认的赋值运算符只是简单地复制指针,a而不释放旧值或更新引用计数;然后临时删除该值,留下a一个悬空指针。

于 2012-07-02T11:08:04.330 回答
2

似乎违反了三规则:您有一个自定义复制构造函数和一个自定义析构函数,但没有自定义赋值运算符。因此a = shared_ptr<int>(new int(13))只会从临时复制两个指针value_ptr_而无需对您的参考跟踪进行任何更新。因此,当您销毁临时对象时,将不再有对该指针的跟踪引用,这将导致其删除。另请注意,旧指针将在分配中泄露。

于 2012-07-02T11:08:46.307 回答
0

您忘记在指针类中添加一个赋值运算符,它应该减少对旧对象的引用数并增加对分配对象的引用数。大多数时候,它是实现 operator= 的最简单的方法,它是一个复制 d'tor 和一个交换函数:

shared_ptr& shared_ptr<T>::operator=( shared_ptr<T> other ) 
{
    other.swap( this );

    return *this;
}
于 2012-07-02T11:11:11.393 回答