Can pop_back() ever reduce the capacity of a vector? (C++)
Asked Answered
D

4

11

According to the C++ standard, is std::vector<T>::pop_back() ever allowed to reduce the capacity of the vector?

I am asking because I would like to have a guarantee, that the following code will not throw an out of memory exception:

my_vec.pop_back();
if (...)
    my_vec.push_back(...);

Assume that my_vec is an std::vector<int>.

I guess there are three possibilities:

  1. Yes, this can happen according to both C++03 and C++11.

  2. No, C++11 prohibits this (but C++03 does not).

  3. No, both C++03 and C++11 prohibits this.

Yes, my question is related to Does std::vector.pop_back() change vector's capacity?, but my question is specifically about what the standard guarantees.

Note also that the accepted answer in Does std::vector.pop_back() change vector's capacity? is mostly about how to reduce the capacity of a vector, not about when it is guaranteed not to happen, and offers no evidence for its claim about pop_back().

Durwood answered 20/5, 2014 at 16:0 Comment(7)
'... that the following code will not throw:' Why should it throw? Because 'out of memory'?Interpenetrate
@πάνταῥεῖ Yes, because of a failure to allocate memory.Durwood
push_back involves a move or a copy. If it's a copy, and the copy constructor for the value_type allocates memory, it could throw a std::bad_alloc exception, even though the vector itself still has capacity.Goy
@AdrianMcCarthy True, but that is not what I am concerned about here. For simplicity, assume that is is a std::vector<int>. I have now edited my example to make this clear.Durwood
Why should this question be reopened? The Q&A linked as dupe, well contains all the info that can be found here.Interpenetrate
@MooingDuck If it's a dupe it's a dupe, isn't it?Interpenetrate
@πάνταῥεῖ: Ah, I missed the context, I thought you were promoting reopening it. I realize you meant it should not be reopened. I agree with you.Ferromagnetic
V
8

According to http://en.cppreference.com/w/cpp/container/vector/pop_back

No iterators or references except for back() and end() are invalidated.

Therefore it may not reallocate. There is no C++11 tag on that page, which implies this is also true in 03. I will dig up the section references and edit them in for completeness.

Edit: Even better: From C++03: [lib.container.requirements] (23.1), paragraph 10:

no erase(), pop_back() or pop_front() function throws an exception.

Same wording at 23.2.1/10 in N3337 (~C++11).

Virginia answered 20/5, 2014 at 16:3 Comment(5)
But the requirement, that allocators are not allowed to be invalidated, does not guarantee "no change in capacity", right? For example, an implementation could attempt a realloc() and thereby retain the base address, I guess.Durwood
Well bear in mind that it has to go through the allocator, and std::allocator doesn't have any kind of reallocate member.Virginia
By "has to go through the allocator", do you mean that the standard demands that? Would it not be possible for a compliant implementation to use realloc() if the allocator is the default one?Durwood
It would be possible for an implementation to specialise for a specific allocator, but would be odd. But see my above edit anyway.Virginia
The "no exceptions" requirement does not say much, really. For example, you could call realloc() and check if you got the same base address. Please note that I am not interested in what would be likely or odd, only in what the standard effectively requires.Durwood
B
7

No. The only way to shrink a vector's capacity is the swap trick, as shown here. And also the C++11 way I mention bellow.

Also, as the ref says:

Removes the last element in the vector,
effectively reducing the container size by one.

In other words, it changes the size of the vector and not its capacity.

Take a look at the iterator validity:

The end iterator and any iterator, pointer and reference
referring to the removed element are invalidated.
Iterators, pointers and references referring to other
elements that have not been removed are guaranteed to keep
referring to the same elements they were referring to before the call.

In C++11 you could use std::vector<>::shrink_to_fit() in order to change the capacity (for more see the 1st link). (tnx Psyduck). Interesting comments below the answer, but this question is not about the above method, so if interested, read the comments.

Notice that even this method is not guaranteed to reduce the capacity, as the ref says:

Requests the container to reduce its capacity to fit its size.

The request is non-binding, and the container implementation is free to optimize otherwise >and leave the vector with a capacity greater than its size.

This may cause a reallocation, but has no effect on the vector size and cannot alter its >elements.

It would be too strange that this function is not guaranteed to reduce capacity and pop_back does, while the ref of the second does not mentions anything relevant.

The way I see it, since the ref does not mention capacity, this means that it's not needed to, which means, that capacity remains the same.

An interesting example is this:

#include <iostream>
#include <vector>

int main() {
  const int N = 1000000;

  std::vector<int> v1;
  v1.reserve(N);
  for (int i = 0; i < N; ++i) {
    v1.push_back(i);
  }

  std::cout << v1.capacity() << " and size = " << v1.size() << std::endl;

  for (int i = 0; i < N - 2; ++i) {
    v1.pop_back();
  }

  std::cout << v1.capacity() << " and size = " << v1.size() << std::endl;

  return 0;
}

Output:

1000000 and size = 1000000
1000000 and size = 2

where capacity is clearly not reduced.

[EDIT]

Another relevant question, which could also be flagged as a duplicate has some good answers. Here are some interesting ones:

1)

Go look at Scott Meyers Effective STL item 17. (some ref that the OP looks) Basically you can't directly reduce the storage size of a std::vector. The "trick" is to > create a new container of the right size, copy the data and swap that with the current container.

2)

No, you cannot reduce the capacity of a vector without copying.

3)

