Why no push/pop in front of vector?

后端 未结 4 744
灰色年华
灰色年华 2020-12-21 08:10

In C++, STL, we have template class . We know that it supports O(1) random access, and tail modification. My question is why we don\'

相关标签:
4条回答
  • 2020-12-21 08:30

    Actually, this is a realistic requirement. To my knowledge, nothing in the standard mandates that the vector cannot have a buffer before the elements (v.prefix_capacity()), just as after (v.capacity() - v.size()). This could guarantee the same runtime for v.push_front() and v.push_back(), while not having any cost for those who don't use it. Additionally, it could guarantee O(1) v.pop_front(), albeit by invalidating iterators. Fancy writing a proposal?

    In the meanwhile, you might create a template (devector?) in terms of vector, which:

    • has a field pre_capacity_ initialized to 0 and a getter pre_capacity()
    • implements pre_reserve(size_t i) which calls both:
      • reserve(capacity() - pre_capacity_ + i)
      • pre_capacity_ += i
    • implements operator[](size_t i) and delegates to v[i + pre_capacity()]
    • implements at(size_t i) and delegates to v.at(i + pre_capacity())
    • implements begin() and delegates to v.begin() + pre_capacity()
    • delegates all the rest of the methods to vector

    Or you can just track the number of elements you've pushed to / popped from front :).

    0 讨论(0)
  • 2020-12-21 08:31

    We already have something as you describe in the STL. It is named deque.

    As you wrote there IS actually some overhead. So if you need this functionality and you have no problem with the overhead, use deque. If you do not require it, you do not want the overhead, so it is better to have something that avoids this overhead, named vector.

    And as an addition: vector guarantees that all its elements are stored in contiguous storage locations, so you can apply pointer arithmetic. This is not the case for a circular buffer.

    0 讨论(0)
  • 2020-12-21 08:32

    Implementing a de-vector introduces 2 challenges:

    • Retaining O(1) random access (unlike: deque).
    • O(1) push operation, to either side (front or back), which's the benefit of deque.

    Both are achievable, in the following manner:

    • Implement a devector class, which publicly inherits from std::vector. Hence we have the std::vector API available, and "just" need to override the methods which require overriding.
    • The inherited vector (base class) will be utilized for regular push_back insertions.
    • The devector class will also have a private std::vector member, let's refer it as front_insertions.
    • Any push_front to the devector, will perform push_back to front_insertions (the private member vector).
    • Size of devector will be the sum of both sizes: the inherited vector, and the size of front_insertions.
    • Random access to an element of index i, will be implemented in the following manner: If front_insertions is empty, we just reuse the inherited random access method as-is. Else if i < front_insertions.size() we access front_insertions[front_insertions.size() - 1 - i]. Else, we access [i - front_insertions.size()] of the inherited vector.
    • Obviously, some other std::vector methods can be overriden, such as emplace_back, emplace_front, at, etc'. I focused on retaining both O(1) random access, and push to either side.
    0 讨论(0)
  • 2020-12-21 08:50

    One explanation is that if we want to push/pop element in the front of a vector, we must shift each element in the array by one step and that would cost O(n)

    You are absolutely right, push_front has no way to run quickly, because, in addition to a possible reallocation, all items need to be copied by one position. This gives you an amortized performance of O(n2) for n objects, which is not something that library designers wanted to encourage.

    Considering that if we implement <vector> with circular array

    Implementing vector with circular array makes it a lot harder to implement several important guarantees that must be true for a vector. For example, vector must guarantee that if iterator a points to an element with a lower index than iterator b, then a < b. When vector is linear, the comparison boils down to comparing addresses of elements to which iterators a and b are pointing. With a circular array implementation one would need to take the address of the vector origin into consideration, which can now be in the middle of the allocated block of memory.

    Another guaranteed that would be violated is this:

    When v is a vector<T>, T is any type other than bool, and n a number between zero and vector's size, &v[n] == &v[0] + n identity must be true.

    This cannot be implemented with a circular array.

    0 讨论(0)
提交回复
热议问题