1. 程式人生 > >c++ STL 常用容器元素型別相關限制 指標 引用

c++ STL 常用容器元素型別相關限制 指標 引用

http://blog.csdn.net/ginewar/article/details/20247215

 

c++ 的 STL 中主要有 vector , list, map, set  , multimap,multiset  

 

這些容器完全支援使用內建型別和指標(指標注意記憶體洩露問題)。

就是說亂用智慧指標或其他指標作為容器元素,有可能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/39570728200911