12.2vector
The most useful standard-library container isvector. Avectoris a sequence of elements of a given type. The elements are stored contiguously in memory. A typical implementation ofvector(§5.2.2, §6.2) will consist of a handle holding pointers to the first element, one-past-the-last element, and one-past-the-last allocated space (§13.1) (or the equivalent information represented as a pointer plus offsets):
In addition, it holds an allocator (here,alloc), from which thevector可以获得memory for its elements. The default allocator usesnewanddeleteto acquire and release memory (§12.7). Using a slightly advanced implementation technique, we can avoid storing any data for simple allocators in avectorobject.
我们可以初始化一个vectorwith a set of values of its element type:
vectorphone_book = { {"David Hume",123456}, {"Karl Popper",234567}, {"Bertrand Arthur William Russell",345678} };
Elements can be accessed through subscripting. So, assuming that we have defined<<forEntry, we can write:
void print_book(const vector& book) { for (int i = 0; i!=book.size(); ++i) cout << book[i] << '\n'; }
As usual, indexing starts at0so thatbook[0]holds the entry forDavid Hume. Thevectormember functionsize()gives the number of elements.
The elements of avectorconstitute a range, so we can use a range-forloop (§1.7):
void print_book(const vector& book) { for (const auto& x : book) // for "auto" see §1.4cout << x << '\n'; }
When we define avector, we give it an initial size (initial number of elements):
vectorv1 = {1, 2, 3, 4}; // size is 4vectorv2; // size is 0vector*> v3(23); //size is 23; initial element value: nullptr向量<二> v4(32岁,9.9);//size is 32; initial element value: 9.9
An explicit size is enclosed in ordinary parentheses, for example,(23), and by default, the elements are initialized to the element type’s default value (e.g.,nullptrfor pointers and0for numbers). If you don’t want the default value, you can specify one as a second argument (e.g.,9.9for the32elements ofv4).
The initial size can be changed. One of the most useful operations on avectorispush_back(), which adds a new element at the end of avector, increasing its size by one. For example, assuming that we have defined>>forEntry, we can write:
void input() { for (Entry e; cin>>e; ) phone_book.push_back(e); }
This readsEntrys from the standard input intophone_bookuntil either the end-of-input (e.g., the end of a file) is reached or the input operation encounters a format error.
The standard-libraryvectoris implemented so that growing avectorby repeatedpush_back()s is efficient. To show how, consider an elaboration of the simpleVectorfrom Chapter 5 and Chapter 7 using the representation indicated in the diagram above:
templateclass Vector { allocator standard-library allocator of space for TsT*elem; //pointer to first elementT*space; //pointer to first unused (and uninitialized) slotT*last; //pointer to last slotpublic://...int size() const { return space-elem; } //number of elementsint capacity() const { return last-elem; } //number of slots available for elements//...void reserve(int newsz); //increase capacity() to newsz//...void push_back(const T& t); //copy t into Vectorvoid push_back(T&& t); //move t into Vector};alloc; //
The standard-libraryvectorhas memberscapacity(),reserve(), andpush_back(). Thereserve()is used by users ofvectorand othervector成员为更多的元素。它可能甲型肝炎e to allocate new memory and when it does, it moves the elements to the new allocation. Whenreserve()moves elements to a new and larger allocation, any pointers to those elements will now point to the wrong location; they have becomeinvalidatedand should not be used.
Givencapacity()andreserve(), implementingpush_back()is trivial:
templatevoid Vector make sure we have space for treserve(size()==0?8:2*size()); //double the capacityconstruct_at(space,t); //initialize *space to t ("place t at space")++space; }::push_back(const T& t) { if (capacity()<=size()) //
Now allocation and relocation of elements happens only infrequently. I used to usereserve()to try to improve performance, but that turned out to be a waste of effort: the heuristic used byvectoris on average better than my guesses, so now I only explicitly usereserve()to avoid reallocation of elements when I want to use pointers to elements.
Avectorcan be copied in assignments and initializations. For example:
vectorbook2 = phone_book;
Copying and movingvectors are implemented by constructors and assignment operators as described in §6.2. Assigning avectorinvolves copying its elements. Thus, after the initialization ofbook2,book2andphone_bookhold separate copies of everyEntryin the phone book. When avectorholds many elements, such innocent-looking assignments and initializations can be expensive. Where copying is undesirable, references or pointers (§1.7) or move operations (§6.2.2) should be used.
The standard-libraryvectoris very flexible and efficient. Use it as your default container; that is, use it unless you have a solid reason to use some other container. If you avoidvectorbecause of vague concerns about “efficiency,” measure. Our intuition is most fallible in matters of the performance of container uses.
12.2.1 Elements
Like all standard-library containers,vectoris a container of elements of some typeT, that is, avector
If you have a class hierarchy (§5.5) that relies onvirtualfunctions to get polymorphic behavior, do not store objects directly in a container. Instead store a pointer (or a smart pointer; §15.2.1). For example:
vectorvs; // No, don't - there is no room for a Circle or a Smiley (§5.5)vector*> vps; //better, but see §5.5.3 (don't leak)vector > vups; // OK
12.2.2 Range Checking
The standard-libraryvectordoes not guarantee range checking. For example:
void silly(vector& book) { int i = book[book.size()].number; // book.size() is out of range//...}
That initialization is likely to place some random value inirather than giving an error. This is undesirable, and out-of-range errors are a common problem. Consequently, I often use a simple range-checking adaptation ofvector:
templatestruct Vec : std::vector use the constructors from vector (under the name Vec)T& operator[](int i) { return vector{ using vector ::vector; // ::at(i); } // range checkconst T& operator[](int i) const { return vector::at(i); } // range check const objects; §5.2.1auto begin() { return Checked_iter>{ *this}; } //see §13.1auto end() { return Checked_iter>{ *this, vector::end()};} };
Vecinherits everything fromvectorexcept for the subscript operations that it redefines to do range checking. Theat()operation is avectorsubscript operation that throws an exception of typeout_of_rangeif its argument is out of thevector’s range (§4.2).
ForVec, an out-of-range access will throw an exception that the user can catch. For example:
void checked(Vec& book) { try { book[book.size()] = {"Joe",999999}; // will throw an exception//...}捕捉(out_of_range&) {cerr < <“距离误差\ n”;} }
The exception will be thrown, and then caught (§4.2). If the user doesn’t catch an exception, the program will terminate in a well-defined manner rather than proceeding or failing in an undefined manner. One way to minimize surprises from uncaught exceptions is to use amain()with atry-block as its body. For example:
int main() try {//your code}catch (out_of_range&) { cerr << "range error\n"; } catch (...) { cerr << "unknown exception thrown\n"; }
This provides default exception handlers so that if we fail to catch some exception, an error message is printed on the standard error-diagnostic output streamcerr(§11.2).
Why doesn’t the standard guarantee range checking? Many performance-critical applications usevectors and checking all subscripting implies a cost on the order of 10%. Obviously, that cost can vary dramatically depending on hardware, optimizers, and an application’s use of subscripting. However, experience shows that such overhead can lead people to prefer the far more unsafe builtin arrays. Even the mere fear of such overhead can lead to disuse. At leastvectoris easily range checked at debug time and we can build checked versions on top of the unchecked default.
A range-for避免隐式acc不惜代价的错误sing all elements in the range. As long as their arguments are valid, the standard-library algorithms do the same to ensure the absence of range errors.
If you usevector::at()directly in your code, you don’t need myVecworkaround. Furthermore, some standard libraries have range-checkedvectorimplementations that offer more complete checking thanVec.