【发布时间】:2016-11-05 08:35:12
【问题描述】:
我正在设计一个这样的自定义 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 的指针,以免隐藏潜在的悬空指针问题。
我对如何处理这个问题有点困惑。
编辑:-------
对于模板容器似乎没有不折不扣的方法。 到目前为止,我看到了这些选项,正如其他人指出的那样:
- 在clear()中彻底删除底层数组;这将是 安全但不高效。但是我不能使用这个选项,因为我 实现无锁多线程容器。
- 我不会在 clear() 中调用元素的析构函数,而是调用默认构造函数:array[i] = C();这似乎 就像迄今为止最好的解决方案一样 - 除了它仍然意味着额外的 默认构造。
- 在 add() 中使用新位置 - 这似乎适用于复制构造。 但是,移动构造到未初始化的对象似乎仍然存在问题,因为大多数移动构造函数都是通过指针之间的交换来实现的——这会使源对象(被移动)无效。
【问题讨论】:
-
只是出于好奇,您如何定义 Container
::add?您是否有预分配的未初始化单元格,还是每次都重新分配整个数组? -
@Necto,我有一个预分配的未初始化单元格数组,并使用复制构造或移动构造(取决于如何调用 add )。如果我没有遗漏什么,就像 std::vector 一样
-
你如何分配那个数组?
-
array = new C[容量];所以数组包含 C 类型的对象,这就是诀窍。当它们被破坏时,它们仍然存在,并且它们的 = 运算符或复制/移动构造函数仍然被调用
-
@BogdanIonitza
newnot 分配未初始化的元素。我猜你的意思是 default-constructed。两者是截然相反的概念。我明白你的意思,但我们都知道在使用 C++ 中的术语时需要多么小心!
标签: c++ c++11 containers