模板化中的析构函数实现 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_ptr
或 std::shared_ptr
,或其他一些 RAII 包装器。
这就是 std::vector
的基本情况,如果您想在向量中存储指向 T 的拥有指针,则可以使用 vector<unique_ptr<T>>
或 vector<shared_ptr<T>>
。
请注意,在您的容器中存储原始 observing 指针是可以的(只要正确处理指向对象的生命周期)。
补充几点:
您的 class 应该禁止通过 =delete
复制构造函数和复制赋值进行复制,或者正确地实现这些复制操作。由于它处于当前状态,如果人们试图复制您的 class.
的构造或复制分配实例,它很容易出错
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 错误:你应该只给 new
ed T
s 一个 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
时会出现未定义的行为。
delete
和 delete
检查 null,因此析构函数中的 if(m_pData != NULL)
是多余的。
祝你好运:)
如果我有这样的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_ptr
或 std::shared_ptr
,或其他一些 RAII 包装器。
这就是 std::vector
的基本情况,如果您想在向量中存储指向 T 的拥有指针,则可以使用 vector<unique_ptr<T>>
或 vector<shared_ptr<T>>
。
请注意,在您的容器中存储原始 observing 指针是可以的(只要正确处理指向对象的生命周期)。
补充几点:
您的 class 应该禁止通过
=delete
复制构造函数和复制赋值进行复制,或者正确地实现这些复制操作。由于它处于当前状态,如果人们试图复制您的 class. 的构造或复制分配实例,它很容易出错
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 错误:你应该只给 new
ed T
s 一个 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
时会出现未定义的行为。delete
和delete
检查 null,因此析构函数中的if(m_pData != NULL)
是多余的。
祝你好运:)