天天看點

C++:20---類模闆(template)

一、類模闆與模闆類

  • 類模闆:一個模闆(是模闆)
  • 模闆類:調用類模闆生成的類對象(是類實體),也稱為類模闆的執行個體化

類模闆的定義:

  • 與函數模闆的定義是一樣的
  1. template <typename T>
  2. class Blob
  3. {
  4. public:
  5. Blob();
  6. Blob(std::initializer_list<T> i);
  7. };

模闆類的使用:

  • 在定義類時,使用到類名的地方都需要顯示的給出模闆類的類型,格式為<>
  1. int main()
  2. {
  3. Blob<int> ia;
  4. Blob<int> ia2 = { 1,2,3 };
  5. Blob<double>* ia4 = new Blob<double>{ 1.1,3.14 };
  6. Blob<string> ia3 = { "Hello","World" };
  7. return 0;
  8. }

二、模闆類的成員函數

  • 如果模闆類的成員函數在類内聲明,而在類外定義,需要遵循以下規則:在函數前也在加上模闆清單,且類名限定符後面給出<>
  1. template <typename T>
  2. class Blob
  3. {
  4. public:
  5. Blob();
  6. Blob(std::initializer_list<T> i);
  7. T func(T const &str);//在類内聲明
  8. };
  9. //類外定義
  10. template <typename T>
  11. T Blob<T>::func(T const &str)
  12. {
  13. }

類模闆中使用其它模闆類型

  1. template <typename T> class Blob{
  2. template <typename It> Blob(It b, It e);//構造函數的參數使用其它模闆類型
  3. };
  4. template <typename T>
  5. template <typename It>
  6. Blob<T>::Blob(It b, It e):data(std::make_shared<std::vector<T>>(b,e))
  7. {
  8. }
  9. int main()
  10. {
  11. vector<long> vi = { 0,1,2 };
  12. list<const char*> w = { "Hello","World" };
  13. Blob<int> a1(vi.begin(), vi.end());
  14. Blob<string> a2(w.begin(), w.end());
  15. return 0;
  16. }

三、友元:類模闆中的友元

  • 一個類模闆中也可以擁有友元(友元類/友元函數)
  • 下面隻有當與Blob類型相同的BlobPtr類和operator==函數才可以成為Blob模闆類的友元
  1. template <typename T> class BlobPtr;
  2. template <typename T> class Blob;
  3. template <typename T>
  4. bool operator==(const Blob<T>&, const Blob<T>&)
  5. {
  6. }
  7. template <typename T> class Blob
  8. {
  9. friend class BlobPtr<T>; //使BlobPtr模闆類成為Blob模闆類的友元
  10. friend bool operator==(const Blob<T>&, const Blob<T>&);//使operator函數成為Blob模闆類的友元
  11. };
  12. template <typename T> class BlobPtr
  13. {
  14. };

四、友元:通用和特定的模闆友元關系

  • 模闆有需要複雜的關系,下面列出兩個執行個體
  1. template <typename T> class Pal;
  2. class C
  3. {
  4. friend class Pal<C>; //用類C執行個體化的Pal是C的友元
  5. template <typename T> friend class Pal2; //Pal2的所有執行個體都是C的友元
  6. };
  7. template <typename T> class C2
  8. {
  9. friend class Pal<T>; //與C2相同類型的執行個體化Pal才是C2的友元
  10. template <typename X> friend class Pal2;//任何類型執行個體化的Pal2對象都是C2的友元,因為模闆參數清單不同
  11. friend class Pal3;//Pal3是一個非模闆類,它是所有類型C2執行個體化的友元
  12. };

五、類模闆的static成員

  • 與任何其他類一樣,類模闆可以聲明static成員
  • 例如:下面Foo類模闆中定義了一個static函數和static變量
  1. template <typename T> class Foo
  2. {
  3. public:
  4. static std : size_t count() { return ctr; }
  5. private:
  6. static std::size_t ctr;
  7. };
  • 因為類的static成員變量隻可在類内定義,在類外初始化。是以模闆來的static變量也要在類外初始化,初始化時需要加上模闆參數清單,例如下面代碼,當一個特定的模闆執行個體化Foo時,其ctr被初始化為0
  1. template <typename T>
  2. std::size_t Foo<T>::ctr = 0; //定義并初始化
  • 靜态成員的調用
  1. Foo<int> fi; //執行個體化Foo<int>類和static資料成員ctr
  2. auto ct=Foo<int>::count();//執行個體化Foo<int>::count
  3. ct=fi.count(); //使用Foo<int>::count,與上面的意義是相同
  4. ct=Foo::count(); //錯誤,Foo沒有指出使用哪個模闆執行個體化
  • 類模闆的static成員的特點:當一個類給出模闆執行個體化之後,與這個類執行個體化類型相同的類共享一樣的靜态成員

