需要一个带有O(1)擦除的std :: vector

时间:2012-04-14 20:27:38

标签: c++ inheritance memory-leaks composition stdvector

我很惊讶地发现调用擦除时的vector :: erase移动元素。我认为它会将最后一个元素与“待删除”元素交换,并将大小减小一个。我的第一反应是:“让我们扩展std :: vector并覆盖erase()”。但我发现许多线程如“Is there any real risk to deriving from the C++ STL containers?”,它可能导致内存泄漏。但是,我没有向vector添加任何新的数据成员。因此没有额外的内存可以释放。还有风险吗?

有些人认为我们应该更喜欢构成而不是继承。在这种情况下,我无法理解这个建议。为什么我要浪费我的时间在“机械”任务中包装其他精彩的std :: vector类的每个函数。继承确实对这项任务最有意义 - 或者我错过了什么?

4 个答案:

答案 0 :(得分:11)

为什么不写一个符合你想要的独立功能:

template<typename T>
void fast_erase(std::vector<T>& v, size_t i)
{
   v[i] = std::move(v.back());
   v.pop_back(); 
}

尽管如此,Seth Carnegie的所有功劳。我最初使用“std :: swap”。

答案 1 :(得分:4)

精致的问题。您要破解的第一条准则是:“Inheritance is not for code reuse”。第二个是:“不要继承标准库容器”。

但是:如果你可以保证,没有人会使用你的unordered_vector<T> vector<T>你是好的。但是,如果有人这样做,结果可能是不确定的和/或可怕的,无论你有多少成员(它可能看起来很完美,但仍然是未定义的行为!)。

您可以使用private继承,但这不会让您无法使用 lot using语句编写包装器或拉动成员函数,这几乎与代码作为组合(尽管少了一点)。


编辑:我对using语句的意思是:

class Base {
  public:
    void dosmth();
};

class Derived : private Base {
  public:
    using Base::dosmth;
};

class Composed {
  private:
    Base base;
  public:
    void dosmth() {return base.dosmth(); }
};

您可以使用std::vector的所有成员函数执行此操作。正如您所看到的,Derived的代码明显少于Composed

答案 2 :(得分:3)

继承的风险在以下示例中:

std::vector<something> *v = new better_vector<something>();
delete v;

这会导致问题,因为您删除了指向没有虚析构函数的基类的指针 但是,如果您总是删除指向您的类的指针,如:

better_vector<something> *v = new better_vector<something>();
delete v;

或者不要在堆上分配它没有危险。只是不要忘记在析构函数中调用父析构函数。

答案 3 :(得分:2)

  

我认为它会将最后一个元素与“待删除”交换掉   元素并将大小减小一个。

vector :: erase维持元素的顺序,同时将最后一个元素移动到已擦除的元素,并将大小减小一个。由于vector实现了数组,因此没有O(1)方法来维护元素的顺序并同时擦除(除非你删除最后一个元素)。

如果维护元素的顺序并不重要,那么你最好使用其他容器(例如 list ,它实现双向链表)。