32

Normally I call reserve on a std::vector immediately after constructing it. Wouldn't this typically cause the std::vector's existing heap allocation to be destroyed and replaced with a new one? Is there a way to reserve the memory at construction time rather than allocate heap space and then immediately destroy it? Or is there an implemenatation trick within the std::vector to ensure this is not an issue?

The available constructors only seem to be able to be useful for filling the std::vector with values, rather than reserving space explicitly.

Emil Laine
  • 41,598
  • 9
  • 101
  • 157
Richard Forrest
  • 601
  • 1
  • 7
  • 9
  • 11
    Why do you think there is any existing heap allocation on a fresh vector? – Ulrich Eckhardt Aug 07 '15 at 17:01
  • 4
    what is wrong with calling `reserve()` in the constructor's body? sounds like you are trying to do micro optimization – BЈовић Aug 07 '15 at 17:02
  • 2
    I think `std::vector v; std::cout << v.capacity();` is guaranteed to print `0`, at least [gcc does so](http://coliru.stacked-crooked.com/a/9ae2c73e77f0d5af), which means there is no heap allocation for in the *default* constructor. – Nawaz Aug 07 '15 at 17:03
  • @Nawaz not guaranteed, but it would be pretty silly if it didn't. – Quentin Aug 07 '15 at 17:07
  • @Nawaz: Technically, that does not prove anything. – Lightness Races in Orbit Aug 07 '15 at 17:09
  • @Nawaz: Ok. So the heap space is not allocated on construction, only when the first element is inserted. So it does not matter that `reserve` is a separate function and not part of the constructor. – Richard Forrest Aug 07 '15 at 17:10
  • 3
    @Richard: I'm curious as to what heap space you thought would be allocated on default-construction, how much, and why? Of course, non-default construction will perform a sensible amount of reservation according to the constructor arguments. You really don't need to be worrying about any of this. – Lightness Races in Orbit Aug 07 '15 at 17:10
  • @LightnessRacesinOrbit: I'm talking *practically*, not *technically*. – Nawaz Aug 07 '15 at 17:11
  • 2
    @Nawaz: Right, and, _practically_, your comment about `v.capacity()` being `0` does not prove anything. Zero-length allocations exist. – Lightness Races in Orbit Aug 07 '15 at 17:12
  • @LightnessRacesinOrbit: As you said yourself *"the standard library is not quite that stupid."* Please don't contradict yourself. – Nawaz Aug 07 '15 at 17:13
  • 1
    @Nawaz: I'm not contradicting myself. I'm contradicting your logical fallacy: _"`std::vector v; std::cout << v.capacity();` is guaranteed to print `0`, at least gcc does so, which means there is no heap allocation for in the default constructor."_ It means no such thing. – Lightness Races in Orbit Aug 07 '15 at 17:14
  • @LightnessRacesinOrbit: there is an assumption *"the standard library is not quite that stupid"*, so if that is correct, then what I said is true. – Nawaz Aug 07 '15 at 17:16
  • @LightnessRacesinOrbit: Presumably, inserting the first element into the vector causes the first heap memory to be allocated. The size of this chunk is probably considerably more than is required for this single element though. The `vector`'s heap initialization and growth strategy is presumably implementation dependent. – Richard Forrest Aug 07 '15 at 17:18
  • @RichardForrest: That is correct (take a look at http://stackoverflow.com/q/5404489/560648). You didn't answer my question, but never mind. – Lightness Races in Orbit Aug 07 '15 at 17:23
  • 1
    @Nawaz: There's a flaw in your argument there. If we start with the premise that "the standard library is not quite that stupid", then you do not need to invent the false correlation between `.capacity()==0` and zero allocations being performed, because we have already decided that default-construction will result in zero allocations being performed, for reasons of sanity. So the entire comment is vacuous. :) – Lightness Races in Orbit Aug 07 '15 at 17:24
  • A typical implementation would not have any problem with that. If you don’t allocate anything else in between, there should be almost no overhead to reallocating a larger or smaller block immediately. The same block will normally fit, and just be resized, but if not, it will just free that one and look for a new one without needing to copy the contents. – Davislor Feb 11 '18 at 06:23

4 Answers4

23

Your question is based on a false premise, namely that a default-constructed std::vector<T> will perform a [zero-length] allocation.

There is literally no reason for it to do so. A fresh vector should have capacity zero (though this is required by sanity, not by the standard).

As such, your goal is already inherently satisfied.

To be blunt, the standard library is not quite that stupid.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 3
    +1. As for the last sentence: I would even say, that standard library (especially its 11+-related part) is designed and implemented very cleverly. I cannot understand why so many people think, that they can do so much better and try to write everything from scratch... I'm getting sick when I see another *"I'm writing a game engine and this is my custom vector/string/whatever. Why it doesn't work?"*... – Mateusz Grzejek Aug 07 '15 at 17:14
  • @MateuszGrzejek: Aside from `boost::bimap` (which I find completely invaluable) I can't remember the last time I solved a storage problem using anything not from the standard library. :) – Lightness Races in Orbit Aug 07 '15 at 17:15
  • Ah, my bad: http://stackoverflow.com/questions/8190950/may-stdvector-make-use-of-small-buffer-optimization – ecatmur Aug 07 '15 at 17:17
  • Accepted. Thanks. I now understand the `std::vector` a little better and will stop worrying and just call `reserve`. – Richard Forrest Aug 07 '15 at 17:21
  • @RichardForrest: Have fun! – Lightness Races in Orbit Aug 07 '15 at 17:24
  • 2
    *"Your question is based on a false premise, namely that a default-constructed std::vector will perform a [zero-length] allocation."*. How do you know if that is a false premise? The specification doesn't say anything about allocation in the default constructor. So **claiming** either way is wrong. – Nawaz Aug 07 '15 at 17:37
  • 1
    @Nawaz: It's the use of the word "will" (I realise he didn't use it, but it was essentially implied in his wording). If he'd posited the assumption as a "may" then it would be less of a "false premise" and more of a "pessimistic expectation" :) Either way I'm astounded that you'd go so far as to actually downvote this. – Lightness Races in Orbit Aug 07 '15 at 18:32
  • *"A fresh vector should have capacity zero"* . Zero capacity doesn't prove anything, as zero-length allocation is still possible. – Nawaz Aug 08 '15 at 04:30
  • @LightnessRacesinOrbit: Just trying to understand my own comments from *you*. ;-) (We're saying the same thing, yet you disagree with me). – Nawaz Aug 08 '15 at 14:04
  • @LightnessRacesinOrbit: Well, all I want to say is that we're saying the same thing, except that you've put it as an answer (and thus you've added more words to make it sound logical and convincing) whereas I put it as comment (and thus didn't add much words). – Nawaz Aug 08 '15 at 16:20
  • @LightnessRacesinOrbit: So please explain what does *"A fresh vector should have capacity zero"* prove? If it does not prove "no allocation", what is the point of saying it? – Nawaz Aug 08 '15 at 17:00
  • https://stackoverflow.com/questions/48744449/is-it-guaranteed-that-stdvector-default-construction-does-not-call-new you can't be sure – interesting Sep 28 '22 at 18:06
3

The reason may be ironic, we are running out of function signature.

The requirement comes from the using scenario that we exactly know how many elements we will save into vector but we really really don't like the n-duplicated elements constructor:

std::vector( size_type count, const T& value = T())

Unfortunately the signature is occupied by the above constructor. Any other possible signature may cause issue. E.g.

  1. vector(size_type count, size_type reserve_count) will conflicts with above n-duplicated elements constructor for vector of T(size_type).

  2. vector(size_type count, const T& value = T(), size_type reserve_count) is a possible solution but it is too long and still boring. We need to construct a default value we never use while calling auto v = vector<T>(0, T(), reserve_count)

Other feasible solutions:

  1. Provides a function like make_pair/make_unique.

  2. Defines a Reserver which derived from Allocator, so we can use the constructor vector( const Allocator& alloc ) like

auto v = vector<Type>(new Reserver(reserve_count));

user3483203
  • 50,081
  • 9
  • 65
  • 94
expect00
  • 31
  • 2
0

If the number of elements is known at compile-time by you, you can list-initialize the vector with such elements:

 class MyClass 
 {
      int x, y, z;
      std::vector<int> v;

  public:
         MyClass(int X, int Y, int Z) : x(X), y(Y), z(Z), v{x, y, z}
         {}
 };

But this isn't very nice to maintain. There are more advanced techniques, such as custom allocators that you can make std::vector to use that could take memory from a pre-allocated memory pool, for instance: I doubt you really need that, however. Modern implementations would optimize a so simple problem easily.

edmz
  • 8,220
  • 2
  • 26
  • 45
0

The object std::vector and its array of elements don't exist on the same contiguous memory block, otherwise its address would be changing every time you resize the array, making it impossible to keep a reliable reference of it. The object's main body only contains control variables and a pointer to the actual array, and it will be instanced in the stack (assuming you are using it as a local variable) along with the other local variables. And at this time the array will be empty, and probably represented by a pointer to nullptr. So it doesn't matter if you reserve at construct time or right after it, there will be no significant optimization happening.

If you want a std::vector with static size that is reserved instantly, you can just use a regular C array instead of std::vector. Just make sure it fits in the stack.

JDługosz
  • 5,592
  • 3
  • 24
  • 45
Havenard
  • 27,022
  • 5
  • 36
  • 62