I'm not saying that GCC couldn't have some method for doing what you want without a copy, > but it would be tricky to implement (I think) because vectors need to use an Allocator object to allocate and deallocate memory, and the interface for an Allocator doesn't include a reallocate() method. I don't think it would be impossible to do, but it might be tricky.

I suggest reading the link for more.

[EDIT.2]

This question also supports that:

Q: Can pop_back() ever reduce the capacity?

A: NO.

When you can't rely on the appropriate methods for reducing capacity (in terms of what you read in the standard), you can't expect pop_back() to do something like that.

Bodnar answered 20/5, 2014 at 16:3 Comment(15)
As of C++11 there's another way, a member, shrink_to_size or somethingFerromagnetic
@MooingDuck shrink_to_fit, but it's a non-binding request, so not guaranteed to shrink,Foxtrot
Edited @MooingDuck (aka Psyduck, wonder why your name is not Psyduck). It was written in the ilnk, that's why I din not write it from the first moment.Bodnar
Recently, I did a question on shrink_to_fit please take a look at the answers #23502791Smutch
@40two 's link is a follow up of his answer here #23496773Bodnar
@G.Samaras I do not see how the "no iterator invalidation" requirement implies "no reduction in capacity". Can you elaborate on why this must be so?Durwood
My logic is not different from the one said in the other answer @KristianSpangsege. However, notice that my answer differs from the other one, in the reasons of why the answer is a negative one. The 1st link I provided also agrees with that. Even if you are not convinced by the iterator.., I think the rest would suffice.Bodnar
@G.Samaras It seems to me that both of your arguments are about what would be expected behavior from an implementation, but that is not what I am interested in here. Also, it still seems to me that there could easily be implementations that actually reduce capacity during pop_back(), for example by using realloc() or a similar system specific memory allocation primitive.Durwood
@KristianSpangsege: realloc can change the address of the buffer. that would invalidate pointers. invalidating pointers is not permitted.Liles
@Cheersandhth.-Alf True, but in that case, the implementation could "back out". Also, specific platforms could have other functions similar to realloc() that would refuse to allocate new memory, but still be able to reduce an existing allocation.Durwood
@Cheersandhth.-Alf Ah, you are right, realloc() won't work, but there could still be other platform specific alternatives that would.Durwood
@KristianSpangsege I am really sorry that I can not provide you by what you are really looking for, but I made an interesting example, which gives support to my answer. Also, notice that the other answer I have linked to also supports the same and so many people there agree to that. But yes, I see your point and why you are still discussing.Bodnar
@KristianSpangsege, I think I got what you need! See my edit!Bodnar
@G.Samaras Thanks for the effort, but none of those items actually shed further light on my particular question, "can pop_back() ever reduce capacity?".Durwood
@KristianSpangsege, you are welcome. I enjoyed it, since I wasn't 100% sure for the answer of your question. So your question made a little better programmer! :D I posted a final attempt to convince you (see my edit).Bodnar
D
2

The comments are not really addressing it. Clearly, if std::vector is prohibited to use anything except std::allocator, and if std::allocator is prohibited to be extended with additional methods, then resizing with same base address is impossible, which makes it impossible to decrease capacity because iterators would be invalidated.

Closest info I could find about reallocation was a stackoverflow comment on Why is there no reallocation functionality in C++ allocators? saying

"There is nothing stopping std::vector from doing that in some cases (e.g., it knows its using the standard allocator). The standard library is allowed to use knowledge of the underlying system. – KeithB Jun 23 '10 at 21:39" (no references mentioned, though)

There have been submitted ideas for adding realloc to std::allocator, but they have both been rejected:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1953.html

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2045.html

The papers don't explicitly state that std::allocator is prohibited to extend std::allocator, though - they only state that it doesn't need to. They also don't explicitly state that std::vector is prohibited to use API calls to the underlying system... So no real info there either.

Disprize answered 21/5, 2014 at 8:41 Comment(0)
J
-1

You can easily execute a test like below:

#include <iostream>
#include <vector>
using namespace std;

class Solution {

    public:
        vector<int> vc;
};

int main() {

    Solution check;

    for (int i = 0; i < 100; i++) {

        cout << "Round(push) = " << i << endl;
        cout << "before size: " << check.vc.size() << endl;
        cout << "before capacity: " << check.vc.capacity() << endl;
        check.vc.push_back(i);
        cout << "after size: " << check.vc.size() << endl;
        cout << "after capacity: " << check.vc.capacity() << endl;
        cout << endl;
    }

    for (int i = 0; i < 100; i++) {

        cout << "Round(pop) = " << i << endl;
        cout << "before size: " << check.vc.size() << endl;
        cout << "before capacity: " << check.vc.capacity() << endl;
        check.vc.pop_back();
        cout << "after size: " << check.vc.size() << endl;
        cout << "after capacity: " << check.vc.capacity() << endl;
        cout << endl;
    }
    
    return 0;
}

As the output result shows, there are some find out:

  1. While a push_back() function be triggered, the capacity will be increased, follow the rule like 0 -> 1 -> 2 -> 4 -> 8, ……
  2. While a pop_back() function be triggered, the capacity will keep in the same size (i.e. the capacity will be 128 when all 100 times push_back are done, and after all 100 times of pop_back() are done, the capacity will still keep in 128, by the test from code above)
Jewess answered 20/7, 2023 at 16:45 Comment(1)
No, testing an implementation only tells you what behaviour that implementation chose, not whether the standard guarantees it for all conforming implementations.Parclose

© 2022 - 2024 — McMap. All rights reserved.