模板化中的析构函数实现 class

destructor implementation in templated class

如果我有这样的class:

template <class T>                            
class Array {                                              
 private:                                        
  T *m_pData;                                   
  unsigned int m_nSize;                         
 public:                                         
 Array(unsigned int nSize) : m_nSize(nSize),m_pData(nullptr) {                                           
 if(m_nSize > 0)
  m_pData = new T[m_nSize];                
 }                                           

 ~Array() {                    
  if(m_pData != NULL) {
   delete [] m_pData;
  }
};

如果现在我像这样创建 class 的对象:

Array<int> miArray(5);

析构函数的实现应该没问题,但是如果我创建一个这样的对象:

Array<int*> miArray(5);

在析构函数中,我应该删除存储在数组中的每个对象以避免内存泄漏。

我怎样才能做到这一点?

谢谢

我认为一个好的设计是将原始 owning 指针(例如 int* 在你的例子中)包装在 smart 指针 classes,如 std::unique_ptrstd::shared_ptr,或其他一些 RAII 包装器。 这就是 std::vector 的基本情况,如果您想在向量中存储指向 T 的拥有指针,则可以使用 vector<unique_ptr<T>>vector<shared_ptr<T>>

请注意,在您的容器中存储原始 observing 指针是可以的(只要正确处理指向对象的生命周期)。


补充几点:

  1. 您的 class 应该禁止通过 =delete 复制构造函数和复制赋值进行复制,或者正确地实现这些复制操作。由于它处于当前状态,如果人们试图复制您的 class.

  2. 的构造或复制分配实例,它很容易出错
  3. Array(unsigned int nSize) 构造函数应标记为 explicit 以避免从无符号整数进行 隐式 转换。

您可以使用 a specialization of your destructor,或使用标记分派删除功能(这会更灵活)。然而,这会导致一个非常笨拙和脆弱的设计。假设您已经实现了 push_back 的等效项,请考虑以下用户代码片段:

{
    Array<int*> arr;
    arr.push_back(new int(42)); // new is on the side of the user
} // ... but delete is not. Weird.

这也会导致整个 class 错误:你应该只给 newed Ts 一个 Array<T*>,但是没有任何安全措施可以防止传入其他内容:

{
    Array<int*> arr;
    int i = 42;
    arr.push_back(&i); // No diagnostic
    arr.push_back(new int[17]); // No diagnostic either
} // Undefined behaviour from calling `delete` on stuff that hasn't been `new`ed

所有这些都是无原始拥有指针规则存在的原因:原始指针根本不应该管理资源生命周期。如果我使用 Array<int*>,它应该存储我的指针并让我使用它们,但绝不会 曾经 delete 它们,因为那试图管理生命周期。

相反,如果我想要一个管理生命周期的 Array,我将使用相应的智能指针和 Array<std::unique_ptr<int>>。这与 Array 很好地结合在一起,要求它只调用所包含对象的析构函数(它已经这样做了)。这些析构函数(~unique_ptr,即)将按照它们的工作传递地释放资源,一切都很好。

补充说明:

  • 如果需要,请注意初始化 Array 的缓冲区——m_pData = new T[m_nSize]; 将分配默认初始化的对象。如果这些对象没有默认构造函数,它们的值将是不确定的。一个简单的解决方法是使用 new T[m_nSize]{},它将执行值初始化——即将算术类型初始化为零,指向 nullptr 的指针和复合类型递归地初始化。

  • 为您的 class 实施复制 and/or 移动语义,请参阅 Rule of three/five/zero。就像现在一样,复制 Array 的实例将导致两个实例认为它们拥有相同的缓冲区,并且当它们都尝试 delete 时会出现未定义的行为。

  • deletedelete 检查 null,因此析构函数中的 if(m_pData != NULL) 是多余的。

祝你好运:)