讀書筆記 effective c++ Item 45 使用成員函數模板來接受“所有相容類型”

來源:互聯網
上載者:User

標籤:operator   ...   weak_ptr   exp   templates   好的   成員函數   參數   div   

 

智能指標的行為像是指標,但是沒有提供加的功能。例如,Item 13中解釋了如何使用標準auto_ptr和tr1::shared_ptr指標在正確的時間自動刪除堆上的資源。STL容器中的迭代器基本上都是智能指標:當然,你不能通過使用“++”來將鏈表中的指向一個節點的內建指標移到下一個節點上去,但是list::iterator可以這麼做。

1. 問題分析——如何?智能指標的隱式轉換

真正的指標能夠做好的一件事情是支援隱式轉換。衍生類別指標可以隱式轉換為基類指標,指向非const的指標可以隱式轉換成為指向const對象的指標,等等。例如,考慮可以在一個三層繼承體系中發生的轉換:

1 class Top { ... };2 class Middle: public Top { ... };3 class Bottom: public Middle { ... };4 Top *pt1 = new Middle;           // convert Middle* ⇒ Top*5 6 Top *pt2 = new Bottom;          // convert Bottom* ⇒ Top*7 8 const Top *pct2 = pt1;             // convert Top* ⇒ const Top*

 

