vector的实现

在vector中,我们常用的是push_back(), 一种我们看似是动态增加的函数。然而,所谓的动态增加大小,并不是在原空间之后接续新空间(因为无法保证原空间之后尚有可供配置的空间),而是以原大小的两倍另外配置一块较大空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间,因此,对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了。

  1. void push_back(const T& x) {
  2. if(finish != end_of_storage) {
  3. construct(finish, x); //全局函数
  4. ++finish;
  5. } else { //没有备用空间
  6. insert_aux(end(), x);
  7. }
  8. }
  9. tempalte <class T, class Alloc>
  10. void vector<T, Alloc>::insert_aux(iterator position, const T& x) {
  11. if(finish != end_of_storage) {
  12. construct(finish, *(finish - 1));
  13. ++finish;
  14. T x_copy = x;
  15. copy_back(position, finish - 2, finish - 1);
  16. *position = x_copy;
  17. } else {
  18. const size_type old_size = size();
  19. const size_type len = old_size != 0 ? 2 * old_size : 1;
  20. iterator new_start = data_allocator::allocate(len);
  21. iterator new_finish = new_start;
  22. try {
  23. new_finish = uninitialized_copy(start, position, new_start);
  24. construct(new_finish, x);
  25. ++new_finish;
  26. new_finish = uninitialized_copy(position, finish, new_finish);
  27. } catch(...) {
  28. destroy(new_start, new_finish);
  29. data_allocator::deallocate(new_start, len);
  30. throw;
  31. }
  32. destroy(begin(), end());
  33. deallocate();
  34. start = new_start;
  35. finish = new_finish;
  36. end_of_storage = new_start + len;
  37. }
  38. }

使用vector的优化方法

测试案例

  1. // Test struct to be inserted/removed from vector
  2. struct BigTestStruct
  3. {
  4. int iValue = 1;
  5. float fValue;
  6. long lValue;
  7. double dValue;
  8. char cNameArr[10];
  9. int iValArr[100];
  10. };
  11. // Helper function to populate the test vectors
  12. void FillVector(vector<BigTestStruct>& testVector)
  13. {
  14. for (int i = 0; i < 10000; i++)
  15. {
  16. BigTestStruct bt;
  17. testVector.push_back(bt);
  18. }
  19. }
  • tips1 避免不必要的内存分配和不断的拷贝,尽量在声明vector时就分配足够的空间
    • Avoid unnecessary reallocate and copy cycles by reserving the size of vector ahead of time.

持续不断的申请新空间,拷贝旧数据到新空间,释放旧空间,会耗费较多的运行时间

  • Programmers like vectors because they can just add items to the container without having to worry about the size of the container ahead of time. However, just starting with a vector of capacity 0 and adding to it as elements come in can cost you quite a lot of runtime performance. If you know ahead of time, how big your vector can get, it is worth reserving the size ahead of time.
  1. vector<BigTestStruct> testVector1;
  2. vector<BigTestStruct> testVector2;
  3. sw.Restart();
  4. FillVector(testVector1);
  5. cout << "Time to Fill Vector Without Reservation:" << sw.ElapsedUs() << endl;
  6. sw.Restart();
  7. testVector2.reserve(10000); //提前就预留出内存
  8. FillVector(testVector2);
  9. cout << "Time to Fill Vector With Reservation:" << sw.ElapsedUs() << endl;
  • tips2 使用shrink_to_fit()去释放之前所耗费的内存,clear()和erase()并不会把之前所申请的内存给释放掉。 ```cpp FillVector(testVector1); size_t capacity = testVector1.capacity(); cout << “Capacity Before Erasing Elements:” << capacity << endl;

testVector1.erase(testVector1.begin(), testVector1.begin() + 3); // capacity = testVector1.capacity(); cout << “Capacity After Erasing 3 elements Elements:” << capacity << endl; testVector1.clear(); capacity = testVector1.capacity(); cout << “Capacity After clearing all emements:” << capacity << endl; testVector1.shrink_to_fit(); capacity = testVector1.capacity(); cout << “Capacity After shrinking the Vector:” << capacity << endl;

//Output Capacity Before Erasing Elements:12138 Capacity After Erasing 3 elements Elements:12138 Capacity After clearing all emements:12138 Capacity After shrinking the Vector:0

  1. tips3: 当填充或是拷贝一个vector时,优先使用assignment,其次才是insert()或push_back()
  2. ```cpp
  3. vector<BigTestStruct> sourceVector, destinationVector;
  4. FillVector(sourceVector);
  5. // Assign sourceVector to destination vector
  6. sw.Restart();
  7. destinationVector = sourceVector;
  8. cout << "Assigning Vector :" << sw.ElapsedUs() << endl;
  9. //Using std::vector::insert()
  10. vector<BigTestStruct> sourceVector1, destinationVector1;
  11. FillVector(sourceVector1);
  12. sw.Restart();
  13. destinationVector1.insert(destinationVector1.end(),
  14. sourceVector1.begin(),
  15. sourceVector1.end());
  16. cout << "Using insert() :" << sw.ElapsedUs() << endl;
  17. //Using push_back()
  18. vector<BigTestStruct> sourceVector2, destinationVector2;
  19. FillVector(sourceVector2);
  20. sw.Restart();
  21. for (unsigned i = 0; i < sourceVector2.size(); ++i)
  22. {
  23. destinationVector2.push_back(sourceVector2[i]);
  24. }
  25. cout << "Using push_back :" << sw.ElapsedUs() << endl;
  26. //Output
  27. Assignment: 589.54 us
  28. Insert(): 1321.27 us
  29. Push_back(): 5354.70 us

Assignment之所以有效的原因就在于它知道将要拷贝的vector的大小,因此只需要调用一次memory manager来创建容纳即将拷贝的vector内容的空间。

Assignment is very efficient because it knows the size of the vector it is copying, and needs to call the memory manager only once to create the assigned vector’s internal buffer. So, to fill up a vector efficiently, try assignment, insert() with iterators from another container, and push_back(), in that order. Of course, if you have to copy from another type of container into a vector, assignment is not an option. In this case, you’d want to do an iterator based insert.

tips4: 如果想要遍历vector中的元素,避免使用std::vector::at() function
遍历vector的方法有三种,其中std::vector::at()最好不要用

//Using an iterator
vector<BigTestStruct> testVectorSum;
FillVector(testVectorSum);
sw.Restart();
int sum = 0;
for (auto it = testVectorSum.begin(); it != testVectorSum.end(); ++it)
{
    sum = sum + it->iValue;
}
cout << "Using Iterator:" << sw.ElapsedUs() << endl;

//Using the at() member function
sw.Restart();
sum = 0;
for (unsigned i = 0; i < testVectorSum.size(); ++i)
{
    sum = sum + testVectorSum.at(i).iValue;
}
cout << "Using at() :" << sw.ElapsedUs() << endl;

// Using the subscript notation
sw.Restart();
sum = 0;
for (unsigned i = 0; i < testVectorSum.size(); ++i)
{
    sum = sum + testVectorSum[i].iValue;
}
cout << "Using subscripting:" << sw.ElapsedUs() << endl;

//Output
Using Iterator:0
Using at() :3.73
Using subscripting:0