在自定义容器

时间:2016-07-03 14:00:36

标签: c++ c++11 containers

我正在设计一个这样的自定义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的指针,以免隐藏潜在的悬空指针问题。

我对如何解决这个问题感到有些困惑。

编辑:-------------------

模板容器似乎没有任何无妥协的方法。 到目前为止,我看到了这些选项,正如其他人所指出的那样:

  1. 在clear()中完全删除底层数组;这将是 安全但不高效。我不能使用这个选项,因为我是 实现一个无锁的多线程容器。
  2. 而不是在clear()中的元素上调用析构函数,我会调用默认构造函数:array [i] = C();这似乎 就像目前为止最好的解决方案 - 除了它仍然意味着额外的 默认施工。
  3. 在add()中使用placement new - 这似乎适用于复制构造。 但是,将构造转换为未初始化的对象仍然存在问题,因为大多数移动构造函数都是通过指针之间的交换实现的 - 这会使源对象(移动)无效。

2 个答案:

答案 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_;
};

在这里你可以在所有初始化元素上调用析构函数,但是你不会第二次调用它,因为你会跟踪哪些是初始化的,哪些不是。