避免对象切割用于非虚析构函数

5
我正在编写智能指针的代码作为练习。使用在线教程(12),我已经开发了一个带有引用计数的普通智能指针类。问题是我无法解决以下问题:
当智能指针检测到不再存在对特定对象的引用时,即使最终智能指针的模板参数是基类型,它也必须通过指向原始类型的指针删除对象,这是为了避免非虚析构函数的对象切片。
我该如何实现呢?基本上,我的代码看起来像下面这样(来自教程)。
template < typename T > class SP
{
private:
    T*    pData;       // pointer
    RC* reference; // Reference count

public:
    SP() : pData(0), reference(0) 
    {
        // Create a new reference 
        reference = new RC();
        // Increment the reference count
        reference->AddRef();
    }

    SP(T* pValue) : pData(pValue), reference(0)
    {
        // Create a new reference 
        reference = new RC();
        // Increment the reference count
        reference->AddRef();
    }

    SP(const SP<T>& sp) : pData(sp.pData), reference(sp.reference)
    {
        // Copy constructor
        // Copy the data and reference pointer
        // and increment the reference count
        reference->AddRef();
    }

    ~SP()
    {
        // Destructor
        // Decrement the reference count
        // if reference become zero delete the data
        if(reference->Release() == 0)
        {
            delete pData;
            delete reference;
        }
    }

    T& operator* ()
    {
        return *pData;
    }

    T* operator-> ()
    {
        return pData;
    }
    
    SP<T>& operator = (const SP<T>& sp)
    {
        // Assignment operator
        if (this != &sp) // Avoid self assignment
        {
            // Decrement the old reference count
            // if reference become zero delete the old data
            if(reference->Release() == 0)
            {
                delete pData;
                delete reference;
            }

            // Copy the data and reference pointer
            // and increment the reference count
            pData = sp.pData;
            reference = sp.reference;
            reference->AddRef();
        }
        return *this;
    }
};

编辑:

为了实现这个目标,我必须拥有指向原始类型的指针。

我在这里发布了一个问题:通过指向派生类而不是基类的指针进行删除

但现在,由于查看了评论和答案,我认为两者都相关。我有以下构造函数:

template <typename T>
template <typename U>
Sptr<T>::Sptr(U* u) : obj(u),ref(NULL) {
    //do something
    ref = new RC();
    ref->AddRef();
}

现在考虑 Sptr<Base1> sp(new Derived);,其中 Derived 是从 Base1 派生而来的。 Base1 有受保护的构造函数/析构函数。 这将为类型 T 的对象存储,但我需要通过类型 U 的对象进行存储。我需要保留它。我该如何做?

如果一个基类没有虚析构函数,而又有人试图通过指向该基类的指针来删除派生类,那么这个人就是在错误地执行它。 - Chad
2
为了实现这一点,您需要给SP一个模板构造函数SP<T>::SP(U *u) { ... },并以某种方式存储原始类型U(必须派生自T),以便能够稍后调用U的析构函数。 - Angew is no longer proud of SO
您似乎还缺少复制、移动和赋值运算符,以便让 SmartPtr<Base> = SmartPtr<Derived> 正常工作。 - Yakk - Adam Nevraumont
@Angew 我该如何存储类型 U?我已经有了那个构造函数。我会更新问题的描述。 - footy
@footy,正如Yakk的回答所示,它存储在原始U*上键入的delete表达式。 - Angew is no longer proud of SO
显示剩余2条评论
2个回答

6
您的智能指针需要三个信息块。
第一,数据的指针(T*或其他)。
第二,引用计数:std::atomic<int>或其他。
第三,析构函数(std::function<void(T*)>或其他)。
当智能指针首次创建时,该析构函数被创建。当您的智能指针被复制到另一个智能指针时,该析构函数被复制。如果新智能指针的类型与旧的不匹配,则该析构函数以兼容的方式进行包装(std::function<void(Base*)> = std::function<void(Derived*)>是否可以直接使用?无论如何,您基本上都在这样做)。
默认情况下,该析构函数只是delete t,但作为一个附带好处,这允许您的智能指针的用户传递一个析构函数,它并不总是delete t
有趣的是,在等同于reset的操作中,您会替换您的析构函数。因此,您实际上可以将析构函数的签名设置为std::function<void()>,这使得在TU类型的智能指针之间移动它变得更加容易。
template < typename T > class SP
{
private:
  T*    pData;       // pointer
  RC* reference; // Reference count
  std::function<void()> destroyData;
public:
  template<typename U>
  SP(U* pValue):
    pData(pValue),
    reference(nullptr),
    // store how to destroy pValue now, for later execution:
    destroyData([pValue]()->void{
      delete pValue;
    })
  {
    // Create a new reference 
    reference = new RC();
    // Increment the reference count
    reference->AddRef();
  }
  // similar for operator=, and you may have to do something for SP<T> as well:
  template<typename U>
  SP(const SP<U>& sp):
    pData(sp.pData),
    reference(sp.reference),
    destroyData(sp.destroyData)
  {
    // Copy constructor
    // Copy the data and reference pointer
    // and increment the reference count
    reference->AddRef();
  }
  template<typename U>
  SP<T>& operator = (const SP<U>& sp)
  {
    // blah blah blah, then
    destroyData = sp.destroyData;
  }

  ~SP()
  {
    // Destructor
    // Decrement the reference count
    // if reference become zero delete the data
    if(reference->Release() == 0)
    {
        delete reference;
        destroyData(); // here I destroyed it!
    }
  }
};

或者类似于这样的东西。

我该如何存储指向正确销毁函数的指针?你能再具体一些吗?因为我对这种模板编程风格还很陌生。 - footy
@footy 包含演示技巧的代码片段。我还包括了处理非T*指针构造和分配给SP<T>类型的非SP<T>类型的开始。它还不完整,但我希望你能理解。一个好的想法是最终使用SFINAE使有效的U类型仅限于在重载分辨率点下降自T的那些类型,但这是一种高级技术。 - Yakk - Adam Nevraumont

0
另一种方法涉及将删除委托给另一个类。

// non templated base
class DeleterBase {
    public:
        virtual ~DeleterBase() { };
};

template <typename T>
class Deleter : public DeleterBase {
    private:
        T *ptr;
    public:
        Deleter(T *p) // remember the pointer with the correct type here
            : ptr{p}
        { }

        ~Deleter() { 
            delete this->ptr; // invokes correct destructor
        }
};

现在在智能指针中:

template <typename T>
class SP {
    private:
        T *p;
        DeleterBase *deleter;
    public:
        template <typename U> // U is deduced to actual type
        explicit SP(U *p)
            : p{p},
            deleter{new Deleter<U>(p)} // correct type
        { }

        // transfer control in the move constructor/assignment
        // increase the ref count in copy constructor/assignment

        // now just delete the deleter in the dtor
        ~SP() {
            if (reference_count_is_zero()) { // however you implement this
                delete this->deleter;
            }
        }
};

既然您已经有了RC类,您可以将我在这里展示的功能移动到该类中。使该类成为一个带有非模板基类的模板类,并在引用计数被删除时销毁指针。少传递一件事。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接