如何在没有虚拟析构函数的情况下使用我的自定义共享指针 class 调用派生析构函数?

How to call derived destructor using my custom shared pointer class without virtual destructor?

我正在创建我的自定义共享指针 class,我希望我的共享指针 class 在超出以下代码的范围时调用派生的 class 析构函数。

...
...
template<class T>
MySharedPtr<T>::MySharedPtr(T * p) : ptr(p), refCnt(new RefCount())
{
    refCnt->AddRef();
}

template<class T>
void MySharedPtr<T>::release()
{
    if (refCnt->Release() == 0) 
    {
        delete ptr;
        delete refCnt;
    }
    ptr = nullptr;
    refCnt = nullptr;
}
...
...

Base class 析构函数在超出范围时调用,但如果我使用 std::shared_ptr<Base> bptr(new Derived());,它会在超出范围时调用派生析构函数和基析构函数。我怎样才能用我的自定义实现相同的行为 class?

  class Base
    {
    public:
        
        Base() {
            cout << "Base default constructor" << endl;
        }
        ~Base() {
            cout << "Base destructor" << endl;
        }
        virtual void display() {
            cout << "in Base" << endl;
        }
    };
    
    class Derived : public Base
    {
    public:
        Derived() {
            cout << "Derived default constructor" << endl;
        }
        ~Derived() {
            cout << "Derived destructor" << endl;
        }
        virtual void display() {
            cout << "in Derived" << endl;
        }
    };
    int main()
    {
        MySharedPtr<Base> bptr(new Derived());
        bptr->display();
    }

您可以将 回调 存储为 RefCount 对象的一部分,当引用计数变为零时将调用该对象。此回调可以根据最初用于构造 MySharedPtr 对象的指针类型“记住”它需要做什么,即使有关大多数派生类型的知识可能已在其他地方丢失。

必须修改MySharedPtr的构造函数,这样调用构造函数时类型信息不会立即销毁。它需要 U*,而不是 T*。如果取T*,那么一进入构造函数,你就已经不知道原来的参数类型是什么了,因为它已经被转换成T*.

template <class T>
class MySharedPtr {
  public:
    template <class U>
    MySharedPtr(U* p);
  // ...
};

template <class T>
template <class U>
MySharedPtr<T>::MySharedPtr(U* p) : ptr(p), refCnt(new RefCount(p))
{
    refCnt->AddRef();
}

RefCount构造函数需要根据这个指针设置存储回调:

class RefCount {
  public:
    template <class U>
    RefCount(U* p) : deleter_{[p]{ delete p; }} {}
    // ...
    void invoke_deleter() { deleter_(); }

  private:
    int ref_count_ = 0;
    std::function<void()> deleter_;
};

现在,如果你这样做:

MySharedPtr<Base> bptr(new Derived());

Derived* 将被传递给 RefCount 构造函数,它将存储一个调用 deletethat 指针的回调(类型 Derived*) 而不是存储在 MySharedPtr 中的 ptr,它的类型是 Base*.

您需要添加代码以在引用计数变为 0 时调用此删除器:

template<class T>
void MySharedPtr<T>::release()
{
    if (refCnt->Release() == 0) 
    {
        refCnt->invoke_deleter();
        delete refCnt;
    }
    ptr = nullptr;
    refCnt = nullptr;
}

这基本上就是 std::shared_ptr 的工作方式,虽然我怀疑它在内部不使用 std::function,但是一些自定义类型擦除机制具有较低的开销,因为它不需要支持大多数 std::function 特征。