1. 程式人生 > >C++:如何高效的使用std::vector?

C++:如何高效的使用std::vector?

目錄:

一、在std::vector尾部新增物件時應儘量使用emplace_back,而不要使用push_back

二、新增多個元素前應使用reserve設定容量,防止擴容時發生元素複製

三、刪除元素時應從最後一個元素開始刪除,不要從中間開始刪除

四、新增新物件時應從結尾處新增,而不要從中間新增

五、使用std::vector(std::vector)和std::vector(std::initializer_list)對std::vector賦初始值會將std::vector和std::initializer_list中所有物件複製

六、需要將物件全部轉移到另外一std::vector時,應使用std::vector.swap()、std::swap()、std::move(std::vector)

七、如果std::vector中在存放指標物件,即std::vector,則應使用智慧指標


先說一下關於std::vector的一些基本知識:

  1. std::vector<T>中存放的T物件是在堆中分配的。
  2. std::vector<T>會負責管理T物件的生命週期,所以當將T物件存放到std::vector<T>中時,std::vector<T>會構造一個自己能完全控制的T物件,構造方式可以是轉移建構函式、拷貝建構函式還是普通建構函式。
  3. 當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管理,所以需要智慧指標幫助管理這些物件。