Foo<int> f1,f2,f3; //f1,f2,f3共享Foo<int>::count()和Foo<int>::str

六、使用類的類型成員(::符号)

引入:

  • 當我們通過作用域符通路的名字是類型還是static成員,編譯器會自動識别,例如:
string::size_type //編譯器知道我們要通路string類中的size_type資料類型
  • 但是對于模闆就不能使用這種方法了,例如:
  1. //編譯器不知道size_type是一個static資料成員還是一種資料類型,是以産生二義性
  2. T::size_type * p;
  • 預設情況下,C++語言假定通過作用域運算符通路的名字不是資料類型,而是資料成員。是以如果我們希望使用一個模闆類型參數的類型成員,就必須顯式地告訴編譯器改名字是一個類型。需要通過typename來實作這一點
  • 例如下面的top函數:
  1. template<typename T>
  2. typename T::value_type top(const T&c)
  3. {
  4. if (!c.empty())
  5. return c.back();
  6. else
  7. return typename T::value_type();
  8. }

七、成員模闆

  • 一個類可以包含模闆類型的成員函數,這種成員稱為“成員模闆”
  • 注意:成員模闆不能為虛函數

①普通(非模闆)類的成員模闆

  • 概念:我們可以在一個非模闆類中定義一個成員模闆

示範案例

  • 預設的情況下,unique_ptr會調用元素的析構函數來删除元素。下面我們定義了一個删除器,删除器使用operator()接收一個元素指針,并将該元素進行delete
  1. //函數對象類,對給定指針執行delete
  2. class DebugDelete
  3. {
  4. public:
  5. DebugDelete(std::ostream &s=std::cerr):os(s){} //構造函數
  6. //根據傳入的參數進行delete
  7. template <typename T>
  8. void operator()(T* p)const //成員模闆
  9. {
  10. os << "deleting unique_ptr" << endl;
  11. delete p;
  12. }
  13. private:
  14. std::ostream &os;
  15. };
  • 下面是基本的使用格式:
  1. int main()
  2. {
  3. double *p = new double;
  4. DebugDelete d;
  5. d(p); //調用DebugDelete::operator()(double*)釋放p
  6. int *ip = new int;
  7. DebugDelete()(ip); //在一個臨時DebugDelete對象上調用operator()(int*)
  8. return 0;
  9. }
  • 下面我們将這個類作為unique_ptr的删除器來使用
  1. int main()
  2. {
  3. //一個類型為int的unique_ptr對象,DebugDelete作為其删除器
  4. unique_ptr<int, DebugDelete> p(new int, DebugDelete());
  5. //一個類型為string的unique_ptr對象,DebugDelete作為其删除器
  6. unique_ptr<std::string, DebugDelete> sp(new std::string, DebugDelete());
  7. return 0;
  8. }

②類模闆的成員模闆

  • 概念:對于類模闆,我們也可以為其定義成員模闆。在此情況下,類和成員各自有自己的、獨立的模闆參數

示範案例

  • 例如下面Blob是一個類模闆,模闆類型為T資料成員vector的類型也為T。另外其構造函數也是一個模闆,其接受的模闆類型為It
  1. template<typename T>
  2. class Blob {
  3. public:
  4. template<typename It>
  5. Blob(It b, It e); //構造函數接受一個疊代器區間,用來初始化data
  6. private:
  7. std::vector<T> data;
  8. };
  • 現在我們在類的外部定義構造函數,由于類模闆與成員函數都是模闆,是以在外部定義時需要分别同時給出這兩個模闆的模闆參數清單
  • 執行個體化成員模闆:為了執行個體化一個類模闆的成員模闆,我們必須同時提供類和函數模闆的實參。見下面的示範案例,其中:
  • a1:Blob的類型為int,構造函數的類型為int*
  • a2:Blob的類型為int,構造函數的類型為vector<long>::iterator
  • a3:Blob的類型為string,構造函數的類型為list<const char*>::iterator

繼續閱讀