-2

Lets say I want to create a vector and call iota on it:

std::vector<int> v(1000);
std::iota(begin(v),end(v),0);

The vector is actually 0-initialized in the constructor. The problem would be the same with:

std::vector<int> v;
v.resize(1000);

I could use reserve, but then I can't call iota. And I see no elegant way to not zero-initialize (maybe with a custom allocator...?).

Why was vector designed in such a way? I know that Stepanov and collaborators very carefully crafted the STL and there is very few flaws. But to me, it is one in the sense that it seems to conflict with "you don't pay for what you don't use". What was the motivation of such a design?

Is there a consensus that retrospectively, it should not have been done like that? (e.g. as it is the case with the unsigned size_t used for size() where people seem to agree that it should have been the same type as difference_type because we are comparing indices and size() so often).

More information

Performance

Some comparisons regarding the generated code. Does the compiler optimize the 0-init away? It is difficult to look at the assembler when using vector because it does a lot of stuff so I tried this code:

int main() {
    int* v = new int[1000]; // clearer for reading assembly
    fill(v,v+1000,0);
    iota(v,v+1000,0);
    return v[999]; // do not optimize away!
}

Result here. Then with std::fill removed

Simple enought right? Well the assembly is not the same with gcc or clang-O3, and it is longer with std::fill, which generally indicates that instructions are actually issued.

So unless I am misinterpreting, it does matter.

History

The code of the original STL can be found here. In version 2, the content of the vector was already default-initialized. It seems like the commitee validated what was already there.

Comparison to std::array

Some people in the comment believe that you absolutely must initialize the content of the vector. But the proof that this is really a design decision is the fact that std::array, on the contrary, does not default-initialize its memory. So maybe it is easier, or makes more sense, to implement vector and array the way they are, but the other way around would have been possible, for both.

