http://blog.csdn.net/ginewar/article/details/20247215
c++ 的 STL 中主要有 vector , list, map, set , multimap,multiset
set 元素不可以重復,set中的元素只可以讀, 不可以修改;要修改先erase,再insert。
這些容器完全支持使用內置類型和指針(指針注意內存泄露問題)。
就是說亂用智能指針或其他指針作為容器元素,有可能2個元素指向同一個對象,2個元素(指針)對應一個對象,甚至更多
C++ 容器要求元素具有 object type,引用不是 object type。
#include <vector> #include <boost/shared_ptr.hpp> using namespace std; class test {}; typedef boost::shared_ptr<test> test_ptr; int main() { vector<test> tmp; vector<test*> tmp1; //小心內存泄露,重復析構等問題 //vector<test&> tmp; //直接編譯通不過 vector<test_ptr> tmp2; //vector<test_ptr&> tmp3; //即使是boost的智能指針的引用也不行 return 0; }
這些容器都要求元素類型滿足以下2種情況:
(1)能被復制:向這些容器添加新元素時,容器會復制一份自己的版本,這要求容器使用的元素類型可以被復制,類類型需要復制構造函數的支持了;
(2)能被賦值:在使用容器的刪除、查找、訪問、使用迭代器修改元素等許多情況下,都需要元素的賦值操作支持,類類型需要賦值操作符運算的支持。
vector、list 中的單參數的resize 操作需要默認初始化指定個數的元素,類類型需要無參數的默認構造函數支持初始化。
set、multiset, map和multimap中的鍵類型、 list 中的sort 操作 都需要 < 比較操作來排序,類類型需要 < 操作符運算的支持。
在STL中,容器的元素要滿足三個基本要求:可拷貝(copyable)、可賦值(assignable)、可析構(destroyable)。基本數據類型和自定義的類都滿足這些條件,但是引用不滿足,因為引用不能析構。
===========================
http://wenku.baidu.com/view/8d049f4f767f5acfa1c7cd11.html
【摘要】對C++語言本身來說,它並不在乎用戶把什么類型的對象作為STL容器的元素,因為模板類型參數在理論上可以為任何類型。比如說STL容器僅支持“值”語義而不支持“引用(&)”語義,並非因為模板類型參數不能為引用,而是因為如果容器元素為引用類型,就會出現“引用的引用”、“引用的指針”等C++語言不支持的語法和語義。智能指針是一種模擬原始指針行為的對象,因此理論上也可以作為容器的元素,就象原始指針可以作為容器元素一樣。但是智能指針畢竟是一種特殊的對象,它們在原始指針共享實值對象的基礎能力上增加了自動銷毀實值對象的能力,如果將它作為容器的元素,可能導致容器之間共享元素對象實值,這不僅不符合STL容器的概念和“值”語義,也會存在安全隱患,同時也會存在許多應用上的限制,特別是象STL中的auto_ptr這樣的智能指針。
可以作為STL容器的元素的數據類型一般來說需要滿足下列條件:
(1)可默認構造的(Default Constructible),也即具有public的default constructor,不論是用戶顯式定義的還是編譯器自動合成的。但是用戶定義的帶參數的constructor(包括copy constructor)會抑制編譯器合成default constructor。實際上並非任何情況下任何一種容器都強制要求其元素類型滿足這一要求,特別是關聯式容器,因為只有序列式容器的某些成員函數才可能明確地或隱含地使用元素類型的默認構造函數,如果你不使用這樣的成員函數,編譯器就不需要元素類型的默認構造函數;
(2)可拷貝構造(Copy Constructible)和拷貝賦值(Copy Assignable)的,即具有public的copy constructor和copy assignment operator,不論是編譯器自動合成的還是用戶顯式定義的。其它版本的operator=()重載並不會抑制編譯器合成copy assignment operator,如果你沒有顯式定義它的話。這個條件可歸結為:元素必須是可拷貝的(Copyable),但實際上拷貝賦值的要求也不是強制的,原因和默認構造函數類似;
(3)具有public的destructor,不論是編譯器自動合成的還是用戶顯式定義的; (4)對於關聯式容器,要求其元素必須是可比的(Comparable)。
std::auto_ptr滿足上述條件嗎?至少滿足前三條,因此至少可以作為序列式容器的元素;如果為auto_ptr定義了比較運算符的話,應該還可以把它作為關聯式容器的元素。
但是auto_ptr的特點是接管和轉移擁有權,而不是像原始指針那樣可以共享實值對象,
int tmp = 10;
int* p1 = &tmp;
int* p2 = &tmp;
指針p1 p2共享實值對象tmp;
即:auto_ptr在初始化時接管實值對象和擁有權,而在拷貝時(拷貝構造和拷貝賦值)會交出實值對象及其擁有權。因此,auto_ptr對象和它的拷貝絕對不會共享實值對象,任何兩個auto_ptr也不應該共享同一個實值對象。這就是說,auto_ptr對象和它的拷貝並不相同。然而根據STL容器“值” 語義的要求,可拷貝構造意味着一個對象必須和它的拷貝相同(標准中的正式定義比這稍復雜一些)。同樣,可賦值意味着把一個對象賦值給另一個同類型對象將產生兩個相同的對象。顯然,auto_ptr不能滿足這一要求,似乎與上面的結論矛盾!
STL容器管理元素的方法是動態創建元素的拷貝
應該說,從應用的方便性和安全角度出發,容器應該要求其元素對象的拷貝與原對象相同或者等價,但auto_ptr顯然不滿足這一條。
==================
http://bbs.csdn.net/topics/310036165
容器元素比如vector對元素對象的唯一要求是可以復制構造。
但比如說你把auto_ptr對象用作了容器元素,雖然其也可以復制構造,只不過復制構造會破壞原始對象,你用了之后會導致未定義現象。
容器的大小是可以改變的,而且往往會自動改變。
對於vector來說,如果空間不夠了,會自動增長,但是如果原來所在的空間不夠的話,系統就會在另外一個地方分配一個滿足需要的空間。
所以在此時,對於vector已有的元素也進行了移動,此時就會執行新的構造函數,同樣還會把原來位置的舊元素析構掉。
如果同時存在兩個vector,其中一個對舊元素執行了析構,會導致另外一個對同一個元素析構,這樣就會出問題。
對於你舉的例子沒有這樣的問題,這是因為對於char*執行的值拷貝。
所謂的“值語義”就是說可不可以拷貝的問題。std::auto_ptr不滿足這個條件。
"值"的語義就是 每個元素都應該是單獨的完整的元素,而其元素指針共享同一對象,導致操作一個元素而影響其他元素影響整個容器。。。
std::auto_ptr這種智能指針的特性,決定了它不適合作為容器的元素的。比如用於vector時,使用push_back(),調用的是復制構造函數。
但auto_ptr在拷貝構造的同時,把原有對象的實值擁有權轉給了vector,同時刪除了原有的auto_ptr。可能會導致后面使用中的錯誤。
當使用vector.clear()或者這個vector的生存周期到了,被釋放的時候,同時會導致原有實值被刪除!這往往不是我們想要的。
例如:
|
1
2
3
4
5
6
|
typedef
auto_ptr<
class
T> aptr;
aptr p(
new
T);
vector<aptr> vec;
vec.push_back(p);
//此時p被刪除,vec.at(0)擁有了原實值
vec.clear();
//原實值徹底被刪除
p->operation();
//還想用p做啥都要崩潰了
|
|
1
2
3
4
5
6
|
int
a=5;
int
*p=&a;
int
*q=&a;
vector <
int
*> vec1,vec2;
vec1.push_back(p);
vec2.push_back(p);
|
vec1和vec2中都有p,也就是a的地址,但vector並沒有獲得a的實值的擁有權!
這里vec1和vec2消逝或者是clear都不會導致a的消亡。
容器在存入數據的時候,是存入數據值的一個拷貝,而不是存入的數據的地址。比如說對象a,
存入容器,容器有一個a的拷貝_a,那么_a和a是互相獨立的。對容器內_a的操作不會影響a,以上就是
STL容器的概念和”值“的語意。
條款8:永不建立auto_ptr的容器
坦白地說,本條款不需要出現在《Effective STL》里。auto_ptr的容器(COAPs)是禁止的。試圖使用它們的代碼都不能編譯。C++ 標准委員會花費了無數努力來安排這種情況[1]。我本來不需要說有關COAPs的任何東西,因為你的編譯器對這樣的容器應該有很多抱怨,而且所有那些都是不能編譯的。
唉,很多程序員使用STL平台不會拒絕COAPs。更糟的是,很多程序員妄想地把COAPs看作簡單、直接、高效地解決經常伴隨指針容器(參見條款7和33)資源泄漏的方案。結果,很多程序員被引誘去使用COAPs,即使建立它們不應該成功。
我會馬上解釋COAPs的幽靈有多令人擔心,以至於標准化委員會采取特殊措施來保證它們不合法。現在,我要專注於一個不需要auto_ptr甚至容器知識的缺點:COAPs不可移植。它們能是怎么樣的?C++標准禁止他們,比較好的STL平台已經實現了。可以有足夠理由推斷隨着時間的推移,目前不能實現標准的這個方面的STL平台將變得更適應,並且當那發生時,使用COAPs的代碼將更比現在更不可移植。如果你重視移植性(並且你應該是),你將僅僅因為它們的移植測試失敗而拒絕COAPs。
但可能你沒有移植性思想。如果是這樣,請允許我提醒你拷貝auto_ptr的獨特——有的人說是奇異——的定義。
當你拷貝一個auto_ptr時,auto_ptr所指向對象的所有權被轉移到拷貝的auto_ptr,而被拷貝的auto_ptr被設為NULL。你正確地說一遍:拷貝一個auto_ptr將改變它的值:
|
1
2
3
4
5
6
|
auto_ptr<Widget> pw1(
new
Widget);
// pw1指向一個Widget
auto_ptr<Widget> pw2(pw1);
// pw2指向pw1的Widget;
// pw1被設為NULL。(Widget的
// 所有權從pw1轉移到pw2。)
pw1 = pw2;
// pw1現在再次指向Widget;
// pw2被設為NULL
|
這非常不尋常,也許它很有趣,但你(作為STL的用戶)關心的原因是它導致一些非常令人驚訝的行為。例如,考慮這段看起來很正確的代碼,它建立一個auto_ptr<Widget>的vector,然后使用一個比較指向的Widget的值的函數對它進行排序。
|
1
2
3
4
5
6
7
8
9
10
|
bool
widgetAPCompare(
const
auto_ptr<Widget>& lhs,
const
auto_ptr<Widget>& rhs) {
return
*lhs < *rhs;
// 對於這個例子,假設Widget
}
// 存在operator<
vector<auto_ptr<Widget> > widgets;
// 建立一個vector,然后
// 用Widget的auto_ptr填充它;
// 記住這將不能編譯!
sort(widgets.begin(), widgets.end(),
// 排序這個vector
widgetAPCompare);
|
這里的所有東西看起來都很合理,而且從概念上看所有東西也都很合理,但結果卻完全不合理。例如,在排序過程中widgets中的一個或多個auto_ptr可能已經被設為NULL。排序這個vector的行為可能已經改變了它的內容!值得去了解這是怎么發生的。
它會這樣是因為實現sort的方法——一個常見的方法,正如它呈現的——是使用了快速排序算法的某種變體。我們不關心快速排序的妙處,但排序一個容器的基本思想是,選擇容器的某個元素作為“主元”,然后對大於和小於或等於主元的值進行遞歸排序。在sort內部,這樣的方法多多少少看起來像這樣:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
template
<
class
RandomAccessIterator,
// 這個sort的聲明
class
Compare>
// 直接來自於標准
void
sort(RandomAccessIterator first,
RandomAccessIterator last,
Compare comp)
{
// 這個typedef在下面解釋
typedef
typename
iterator_traits<RandomAccessIterator>::value_type
ElementType;
RandomAccessIterator i;
...
// 讓i指向主元
ElementType pivotValue(*i);
// 把主元拷貝到一個
// 局部臨時變量中;參見
// 下面的討論
...
// 做剩下的排序工作
}
|
除非你是在閱讀STL源代碼方面很有經驗,否則這看起來可能有些麻煩,但其實並不壞。唯一的難點是引用了iterator_traits<RandomAccessIterator>::value_type,而只不過是傳給sort的迭代器所指向的對象類型的怪異的STL方式。(當我們涉及iterator_traits<RandomAccessIterator>::value_type時,我們必須在它前面寫上typename,因為它是一個依賴於模板參數類型的名字,在這里是RandomAccessIterator。更多關於typename用法的信息,翻到第7頁。)
上面代碼中棘手的是這一行,
|
1
|
ElementType pivotValue(*i);
|
因為它把一個元素從保存的區間拷貝到局部臨時對象中。在我們的例子里,這個元素是一個auto_ptr<Widget>,所以這個拷貝操作默默地把被拷貝的auto_ptr——vector中的那個——設為NULL。另外,當pivotValue出了生存期,它會自動刪除指向的Widget。這時sort調用返回了,vector的內容已經改變了,而且至少一個Widget已經被刪除了。也可能有幾個vector元素已經被設為NULL,而且幾個widget已經被刪除,因為快速排序是一種遞歸算法,遞歸的每一層都會拷貝一個主元。
這落入了一個很討厭的陷阱,這也是為什么標准委員會那么努力地確保你不會掉進去。通過永不建立auto_ptr的容器來尊重它對你的利益的工作,即使你的STL平台允許那么做。
http://hsw625728.blog.163.com/blog/static/3957072820091116115732821/