在使用者自訂的智能指標中模仿這種轉換是很微妙的。我們想讓下面的代碼通過編譯:

 1 template<typename T> 2 class SmartPtr { 3 public: // smart pointers are typically 4 explicit SmartPtr(T *realPtr); // initialized by built-in pointers 5 ... 6 }; 7 SmartPtr<Top> pt1 = // convert SmartPtr<Middle> ⇒ 8 SmartPtr<Middle>(new Middle); // SmartPtr<Top> 9 SmartPtr<Top> pt2 = // convert SmartPtr<Bottom> ⇒10 SmartPtr<Bottom>(new Bottom); // SmartPtr<Top>11 SmartPtr<const Top> pct2 = pt1; // convert SmartPtr<Top> ⇒12 // SmartPtr<const Top>

 

同一個模板的不同執行個體之間沒有固有的關係,所以編譯器將SmartPtr<Middle>和SmartPtr<Top>視為完全不同的類,它們之間的關係不比vector<float>和Widget來的近。為了實現SmartPtr類之間的轉換,我們必須顯示的實現。

 

在上面的智能指標範例程式碼中,每個語句都建立了一個新的智能指標對象,所以現在我們把焦點放在如何?出一個行為表現如我們所願的智能指標建構函式。關鍵的一點是沒有辦法實現我們需要的所有建構函式。在上面的繼承體系中,我們可以用一個SmartPtr<Middle>或一個SmartPtr<Bottom>來構造一個SmartPtr<Top>,但是如果這個繼承體系在未來擴充了,SmartPtr<Top>對象必須能夠從其他智能指標類型中構造出來。例如,如果我們增加了下面的類:

1 class BelowBottom: public Bottom { ... };

 

我們將會需要支援用SmartPtr<BelowBottom>對象來建立SmartPtr<Top>對象,我們當然不想通過修改SmartPtr模板來實現它。

 

2. 使用成員函數模板——泛化拷貝建構函式進行隱式轉換

從原則上來說,我們所需要的建構函式的數量是沒有限制的。既然模板可以被執行個體化成為沒有限制數量的函數,因此看上去我們不需要一個SmartPtr的建構函式,我們需要的是一個建構函式模板。這樣的模板是成員函數模板(member function templates) (也被叫做member templates)的一個例子——也即是為類產產生員函數的模板:

1 template<typename T>2 class SmartPtr {3 public:4 template<typename U> // member template5 SmartPtr(const SmartPtr<U>& other); // for a ”generalized6 7 ...                                  // copy constructor”8 9 };

 

這就是說對於每個類型T和每個類型U,一個SmartPtr<T>能夠用SmartPtr<U>創造出來,因為SmartPtr<T>有一個以SmartPtr<U>作為參數的建構函式 。像這樣的建構函式——用一個對象來建立另外一個對象,兩個對象來自於相同的模板但是它們為不同類型(例如,用SmartPtr<U>來建立SmartPtr<T>),它通常被叫做泛化拷貝建構函式(generalized copy constructors)。

 

2.1 隱式轉換不需要explicit

上面的泛化拷貝建構函式並沒有被聲明為explicit。這是經過仔細考慮的。內建指標類型之間的類型轉換(例如從衍生類別轉換到基類指標)是隱式的,並且不需要cast,因此智能指標模仿這種行為就是合理的。在模板化的建構函式上省略explicit正好做到了這一點。

 

2.2 將不符合要求的模板執行個體化函數剔除掉

為SmartPtr實現的泛化拷貝建構函式比我們想要的提供了更多的東西。我們想要用SmartPtr<Bottom>建立SmartPtr<Top>,但是我們不想用SmartPtr<Top>建立SmartPtr<Bottom>,因為這違背了public繼承的含義(Item 32)。我們同樣不想用SmartPtr<double>建立SmartPtr<int>,因為沒有從double*到int*之間的隱式轉換。因此,我們必須將成員模板產生的這種成員函數集合剔除掉。

 

假設SmartPtr遵循auto_ptr和tr1::shared_ptr的設計,也提供一個get成員函數來返回智能指標對象所包含的內建類型指標的一份拷貝(Item 15),我們可以使用建構函式模板的實現來對一些轉換進行限制:

 1 template<typename T> 2 class SmartPtr { 3 public: 4 template<typename U> 5 SmartPtr(const SmartPtr<U>& other) // initialize this held ptr 6 : heldPtr(other.get()) { ... } // with other’s held ptr 7 T* get() const { return heldPtr; } 8 ... 9 private: // built-in pointer held10 T *heldPtr; // by the SmartPtr11 }

 

我們在成員初始化列表中用SmartPtr<U>中包含的類型為U*的指標來初始化SmartPtr<T>中的類型為T*的資料成員。這隻有在能夠從U*指標到T*指標進行隱式轉換的情況下才能通過編譯,這也正是我們所需要的。實際結果是現在SmartPtr<T>有了一個泛化拷貝建構函式,只有傳遞的參數為相容類型時才能夠通過編譯。

3. 成員函數模板對賦值的支援

成員函數模板的使用不僅僅限定在建構函式上。它們的另外一個普通的角色是對賦值的支援。例如,tr1的shared_ptr(Item 13)支援用所有相容的內建指標來對其進行構造,可以用tr1::shared_ptr,auto_ptr和tr1::weak_ptr(Item 54)來進行構造,對賦值也同樣使用,但是tr1::weak_ptr例外。下面是從tr1的說明中摘錄下來的tr1::shared_ptr的實現,可以看到在聲明模板參數的時候它傾向於使用class而不是typename。(Item 42中描述的,在這個上下文中它們的意義相同。)

 

 1 template<class T> class shared_ptr { 2 public: 3  4 template<class Y>                              // construct from 5  6 explicit shared_ptr(Y * p);                   // any compatible 7  8 template<class Y>                              // built-in pointer, 9 10 11 shared_ptr(shared_ptr<Y> const& r); // shared_ptr,12 template<class Y> // weak_ptr, or13 14 explicit shared_ptr(weak_ptr<Y> const& r);    // auto_ptr15 16 template<class Y>                                          17 18 explicit shared_ptr(auto_ptr<Y>& r);             19 20 template<class Y> // assign from21 shared_ptr& operator=(shared_ptr<Y> const& r); // any compatible22 template<class Y> // shared_ptr or23 shared_ptr& operator=(auto_ptr<Y>& r); // auto_ptr24 ...25 };

 

所有的這些建構函式都是explicit的,除了泛化拷貝建構函式。這就意味著從shared_ptr的一種類型隱式轉換到shared_ptr的另一種類型是允許的,但是內建類型指標和其他的智能指標類型到shared_ptr的隱式轉換是禁止的。(顯示的轉換是可以的(例如通過使用cast))。同樣有趣的是傳遞給tr1::shared_ptr建構函式和賦值運算子的auto_ptr沒有被聲明為const,但是tr1::shared_ptr和tr1::weak_ptr的傳遞卻聲明為const了。這是因為auto_ptr被拷貝的時候已經被修改了(Item 13)。

 

4. 成員函數模板會產生預設拷貝建構函式

 

成員函數模板是美好的東西,但是它們沒有修改語言的基本規則。Item 5解釋了編譯器會自動產生的4個成員函數中的兩個函數為拷貝建構函式和拷貝賦值運算子。Tr1::shared_ptr聲明了一個泛化拷貝建構函式,很清楚的是如果類型T和類型Y是相同的,泛化拷貝建構函式就會被執行個體化成一個“普通”的拷貝建構函式。那麼編譯器會為tr1::shared_ptr產生一個拷貝建構函式嗎?或者說用相同類型的tr1::shared_ptr構造另外一個tr1::shared_ptr的時候,編譯器會執行個體化泛化拷貝建構函式嗎?

 

正如我所說的,成員模板沒有修改語言的規則。“如果你需要一個拷貝建構函式而你沒有自己聲明,編譯器會為你產生一個”這條規則也是其中之一。在一個類中聲明一個泛化拷貝建構函式(一個member template)不會阻止編譯器產生它們自己的拷貝建構函式(non-template),所以如果你想控制拷貝建構函式的所有方面,你必須同時聲明一個泛化拷貝建構函式和“普通的”建構函式。對於賦值同樣適用。下面是tr1::shared_ptr的定義:

 1 template<class T> class shared_ptr { 2 public: 3 shared_ptr(shared_ptr const& r);       // copy constructor 4  5 template<class Y>                        // generalized 6  7   8  9 shared_ptr(shared_ptr<Y> const& r);       // copy constructor10 11 shared_ptr& operator=(shared_ptr const& r);            // copy assignment12 13 template<class Y>                                     // generalized14 15 16 shared_ptr& operator=(shared_ptr<Y> const& r); // copy assignment17 ...18 };

 

5. 總結
  • 使用成員函數模板來產生接受所有相容類型的函數。
  • 如果你為泛化拷貝建構函式和泛化賦值運算子聲明成員模板,你同樣需要聲明普通的拷貝建構函式和拷貝賦值運算子。

讀書筆記 effective c++ Item 45 使用成員函數模板來接受“所有相容類型”

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.