版權聲明:原創文章,歡迎轉載,但請註明出處,謝謝。
https://blog.csdn.net/qiuguolu1108/article/details/107146466
文章目錄
vector之所以會發生大量的拷貝,是因爲其內存分配策略造成的。每當vector的內存不夠用時,vector都會重新申請兩倍的空間,並將之前的元素搬移到新空間。這纔是發生拷貝的根源,既然這樣,我們能不能預先給vector申請一定的空間,避免因空間不夠而發生元素搬移。
1、使用reverse()函數預申請空間
reverse()函數可以給vector預先分配指定大小的空間。
vector<A> va;
va.reserve(1024);
cout<<"va size = "<<va.size()<<endl;
cout<<"va capacity = "<<va.capacity()<<endl<<endl;
A a;
for(int i=0;i<1024;i++)
{
va.push_back(a);
}
A::dis_copy_construct_count();
cout<<endl;
cout<<"va size = "<<va.size()<<endl;
cout<<"va capacity = "<<va.capacity()<<endl;
使用reverse()給va預先分配了1024個空間,所以再往va推入1024個元素的時候,並沒有發生多餘的拷貝構造。
通過reverse()給vector預分配空間,確實可以減少元素的拷貝構造,但我們在使用vector時,有時很難確定容器元素的個數。
在使用reverse()時需要自己去平衡,如果reverse()過大,會造成空間的浪費,如果過小還是會發生拷貝構造。
現在又帶來一個問題,如何將vector過多的沒有存放元素的空間還給系統。
2、erase()函數和clear()會減少vector的內存佔用嗎?
上篇文章中,我們說過vector佔用空間的大小,可以通過capacity()函數來查看。
通過一個示例,來驗證一下erase()、clear()會減少vector內存佔用嗎?
vector<int> vi;
for(int i=0;i<65;i++)
{
vi.push_back(i);
}
cout<<"vi size = "<<vi.size()<<endl;
cout<<"vi capacity = "<<vi.capacity()<<endl<<endl;
auto itr = find(vi.begin(),vi.end(),30);
/*刪除0~29元素*/
vi.erase(vi.begin(),itr);
cout<<"vi size = "<<vi.size()<<endl;
cout<<"vi capacity = "<<vi.capacity()<<endl<<endl;
vi.clear();
cout<<"vi size = "<<vi.size()<<endl;
cout<<"vi capacity = "<<vi.capacity()<<endl<<endl;
通過測試我們發現,erase()和clear()只能將vector空間的元素給析構掉,並能減少vector內存的佔用。
這是侯捷老師《STL源碼剖析》對vector的erase()、clear()函數實現介紹。這也進一步證實了erase()、clear()並不能釋放vector的空間。
3、data()函數可以返回vector元素存放的位置
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vi;
for(int i=0;i<10;i++)
{
vi.push_back(i);
}
int * p = vi.data();
for(int i=0;i<10;i++)
{
cout<<*p++<<endl;
}
return 0;
}
data()這個函數不用多說了,通過示例就可以看出這個函數好強大,直接殺入了vector的老巢。
4、swap()函數用於交換兩個vector
swap()函數可以用於交換兩個vector,但是交換了vector的哪些東西?
vector<int> vi0;
for(int i=0;i<5;i++)
{
vi0.push_back(i);
}
cout<<"&vi0 = "<<&vi0<<endl;
cout<<"vi0.data() = "<<vi0.data()<<endl<<endl;
vector<int> vi1;
for(int i=0;i<5;i++)
{
vi1.push_back(i*100);
}
cout<<"&vi1 = "<<&vi1<<endl;
cout<<"vi1.data() = "<<vi1.data()<<endl;
cout<<endl<<"====================="<<endl<<endl;
vi0.swap(vi1);
cout<<"&vi0 = "<<&vi0<<endl;
cout<<"vi0.data() = "<<vi0.data()<<endl<<endl;
cout<<"&vi1 = "<<&vi1<<endl;
cout<<"vi1.data() = "<<vi1.data()<<endl;
從示例中可以看出,vector的data()發生了交換,但vi0和vi1所在的地址並沒有發生變化。swap()函數還交換了其他內部成員數據,但我們弄清了我們關心的一點,swap並沒有發生空間的大量拷貝,交換的僅僅是兩個空間地址。
補充一點
:swap()函數,不僅僅交換兩個容器的內容,同時它們的迭代器、指針和引用也被交換。在swap發生後,原先指向容器中元素的迭代器、指針和引用依然有效,並指向同樣的元素----但是,這些元素已經在另一個容器中了。
5、vector的拷貝構造
使用一個vector去構造器另外一個vector,在構造新的vector時,僅會根據vector實際元素個數去構造新的vector。
vector<int> vi0;
vi0.reserve(100);
//插入5個元素
for(int i=0;i<5;i++)
{
vi0.push_back(i);
}
cout<<"vi0 size = "<<vi0.size()<<endl;
cout<<"vi0 capacity = "<<vi0.capacity()<<endl<<endl;
vector<int> vi1(vi0);
cout<<"vi1 size = "<<vi1.size()<<endl;
cout<<"vi1 capacity = "<<vi1.capacity()<<endl;
雖然vi0的內存空間可以存放100個int,但實際有效元素只有5個int。通過vi0拷貝構造vi1的時候,並不會像vi0那樣佔用100個int空間,而是根據實際元素的個數申請空間,並不會有多餘的空間。
6、使用swap技巧移除多餘的容量
鋪墊了這麼多,回到我們的主題上,如何減少vector的容量?
vector的構造器在構建新的容器時,會自動的去掉多餘的空間,我們可以利用這個特性,結合swap函數去掉vector中多餘的容量。
6.1 方法一:通過定義一個新的vector
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vi;
vi.reserve(100);
for(int i=0;i<5;i++)
{
vi.push_back(i);
}
cout<<"vi size = "<<vi.size()<<endl;
cout<<"vi capacity = "<<vi.capacity()<<endl<<endl;
vector<int> tmp(vi);
cout<<"tmp size = "<<tmp.size()<<endl;
cout<<"tmp capacity = "<<tmp.capacity()<<endl<<endl;
cout<<"=================="<<endl<<endl;
tmp.swap(vi);
cout<<"vi size = "<<vi.size()<<endl;
cout<<"vi capacity = "<<vi.capacity()<<endl<<endl;
cout<<"tmp size = "<<tmp.size()<<endl;
cout<<"tmp capacity = "<<tmp.capacity()<<endl<<endl;
return 0;
}
vi有多餘的容量,通過vi構造一個新的容器tmp,tmp沒有多餘的容量,通過swap函數將tmp和vi交換,則tmp變成了有多餘容量的容器。tmp是一個局部變量,在離開其作用域時,會調用vector的析構器,將其自己釋放。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vi;
vi.reserve(100);
for(int i=0;i<5;i++)
{
vi.push_back(i);
}
{
vector<int> tmp(vi);
tmp.swap(vi);
}//tmp在此處就會調用vector的析構器,將其自己銷燬。
return 0;
}
這樣可以更加快速的消除多餘容量。之前示例,tmp需要在main函數的最後才被銷燬,此處的示例,tmp在swap之後就立即被銷燬。
6.2 方法二:使用臨時變量
上面的方法確實可以消除vector多餘的容量,但不夠優雅,略顯囉嗦。使用臨時變量可以更加簡潔。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vi;
vi.reserve(100);
for(int i=0;i<5;i++)
{
vi.push_back(i);
}
cout<<"vi size = "<<vi.size()<<endl;
cout<<"vi capacity = "<<vi.capacity()<<endl<<endl;
{
vector<int>(vi).swap(vi);
}//臨時對象在此處離開其作用域,會被銷燬。
cout<<"vi size = "<<vi.size()<<endl;
cout<<"vi capacity = "<<vi.capacity()<<endl;
return 0;
}
沒有定義多餘的變量,一行代碼搞定,真的很優雅了。
簡單的說一下:vector<int>(vi)
定義一個臨時對象,這個臨時對象通過拷貝構造器構造。臨時對象調用swap成員函數將其自己和vi交換。臨時對象在離開其作用域時被銷燬。
6.3 清空vi
clear()僅會清空容器中的元素,並不能真正的釋放vector佔用的內存。使用swap()可以釋放vector內存。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vi;
vi.reserve(100);
for(int i=0;i<5;i++)
{
vi.push_back(i);
}
cout<<"vi size = "<<vi.size()<<endl;
cout<<"vi capacity = "<<vi.capacity()<<endl<<endl;
{
vector<int>().swap(vi);
}
cout<<"vi size = "<<vi.size()<<endl;
cout<<"vi capacity = "<<vi.capacity()<<endl;
return 0;
}
vector<int>()
會產生一個臨時對象,這個對象沒有名字,其size和capacity皆爲零。
6.4 總結:
去除vi多餘的容量:vector<int>(vi).swap(vi)
將vi的空間清空:vector<int>().swap(vi)
7、使用C++11中shrink_to_fit()函數去除多餘容量
看看官方介紹
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v;
std::cout << "Default-constructed capacity is " << v.capacity() << '\n';
v.resize(100);
std::cout << "Capacity of a 100-element vector is " << v.capacity() << '\n';
v.clear();
std::cout << "Capacity after clear() is " << v.capacity() << '\n';
v.shrink_to_fit();
std::cout << "Capacity after shrink_to_fit() is " << v.capacity() << '\n';
}
官方給的示例,很容易理解。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vi;
vi.reserve(100);
cout<<"vi size = "<<vi.size()<<endl;
cout<<"vi capacity = "<<vi.capacity()<<endl<<endl;
for(int i=0;i<10;i++)
{
vi.push_back(i);
}
cout<<"vi size = "<<vi.size()<<endl;
cout<<"vi capacity = "<<vi.capacity()<<endl<<endl;
vi.shrink_to_fit();
cout<<"vi size = "<<vi.size()<<endl;
cout<<"vi capacity = "<<vi.capacity()<<endl<<endl;
return 0;
}
vi的容量是100,向其推入10個元素,則有90個多餘的空間,調用shrink_to_fit()後,其容量變爲10,釋放了多餘的空間。
8、減少vector容量,必要的拷貝依然存在。
不管是swap()函數還是shrink_to_fit()函數,在去除vector多餘空間的時候,還是會發生必要的元素拷貝。
8.1 swap方法
vector<A> va;
va.reserve(10);
A a;
for(int i=0;i<3;i++)
{
va.push_back(a);
}
cout<<endl<<"======================"<<endl<<endl;
vector<A>(va).swap(va);
cout<<endl<<"======================"<<endl<<endl;
這裏的拷貝構造主要是在構造臨時對象產生的。
8.2 shrink_to_fit方法
vector<A> va;
va.reserve(10);
A a;
for(int i=0;i<3;i++)
{
va.push_back(a);
}
cout<<endl<<"======================"<<endl<<endl;
va.shrink_to_fit();
cout<<endl<<"======================"<<endl<<endl;
結果都是一樣的,發生了拷貝構造。
9、resize()函數
順便說一下resize()函數,這個函數使用也有一定的迷惑性。現在通過幾個例子說明一下其背後都做了哪些事情。
現在vector有兩個大小,一個是size(),vector實際元素的個數;另一個是capacity(),vector的容量。
size是小於等於capacity的。
9.1 resize()參數小於size()
將多餘的元素析構掉
#include <iostream>
#include <vector>
using namespace std;
class A
{
public:
A(int data = 100)
:data_(data)
{
cout<<"constructor : "<<this<<endl;
}
A(const A& a)
{
data_ = a.data_;
cout<<this<<" : copy constructor form : "<<&a<<endl;
}
void display()
{
cout<<data_<<" ";
}
~A()
{
cout<<"deconstructor : "<<this<<endl;
}
private:
int data_;
};
int main()
{
vector<A> va;
va.reserve(8);
A a(0),b(1),c(2),d(3);
cout<<endl<<"========================"<<endl;
va.push_back(a);
va.push_back(b);
va.push_back(c);
va.push_back(d);
cout<<endl<<"========================"<<endl;
for(auto & i : va)
{
i.display();
}
cout<<endl;
va.resize(2);
for(auto & i : va)
{
i.display();
}
cout<<endl;
return 0;
}
9.2 resize()參數大於size(),小於capacity()。
9.2.1 情況一:使用默認構造構造新元素
vector<A> va;
va.reserve(8);
A a(0),b(1),c(2),d(3);
cout<<endl<<"========================"<<endl;
va.push_back(a);
va.push_back(b);
va.push_back(c);
va.push_back(d);
cout<<endl<<"========================"<<endl;
for(auto & i : va)
{
i.display();
}
cout<<endl;
va.resize(6);
for(auto & i : va)
{
i.display();
}
cout<<endl;
需要添加兩個新的元素,沒有指定添加的元素,則調用類A的默認構造生成新元素。
A(int data = 100)
:data_(data)
{
cout<<"constructor : "<<this<<endl;
}
這是類A的默認構造器,使用這個構造器生成的對象,其默認值爲100。
9.2.2 情況二:使用指定的元素構造新元素
vector<A> va;
va.reserve(8);
A a(0),b(1),c(2),d(3);
cout<<endl<<"========================"<<endl;
va.push_back(a);
va.push_back(b);
va.push_back(c);
va.push_back(d);
cout<<endl<<"========================"<<endl;
for(auto & i : va)
{
i.display();
}
cout<<endl;
A aa(99);
va.resize(6,aa);
for(auto & i : va)
{
i.display();
}
cout<<endl;
和上面類似,但新元素的生成,調用了拷貝構造器。但這裏多發生了一次拷貝構造,根據調用情況,猜測resize()先把元素拷貝到其內部,所有新元素的拷貝都是基於resize()內部的副本。
9.3 resize()參數大於capacity()
重新申請空間,將原數據拷貝過來,在新的位置調用默認構造器生成新的元素。
vector<A> va;
va.reserve(8);
A a(0),b(1),c(2),d(3);
cout<<endl<<"========================"<<endl;
va.push_back(a);
va.push_back(b);
va.push_back(c);
va.push_back(d);
cout<<endl<<"========================"<<endl;
for(auto & i : va)
{
i.display();
}
cout<<endl;
va.resize(10);
for(auto & i : va)
{
i.display();
}
cout<<endl;
cout<<va.capacity()<<endl;
10、總結
本文介紹瞭如何去除vector多餘的容量。shrink_to_fit()是C++11提供的新方法,在C++98中可以使用swap()函數實現去除多餘的容量。
參考:《STL源碼剖析》 : https://book.douban.com/subject/1110934/
《Effective STL》:https://book.douban.com/subject/24534868/