我正在设计一个这样的自定义C ++模板容器:
template <class C>
class Container {
public:
...
void clear() {
// should I call destructor on elements here?
for (i...)
array[i].~C(); // ?
}
~Container() {
delete [] array_;
}
private:
C* array_;
};
我应该在clear()中手动调用元素上的析构函数吗?如果我不这样做,它们将在容器被销毁之后被调用(因为我在析构函数中删除了[] array_),但这不是预期的行为(我们希望它们在clear()内被销毁,只是像std :: vector一样)。 但是,如果我确实调用析构函数,那个内存仍然存在,当我将在旧的元素(现在被销毁)之上添加新元素时,将在这些被破坏的元素上调用赋值运算符,这可能会导致在未定义的行为。 假设我有一个班级:
class Foo {
public:
Foo() { foo_ = new int; }
~Foo() { delete foo_; } // note I don't explicitly set foo_ to nullptr here
Foo(Foo &&f) {
f.foo_ = std::exchange(foo_, f.foo_); // standard practice in move constructors
}
};
好的,到目前为止这看起来不错,但现在如果我做了
Container<Foo> c;
c.add(Foo());
c.clear();
c.add(Foo());
当调用clear()时,调用初始Foo的析构函数,使其foo_指针悬空。接下来,当添加第二个Foo时,临时R值与被破坏对象的旧内容交换,当temp被销毁时,它的析构函数将尝试再次删除相同的悬空指针,这将崩溃。
那么,如何正确清除()容器而不留双删除空间? 我还读到它建议不要在析构函数中设置指向nullptr的指针,以免隐藏潜在的悬空指针问题。
我对如何解决这个问题感到有些困惑。
编辑:-------------------
模板容器似乎没有任何无妥协的方法。 到目前为止,我看到了这些选项,正如其他人所指出的那样:
答案 0 :(得分:3)
delete [] array_
将为array_
的每个元素调用析构函数(假设C
是具有析构函数的类型)。
在任何给定对象上调用两次析构函数会产生未定义的行为。
这意味着您的clear()
成员函数不应直接调用数组元素的析构函数。
如果你坚持使用一个单独的clear()
函数,它不会干扰析构函数的工作,只需将其实现为
void clear()
{
delete [] array_;
array = nullptr; // NULL or 0 before C++11
}
这不会干扰析构函数,因为如果作用于NULL指针,则运算符delete
无效。
答案 1 :(得分:0)
正如您在注释中指定的那样,在未分配的单元格中,您拥有由默认构造函数(T()
)构造的对象。这可能对性能有害,但肯定会保留对象抽象。
不要删除条目,只需将它们替换为默认构造函数构造的条目:
template <class C>
class Container {
public:
...
void clear() {
for (i...)
array_[i] = C();
}
~Container() {
delete [] array_;
}
private:
C* array_;
};
另一方面,我会提出一种更高效的方法,尽管它打破了很好的C ++抽象。您可以在不调用不必要的默认构造函数的情况下分配内存:
template <class C>
class Container {
public:
Container(int n) {
array_ = (C*)malloc(sizeof(C)*n);
size_ = 0;
}
...
void clear() {
for (... i < size_ ...)
array_[i].~C();
size_ = 0;
}
~Container() {
clear();
free(array_);
}
private:
C* array_;
int size_;
};
在这里你可以在所有初始化元素上调用析构函数,但是你不会第二次调用它,因为你会跟踪哪些是初始化的,哪些不是。