引用计数智能指针的正确实现



简介:

我们正在尝试实现我们自己的 ref 计数智能指针(类似于std::shared_ptr)。我们已经有了引用计数的接口类,其他类可以从中继承。该接口提供了grab()drop()方法,这些方法相应地增加和减少引用计数。Tldr 手动引用计数。我们现在要做的是类似 RAII 的包装器,它在复制时调用grab()(复制 ctor)并在析构函数中drop()

法典

下面是简介部分中描述的智能指针实现的源代码。下面还粘贴了必要的部分:

//IReferenceCounted is the interface class defining grab() and drop() methods incrementing and decrementing ref count
template<class I_REFERENCE_COUNTED>
class smart_refctd_ptr
{
static_assert(std::is_base_of<IReferenceCounted, I_REFERENCE_COUNTED>::value,"Wrong Base Class!");

mutable I_REFERENCE_COUNTED* ptr; // since IReferenceCounted declares the refcount mutable atomic
template<class U> friend class smart_refctd_ptr;
public:
constexpr smart_refctd_ptr() noexcept : ptr(nullptr) {}
constexpr smart_refctd_ptr(std::nullptr_t) noexcept : ptr(nullptr) {}
template<class U>
explicit smart_refctd_ptr(U* _pointer) noexcept : ptr(_pointer)
{
if (_pointer)
_pointer->grab();
}
template<class U>
explicit smart_refctd_ptr(U* _pointer, dont_grab_t t) noexcept : ptr(_pointer) {}
template<class U>
smart_refctd_ptr(const smart_refctd_ptr<U>& other) noexcept : smart_refctd_ptr(other.ptr) {}
template<class U>
smart_refctd_ptr(smart_refctd_ptr<U>&& other) noexcept : smart_refctd_ptr()
{
if (ptr) // should only happen if constexpr (is convertible)
ptr->drop();
ptr = other.ptr;
other.ptr = nullptr; // should only happen if constexpr (is convertible)
}
~smart_refctd_ptr() noexcept
{
if (ptr)
ptr->drop();
}
template<class U>
inline smart_refctd_ptr& operator=(U* _pointer) noexcept
{
if (_pointer)
_pointer->grab();
if (ptr)
ptr->drop();
ptr = _pointer;
return *this;
}
template<class U>
inline smart_refctd_ptr& operator=(const smart_refctd_ptr<U>& other) noexcept
{
return operator=(other.ptr);
}
template<class U>
inline smart_refctd_ptr& operator=(smart_refctd_ptr<U>&& other) noexcept
{
if (ptr) // should only happen if constexpr (is convertible)
ptr->drop();
ptr = other.ptr;
other.ptr = nullptr; // should only happen if constexpr (is convertible)
return *this;
}
inline I_REFERENCE_COUNTED* get() { return ptr; }
inline const I_REFERENCE_COUNTED* get() const { return ptr; }
inline I_REFERENCE_COUNTED* operator->() { return ptr; }
inline const I_REFERENCE_COUNTED* operator->() const { return ptr; }
inline I_REFERENCE_COUNTED& operator*() { return *ptr; }
inline const I_REFERENCE_COUNTED& operator*() const { return *ptr; }
inline I_REFERENCE_COUNTED& operator[](size_t idx) { return ptr[idx]; }
inline const I_REFERENCE_COUNTED& operator[](size_t idx) const { return ptr[idx]; }

inline explicit operator bool() const { return ptr; }
inline bool operator!() const { return !ptr; }
template<class U>
inline bool operator==(const smart_refctd_ptr<U> &other) const { return ptr == other.ptr; }
template<class U>
inline bool operator!=(const smart_refctd_ptr<U> &other) const { return ptr != other.ptr; }
template<class U>
inline bool operator<(const smart_refctd_ptr<U> &other) const { return ptr < other.ptr; }
template<class U>
inline bool operator>(const smart_refctd_ptr<U>& other) const { return ptr > other.ptr; }
};

问题所在

事实证明,这并不像看起来那么容易,因为我认为,复制省略。在进行复制时不调用智能指针的复制构造函数。我在Visual Studio 2017中工作,似乎甚至没有从复制构造函数C++代码生成的程序集:我无法在其中放置断点,也无法看到为其生成任何asm。 即使是这 2 行简单的C++代码......

core::smart_refctd_ptr<IAsset> mesh_ptr(mesh);
core::smart_refctd_ptr<IAsset> mesh_ptr2 = mesh_ptr;

。我得到这个 asm:

;    core::smart_refctd_ptr<IAsset> mesh_ptr(mesh);
mov         rdx,qword ptr [mesh]  
lea         rcx,[mesh_ptr]  
call        irr::core::smart_refctd_ptr<irr::asset::IAsset>::smart_refctd_ptr<irr::asset::IAsset><irr::asset::SCPUMesh> (013F2A79B7h)  
nop  
;   core::smart_refctd_ptr<IAsset> mesh_ptr2 = mesh_ptr;
mov         rax,qword ptr [mesh_ptr]  
mov         qword ptr [mesh_ptr2],rax  

根本不调用复制构造函数。因此,引用计数不会递增,而是调用 2 个析构函数,这显然会导致递减次数多于递增次数。这发生在带有/Od 标志的调试构建中(甚至还没有尝试完全优化)。 由于它应该与std::shared_ptr非常相似,我在编译器(上面提到的VS2017)中查找了它的实现。但是,我真的看不到任何可以向我展示解决方案的技巧。我试过 -std::shared_ptr工作正常(复制时引用计数正确增加),但我不知道为什么。为什么shared_ptr有效,而我们的smart_refctd_ptr不起作用?

解决方案是制作非模板复制构造函数。用户定义的复制构造函数必须是非模板构造函数,因为12.8.2 [class.copy]中的 C++14 个标准状态。移动构造函数、复制赋值运算符和移动赋值运算符也需要做同样的事情。因此,该复制构造函数现在如下所示:

template<class U>
void copy(const smart_refctd_ptr<U>& other) noexcept
{
if (other.ptr)
other.ptr->grab();
ptr = other.ptr;
}
template<class U, std::enable_if_t<!std::is_same<U,I_REFERENCE_COUNTED>::value, int> = 0>
smart_refctd_ptr(const smart_refctd_ptr<U>& other) noexcept
{
this->copy(other);
}
smart_refctd_ptr(const smart_refctd_ptr<I_REFERENCE_COUNTED>& other) noexcept
{
this->copy(other);
}

请注意,很可能不需要std::enable_if_t<...,int> = 0模板参数。

相关内容

  • 没有找到相关文章

最新更新