C++:如何高效的使用std::vector?
目錄:
一、在std::vector尾部新增物件時應儘量使用emplace_back,而不要使用push_back
二、新增多個元素前應使用reserve設定容量,防止擴容時發生元素複製
六、需要將物件全部轉移到另外一std::vector時,應使用std::vector.swap()、std::swap()、std::move(std::vector)
七、如果std::vector中在存放指標物件,即std::vector,則應使用智慧指標
先說一下關於std::vector的一些基本知識:
- std::vector<T>中存放的T物件是在堆中分配的。
- std::vector<T>會負責管理T物件的生命週期,所以當將T物件存放到std::vector<T>中時,std::vector<T>會構造一個自己能完全控制的T物件,構造方式可以是轉移建構函式、拷貝建構函式還是普通建構函式。
- 當std::vector<T>的生命週期結束時,std::vector<T>也同時會將其中存放的物件析構。
對於如何高效使用std::vector,以下有幾點建議。先給出一個類的程式碼,後面會用到它:
class Girl { public: string name; int age; Girl() { cout << "Girl()" << endl; } Girl(const string& _name, int _age) : name(_name), age(_age) { cout << "Girl(string _name, int _age)" << name << endl; } Girl(const Girl& b) : name(b.name), age(b.age) { cout << "Girl(const Girl&)" << name << endl; } Girl(Girl&& b) : name(move(b.name)), age(move(b.age)) { cout << "Girl(Girl&&)" << name << endl; } Girl& operator=(const Girl& b) { this->name = b.name; this->age = b.age; cout << "operator=(const Girl&)" << name << endl; return *this; } ~Girl() { cout << "~Girl()" << name << endl; } };
一、在std::vector<T>尾部新增物件時應儘量使用emplace_back,而不要使用push_back
int main() {
vector<Girl> vvv1;
vector<Girl> vvv2;
cout << "------------------------------------------------" << endl;
vvv1.push_back(Girl("dd", 90));
cout << "------------------------------------------------" << endl;
vvv2.emplace_back("dd", 90);
cout << "------------------------------------------------" << endl;
return 1;
}
------------------------------------------------
Girl(string _name, int _age)dd
Girl(Girl&&)dd
~Girl()
------------------------------------------------
Girl(string _name, int _age)dd
------------------------------------------------
~Girl()dd
~Girl()dd
從結果中可以看到,使用push_back會比emplace_back多調了轉移建構函式和解構函式,因而效能偏低。
二、新增多個元素前應使用reserve設定容量,防止擴容時發生元素複製
int main() {
vector<Girl> vvv1;
cout << vvv1.size() << endl;
cout << vvv1.capacity() << endl;
vvv1.emplace_back("ss", 12);
cout << "------------------------------------------------" << endl;
vvv1.emplace_back("xx", 33);
cout << "------------------------------------------------" << endl;
vvv1.emplace_back("wq", 123);
cout << "------------------------------------------------" << endl;
cout << vvv1.size() << endl;
cout << vvv1.capacity() << endl;
cout << "------------------------------------------------" << endl;
vector<Girl> vvv2;
vvv2.reserve(10);
cout << vvv2.size() << endl;
cout << vvv2.capacity() << endl;
vvv2.emplace_back("ss", 12);
cout << "------------------------------------------------" << endl;
vvv2.emplace_back("xx", 33);
cout << "------------------------------------------------" << endl;
vvv2.emplace_back("wq", 123);
cout << "------------------------------------------------" << endl;
cout << vvv2.size() << endl;
cout << vvv2.capacity() << endl;
cout << "------------------------------------------------" << endl;
return 1;
}
0
0
Girl(string _name, int _age)ss
------------------------------------------------
Girl(string _name, int _age)xx
Girl(const Girl&)ss
~Girl()ss
------------------------------------------------
Girl(string _name, int _age)wq
Girl(const Girl&)ss
Girl(const Girl&)xx
~Girl()ss
~Girl()xx
------------------------------------------------
3
3
------------------------------------------------
0
10
Girl(string _name, int _age)ss
------------------------------------------------
Girl(string _name, int _age)xx
------------------------------------------------
Girl(string _name, int _age)wq
------------------------------------------------
3
10
------------------------------------------------
~Girl()ss
~Girl()xx
~Girl()wq
~Girl()ss
~Girl()xx
~Girl()wq
從結果中可以看到,由於vvv1沒有設定reserver,導致初始容量不足,每新增一個新的物件都要先擴容,然後將以前存放的物件複製過去,因而效能偏低。
三、刪除元素時應從最後一個元素開始刪除,不要從中間開始刪除
int main() {
vector<Girl> vvv1;
vvv1.reserve(10);
vvv1.emplace_back("ss", 12);
vvv1.emplace_back("xx", 33);
vvv1.emplace_back("wq", 123);
cout << "------------------------------------------------" << endl;
vector<Girl> vvv2;
vvv2.reserve(10);
vvv2.emplace_back("ss", 12);
vvv2.emplace_back("xx", 33);
vvv2.emplace_back("wq", 123);
cout << "------------------------------------------------" << endl;
vector<Girl> vvv3;
vvv3.reserve(10);
vvv3.emplace_back("ss", 12);
vvv3.emplace_back("xx", 33);
vvv3.emplace_back("wq", 123);
cout << "++++++++++++++++++++++++++++++++++++++++++++++++" << endl;
vvv1.erase(vvv1.begin());
cout << "------------------------------------------------" << endl;
vvv1.erase(vvv1.begin());
cout << "++++++++++++++++++++++++++++++++++++++++++++++++" << endl;
vvv2.erase(vvv2.end() - 1);
cout << "------------------------------------------------" << endl;
vvv2.erase(vvv2.end() - 1);
cout << "++++++++++++++++++++++++++++++++++++++++++++++++" << endl;
vvv3.pop_back();
cout << "------------------------------------------------" << endl;
vvv3.pop_back();
cout << "++++++++++++++++++++++++++++++++++++++++++++++++" << endl;
return 1;
}
Girl(string _name, int _age)ss
Girl(string _name, int _age)xx
Girl(string _name, int _age)wq
------------------------------------------------
Girl(string _name, int _age)ss
Girl(string _name, int _age)xx
Girl(string _name, int _age)wq
------------------------------------------------
Girl(string _name, int _age)ss
Girl(string _name, int _age)xx
Girl(string _name, int _age)wq
++++++++++++++++++++++++++++++++++++++++++++++++
operator=(const Girl&)xx
operator=(const Girl&)wq
~Girl()wq
------------------------------------------------
operator=(const Girl&)wq
~Girl()wq
++++++++++++++++++++++++++++++++++++++++++++++++
~Girl()wq
------------------------------------------------
~Girl()xx
++++++++++++++++++++++++++++++++++++++++++++++++
~Girl()wq
------------------------------------------------
~Girl()xx
++++++++++++++++++++++++++++++++++++++++++++++++
~Girl()ss
~Girl()ss
~Girl()wq
從結果中可以看到,如果從中間或開頭開始刪除,後面的物件由於要向前移動而要呼叫賦值函式,執行效能偏低。這裡移動的方式要注意,它是每二個物件賦值給第一個物件,第三個物件賦值給第二個物件,第三個物件析構銷燬。
四、新增新物件時應從結尾處新增,而不要從中間新增
int main() {
vector<Girl> vvv1;
vvv1.reserve(10);
vvv1.emplace_back("ss", 12);
cout << "------------------------------------------------" << endl;
vvv1.insert(vvv1.begin(), Girl("xx", 33));
cout << "------------------------------------------------" << endl;
vvv1.insert(vvv1.begin(), Girl("wq", 123));
cout << "------------------------------------------------" << endl;
cout << "------------------------------------------------" << endl;
vector<Girl> vvv2;
vvv2.reserve(10);
vvv2.emplace_back("ss", 12);
cout << "------------------------------------------------" << endl;
vvv2.emplace(vvv2.begin(), "xx", 33);
cout << "------------------------------------------------" << endl;
vvv2.emplace(vvv2.begin(), "wq", 123);
cout << "------------------------------------------------" << endl;
return 1;
}
Girl(string _name, int _age)ss
------------------------------------------------
Girl(string _name, int _age)xx
Girl(Girl&&)xx
Girl(Girl&&)ss
operator=(const Girl&)xx
~Girl()xx
~Girl()
------------------------------------------------
Girl(string _name, int _age)wq
Girl(Girl&&)wq
Girl(Girl&&)ss
operator=(const Girl&)xx
operator=(const Girl&)wq
~Girl()wq
~Girl()
------------------------------------------------
------------------------------------------------
Girl(string _name, int _age)ss
------------------------------------------------
Girl(string _name, int _age)xx
Girl(Girl&&)ss
operator=(const Girl&)xx
~Girl()xx
------------------------------------------------
Girl(string _name, int _age)wq
Girl(Girl&&)ss
operator=(const Girl&)xx
operator=(const Girl&)wq
~Girl()wq
------------------------------------------------
~Girl()wq
~Girl()xx
~Girl()ss
~Girl()wq
~Girl()xx
~Girl()ss
從執行結果可以看到,無論是使用emplace還是insert,在插入位置後的物件都會發生移動,這樣會影響效能。
五、使用std::vector(std::vector)和std::vector(std::initializer_list<T>)對std::vector賦初始值會將std::vector和std::initializer_list<T>中所有物件複製
int main() {
cout << "------------------------------------------------" << endl;
vector<Girl> vv({ Girl("d",90), Girl("ll",80) });
cout << "------------------------------------------------" << endl;
vector<Girl> vvv1;
vvv1.reserve(10);
vvv1.emplace_back("ss", 12);
vvv1.emplace_back("xx", 33);
cout << "------------------------------------------------" << endl;
vector<Girl> vvv2(vvv1);
cout << "------------------------------------------------" << endl;
return 1;
}
------------------------------------------------
Girl(string _name, int _age)d
Girl(string _name, int _age)ll
Girl(const Girl&)d
Girl(const Girl&)ll
~Girl()ll
~Girl()d
------------------------------------------------
Girl(string _name, int _age)ss
Girl(string _name, int _age)xx
------------------------------------------------
Girl(const Girl&)ss
Girl(const Girl&)xx
------------------------------------------------
~Girl()ss
~Girl()xx
~Girl()ss
~Girl()xx
~Girl()d
~Girl()ll
通過執行結果可以看到,當初始化std::vector時,物件雙被複制了一份。通過初始化的方式無法將物件在多個集合中共享。如果像讓std::vector中的所有物件暴露出來,可以使用T* std::vector.data(),它會返回std::vector中所包含的物件的指標。
六、需要將物件全部轉移到另外一std::vector時,應使用std::vector.swap()、std::swap()、std::move(std::vector)
int main() {
{
cout << "------------------------------------------------" << endl;
vector<Girl> vvv1;
vvv1.reserve(10);
vvv1.emplace_back("ss", 12);
vvv1.emplace_back("xx", 33);
vvv1.emplace_back("wq", 123);
cout << "------------------------------------------------" << endl;
vector<Girl> vvv2 = move(vvv1);
cout << "------------------------------------------------" << endl;
}
{
cout << "------------------------------------------------" << endl;
vector<Girl> vvv1;
vvv1.reserve(10);
vvv1.emplace_back("ss", 12);
vvv1.emplace_back("xx", 33);
vvv1.emplace_back("wq", 123);
cout << "------------------------------------------------" << endl;
vector<Girl> vvv2;
vvv2.swap(vvv1);
cout << "------------------------------------------------" << endl;
}
{
cout << "------------------------------------------------" << endl;
vector<Girl> vvv1;
vvv1.reserve(10);
vvv1.emplace_back("ss", 12);
vvv1.emplace_back("xx", 33);
vvv1.emplace_back("wq", 123);
cout << "------------------------------------------------" << endl;
vector<Girl> vvv2;
swap(vvv1, vvv2);
cout << "------------------------------------------------" << endl;
}
return 1;
}
------------------------------------------------
Girl(string _name, int _age)ss
Girl(string _name, int _age)xx
Girl(string _name, int _age)wq
------------------------------------------------
------------------------------------------------
~Girl()ss
~Girl()xx
~Girl()wq
------------------------------------------------
Girl(string _name, int _age)ss
Girl(string _name, int _age)xx
Girl(string _name, int _age)wq
------------------------------------------------
------------------------------------------------
~Girl()ss
~Girl()xx
~Girl()wq
------------------------------------------------
Girl(string _name, int _age)ss
Girl(string _name, int _age)xx
Girl(string _name, int _age)wq
------------------------------------------------
------------------------------------------------
~Girl()ss
~Girl()xx
~Girl()wq
通過執行結果可以看到,轉移的過程只是更改了指標的指向,沒有發生任何複製或拷貝。
七、如果std::vector中在存放指標物件,即std::vector<T*>,則應使用智慧指標
-
std::vector<std::unique_ptr<T>>
-
std::vector<std::shared_ptr<T>>
因為如果std::vector中存放指標,指標指向的物件並不受std::vector管理,所以需要智慧指標幫助管理這些物件。