Bérenger
  • 2,678
  • 2
  • 21
  • 42
  • 1
    I removed the `stl` tag since it looks like you're using the C++ standard library. See [What's the difference between “STL” and “C++ Standard Library”?](https://stackoverflow.com/q/5205491/11082165) and the `stl` tag description. – Brian61354270 Aug 25 '21 at 21:52
  • 1
    Thanks but I put it again because AFAIK the design choice was made by the STL before being standardized so I think it makes sense to have the historical perspective as well – Bérenger Aug 25 '21 at 21:58
  • I've closed the question with targets that explain how you can do what you would like. I take it that's ok with you? If you really want to know *why* vector is this way, I wouldn't mind reopening the question (it might be opinion based that way, but the duplicate closure at least wouldn't apply). – cigien Aug 25 '21 at 22:02
  • 2
    That was not my question. I am asking *why*, not how. The other questions don't tell why it was done this way. Is it possible to re-open ? – Bérenger Aug 25 '21 at 22:03
  • 1
    Hopefully some day we'll see a *The Continuing Evolution of C++* from Stroustrup (and likely a few others) that explains the rational behind these decisions, but it probably stems from the murk around whether or not a fundamental or POD type in a memory block that was just `malloc`ed is really real and not UB that will "always work". – user4581301 Aug 25 '21 at 22:05
  • A "Why was this decision made?" question is next to impossible to answer to Stack Overflow's expected level without the input from the committee members that made the decision or the minutes for the meeting where this was decided. Though you're right, in this case it might require an appeal to Stepanov , et al. Anyway, it's probably doomed to being closed as unanswerable, but I don't really like the duplicate. – user4581301 Aug 25 '21 at 22:09
  • @user4581301 Yes. Stepanov has mentioned many decisions he made in the STL (A9 videos...), it is possible that he talked about that somewhere. Anyway, I would I least like to know if it is considered a good decision... You seem interested too, you can click the "reopen" button to upvote reopening (didn't know it existed) – Bérenger Aug 25 '21 at 22:14
  • I've reopened the question, since the target isn't appropriate if you're really asking *why*. As I mentioned, now it's probably an opinion based question. BTW, you should have pinged me directly and it would have avoided another user having to cast a reopen vote. – cigien Aug 25 '21 at 22:22
  • The only reason I haven't voted to reopen is I've managed to acquire enough pull to automatically reopen the question *without* voting on it similar to how cigien could instantly close it, and I'm not 100% sure I should summarily reopen it. Same reason I didn't close it myself. Moot point now. – user4581301 Aug 25 '21 at 22:23
  • You can get a range with C++20 `views::iota` and use the constructor of `vector` that takes iterators. – Marc Glisse Aug 25 '21 at 22:26
  • I've googled up a bunch of potential duplicates, but all of the answers they've received are, my opinion, not worthy answers. Only a couple crappy enough to deserve a downvote, but none answer the question. – user4581301 Aug 25 '21 at 22:27
  • @cigien sorry I rarely get my questions closed I didn't know what to do I panicked :D Thanks! – Bérenger Aug 25 '21 at 22:27
  • @MarcGlisse Yes it would work in this particular case. But it doesn't seem natural, plus in reality, except for standard algorithms, their is a good chance that there is no range version – Bérenger Aug 25 '21 at 22:32
  • An optimizing compiler could eliminate the unnecessary 0-fill when the vector is immediately overwritten. Doesn't yours do that? – rustyx Aug 25 '21 at 22:35
  • 1
    I think the thing that's missing is `std::iota_n`, because then you could pass a `std::back_inserter` pointing to a vector that you reserved – Caleth Aug 25 '21 at 22:41
  • Meh. You're asking for a "consensus" answer ... that strikes me - at face value - as "opinion based" ... but I'm willing to wait a wee while before casting my close vote ... – Adrian Mole Aug 25 '21 at 22:45
  • @AdrianMole I don't even see why this would be opinion-based. There is a clear disadvantage of initializing when it is not required. I guess one advantage is that 0 is "better" than a random number for some definition of "better" but since C++ does not do it by default to ints, what it the point to doing it to vectors of ints. Even if it is opinion-based, I think it is interesting to know the pros and cons. – Bérenger Aug 25 '21 at 22:53
  • @Bérenger I've mentioned this in [chat](https://chat.stackoverflow.com/transcript/message/52901372#52901372). I'm not voting either way (for now) .... – Adrian Mole Aug 25 '21 at 22:57
  • @rustyx I have learned to not trust compilers. I actually looked at it, see the edited answer. Did you manage to make your compiler optimize it? – Bérenger Aug 25 '21 at 23:25
  • The only people for whom this would not be opinion are those who know what happened at the meeting that made this decision. Everyone else is, at the very least, making guesses. Now-a-days all the decisions get logged and more or less publicly debated, but back in '97 when this was all going down I was writing embedded C and a wee bit of Java. I'd taken courses on C++ that I later found weren't worth half a smurf, but that was it. Without eyes on the community I have no clue what sort of records were left. – user4581301 Aug 26 '21 at 00:37
  • @user4581301 It was already like that for STL 2.03 (1996 ?). It can be downloaded here http://www.rrsd.com/software_development/stl/stl/download.html – Bérenger Aug 26 '21 at 00:48
  • STL != Standard Library. STL was a huge influence, but at the end of the day the Standard committee had to sign off on the wording of the Standard even if the decision was, " it. Let's just do what STL did." – user4581301 Aug 26 '21 at 00:54
  • I know it is different, but I am just saying that the behavior was not changed by the standard commitee, it was there before (e.g. unlike vector iterators that were just pointers) – Bérenger Aug 26 '21 at 00:58
  • What you describe was true before C++11, but is untrue from C++11. Before C++11, `vector` had a constructor `vector(size_type, const T&value = T(), const Allocator & = Allocator())` which behaves as you describe. In C++11, `value` no longer has a default value, and there is an additional constructor `vector(size_type)` which default-inserts the contents (so leaves elements like `int` unitialised). C++20 tweaked again and made the latter constructor into `vector(size_type, const Allocator & = Allocator())`. – Peter Aug 26 '21 at 01:36
  • @Peter No it does the same thing with C++11. Else it would have broken people relying on default initialization. An you can test that indeed the vector is filled with 0s, not random numbers. Plus, see the answer be Paul Sanders on how default-insertion works. – Bérenger Aug 26 '21 at 08:02

3 Answers3

3

Actually, you can avoid this. To quote from cppreference for, for example, resize:

additional default-inserted elements are appended

And the page discussing C++ named requirements: DefaultInsertable says (emphasis mine):

If value-initialization is undesirable, for example, if the object is of non-class type and zeroing out is not needed, it can be avoided by providing a custom Allocator::construct

Finally, a link back to Stack Overflow (!) shows how this can be done (I have not analysed this code in any detail).

Of course, for non-POD types you do want the new elements to be default constructed. Otherwise, accessing them (or even trying to assign to them) would cause UB.

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
  • I did not look at it in detail, but it seems that the vector type should then be e.g. `std::vector` right? If so, it is heavyweight (can't use normal vector anymore!) but at least it solves the problem. – Bérenger Aug 26 '21 at 00:56
  • Yes, you pass the custom allocator as the last template parameter to `std::vector`. I would not describe this as 'heavyweight' though - I don't think it costs much. – Paul Sanders Aug 26 '21 at 01:04
  • Yes not heavyweight in the sense that the code is more costly, but because then I use another vector type, which mean I may have to adapt all interfaces that where not templated on the allocator – Bérenger Aug 26 '21 at 07:44
2

The various properties of std::vector are designed for specific use cases, and to meet certain requirements. Some of those requirements are:

  1. Contiguous memory
  2. Constant amortized time complexity for push_back
  3. Random access iterators

And perhaps a few others.

There are other containers in the C++ library that have other properties and are optimized for other use cases, like std::list or std::queue. This is why there are different containers: they are optimized for different situations. std::vector requires a copyable/movable type. Other containers do not. If you have a non-copyable/movable type you cannot use std::vector at all.

No claim is made that either std::vector, or any other container, will provide optimal results in every possible situation. There is no jack-of-all-trades container in the C++ library that does everything better than everything else. This is why different containers exist, and a key factor in using them correctly is understanding what each container is good for.

You've described several situations where std::vector is not going to produce optimal results. This does not indicate a flaw in std::vector's design, it is simply not what it's intended to do.

Why was vector designed in such a way?

It was designed in order to provide optimal results in specific use cases.

What was the motivation of such a design?

The motivation for such a design was, primarily so that std::vector provides optimal results for:

  1. Contiguous memory
  2. Constant amortized time complexity for push_back
  3. Random access iterators

Is there a consensus that retrospectively, it should not have been done like that?

No, actually quite the opposite. The current consensus is that std::vector gives surprisingly good results (when used correctly) with modern CPU hardware which excels with contiguous memory access, even when compared when other containers that would, at first glance, provide a better match for certain use cases.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • OK lets say that I want a container that does exactly the same as `std::vector` except I don't want it to initialize data. What should I use? – Bérenger Aug 25 '21 at 23:26
  • For me, the problem is that if it was the other way around, that is, the vector would not default construct elements and I wanted it to, I can always call `std::fill`. Whereas the way it is now, I have no choice! – Bérenger Aug 25 '21 at 23:28
  • Sorry, but C++ does not work this way. In C++, when an object is created it ***must*** be constructed. This is fundamental to C++. If a container contains ***something***, whatever something is must be constructed. Otherwise there cannot be anything in the container. What is the ***real*** problem are you trying to solve? No, not the one about a container with "unintialized" objects, but the problem to which you believe the solution is a container with uninitialized objects, so that's what you're asking about. – Sam Varshavchik Aug 25 '21 at 23:39
  • Oh yes it does work this way! It works this way for `std::array`, and it would be perfectly possible for it to work with `std::vector`. Once again, an `int` is not default initialized, why would all non-built in have to be? – Bérenger Aug 25 '21 at 23:45
  • If you try to use `std::array` with a class with a default constructor, you should be able to see each value in the array getting default-constructed. – Sam Varshavchik Aug 25 '21 at 23:50
  • Yes but it does not change the fact that unlike `std::vector`, it does not default-initialize its values. – Bérenger Aug 25 '21 at 23:57
1

Not really an answer to your question, but rather an answer to your problem (untested code):

vector<int> v;
int val = 0;
v.reserve(1000);
std::generate_n(std::back_inserter(v), 1000, [&val]() { return val++; });
Marshall Clow
  • 15,972
  • 2
  • 29
  • 45