0

Let's create a helper class to assist visualizing the issue:

class C
{
int ID = 0;

public:
C(const int newID)
{
    ID = newID;
}

int getID()
{
    return ID;
}
};

Suppose you create an empty std::vector<C> and then reserve it to hold 10 elements:

std::vector<C> pack;
pack.reserve(10);
printf("pack has  %i\n", pack.size()); //will print '0'

Now, you assign a new instance of C into index 4 of the vector:

pack[4] = C(57);
printf("%i\n", pack[4].getID()); //will print '57'
printf("pack has  %i\n", pack.size()); //will still print '0'

I found two things to be weird here:

1) shouldn't the assignment make the compiler (Visual Studio 2015, Release Mode) throw an error even in Release mode?

2) since it does not and the element is in fact stored in position 4, shouldn't the vector then have size = 1 instead of zero?

blipblop
  • 155
  • 1
  • 12
  • Possible duplicate of [Choice between vector::resize() and vector::reserve()](https://stackoverflow.com/questions/7397768/choice-between-vectorresize-and-vectorreserve) – underscore_d Dec 06 '17 at 14:35
  • @underscore_d There's absolutely no way my question is a duplicate of that one. Not by a long shot. I know quite well what `reserve` and `resize` do. My question is about the behavior of `at` after `reserve` is called. – blipblop Dec 06 '17 at 19:36
  • _"My question is about the behavior of `at`"_ Not by a long shot. How on Earth is it about the behaviour of `.at()`, when it does not mention that method even once? Had you used `.at()`, the program would signal a clear error, instead of producing undefined behaviour. Anyway, that question *is* a dupe IMO, because your question boils down to asking whether specific ways other than `.resize()` - including `.reserve()` and indexing with `operator[]` - can increase `.size()`, and whether indexing to an unallocated point after `.reserve()` is possible, which it is not. As `.at()` would've told you – underscore_d Dec 07 '17 at 09:48

2 Answers2

2

Undefined behavior is still undefined. If we make this a vector of objects, you would see the unexpected behavior more clearly.

#include <iostream>
#include <vector>

struct Foo {
  int data_ = 3;
};

int main() {
  std::vector<Foo> foos;
  foos.reserve(10);
  std::cout << foos[4].data_; // This probably doesn't output 3.
}

Here, we can see that because we haven't actually allocated the object yet, the constructor hasn't run.

Another example, since you're using space that the vector hasn't actually started allocating to you, if the vector needed to reallocate it's backing memory, the value that you wrote wouldn't be copied.

#include <iostream>
#include <vector>

int main() {
  std::vector<int> foos;
  foos.reserve(10);
  foos[4] = 100;
  foos.reserve(10000000);
  std::cout << foos[4]; // Probably doesn't print 100.
}
Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
  • Thanks for your answer. However, I don't think it covers my actual example. In your code, you are **not** assigning a new Foo object like `foos[4] = Foo()`. If you do, then yes, it will always print `3` and yet the vector remains having the size of zero. All without the compiler throwing any errors. – blipblop Dec 06 '17 at 06:06
1

Short answers:

  1. There is no reason to throw an exception since operator[] is not supposed to verify the position you have passed. It might do so in Debug mode, but for sure not in Release (otherwise performance would suffer). In Release mode compiler trusts you that code you provide is error-proof and does everything to make your code fast.

Returns a reference to the element at specified location pos. No bounds checking is performed.

http://en.cppreference.com/w/cpp/container/vector/operator_at

  1. You simply accessed memory you don't own yet (reserve is not resize), anything you do on it is undefined behavior. But, you have never added an element into vector and it has no idea you even modified its buffer. And as @Bill have shown, the vector is allowed to change its buffer without copying your local change.

EDIT: Also, you can get exception due to boundary checking if you use vector::at function.

That is: pack.at(4) = C(57); throws exception

Example: https://ideone.com/sXnPzT

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
R2RT
  • 2,061
  • 15
  • 25