Отправляет email-рассылки с помощью сервиса Sendsay

C++ для всех

  Все выпуски  

C++ для всех


Информационный Канал Subscribe.Ru

C++ для всех. Выпуск 7

Шаблонные классы. Объявление шаблонных классов

Здравствуйте, уважаемые подписчики

Думаю не ошибусь если скажу, что шаблоны и шаблонные классы в частности, являются самой горяче обсуждаемой темой в большинстве С++ журналов и конференций. С помощью шаблонов творят просто невообразимые вещи. Но об этом позже,- сейчас нас интересуют базовые понятия.

Как и шаблонные функции, шаблонные классы могут применятся для обработки разнотипных данных. Наибольшее применение получили в контейнерных классах, и как замечательный пример - дружно смотрим в сторону стандартной библиотеки STL.

Итак начнем. Сначала декларация:


 template<class T>
 class A
 {
 public:
  A(T *p=0):ptype(p){}
  ~A(){ delete ptype; }
  
  void checkValue(const T&){}
  const T* getValue()const{ return ptype; }

 private:
  T *ptype;
 };

Теперь мы можем использовать вышеприведенный класс с любым типом:


 struct STest{
  int val;
  double x;
 };

 int main()
 {
  
  A<int> a_int;
  A<int*> a_pointer_int;
  A<STest> *pa = new A<STest>(new STest());
  ...
  delete pa;

  return 0;
 }

Шаблоны могут иметь несколько параметров, также в качестве параметров могут передаваться другие шаблоны. Для упрощения восприятия шаблонам можно давать эквивалентные имена с помощью ключевого слова typedef. Так, например широко используемый в стандартной STL тип std::string объявляется с помощью typedef:

 
 namespace std{
  typedef basic_string<char>    string;
  typedef basic_string<wchar_t> wstring;
 }

Такая возможность позволяет значительно упростить внешний вид параметров. Вместо

 
 a)
 int main()
 {
  A<std::basic_string<char> > a_int;
  
  return 0;
 }

можно использовать более изящное решение:

 
 б)
 int main()
 {
  A<std::string> a_int;
  
  return 0;
 }

В примере a) прошу обратить внимание на способ записи параметров-шаблонов - между конечными угловыми скобками > > делается пробел, иначе будет ошибка компиляции. А теперь с несколькими параметрами:


 template<class T1,class T2,class T3>
 class A
 {
 public:
  A(const T1&,const T2&,const T3&){}  
 };
 
 int main()
 {
  A<int,double,double*> a(10,20,new double(30));

  return 0;
 }
 

Также шаблоны могут содержать уже определенные аргументы. Так поступают при необходимости получить определенное значение конкретного типа:

 
 template<class T,int v> void func(T t)
 {  
  std::cerr << "T: " << t << '\n';
  std::cerr << "int: " << v << '\n';
 }
 
 int main()
 {
  func<double,20>(100.23);
  return 0;
 }
 

Для шаблонов с определенными аргументами добавлю, что в эти аргументы не являются модифицируемыми, т.е. такие действия не пройдут:


 template<class T,int v> void func(T t)
 {  
  v++; //ошибка на этапе компиляции, - v не модифицирумый тип
  int x = &v; //аналогичная ошибка
  int &x = v; //аналогичная ошибка
  const int &x = v; //правильно

 };

Кроме того, для немодифицируемых аргументов условия сравнения нужно брать в скобки, т.к. первый встреченный '>' будет рассматриваться как конец объявления шаблона:


 template<int i = 10>20> class A{}; //ошибка
 template<int i = (10>20)> class A{}; //нормально

Согласно стандарту (14.1.7) нельзя также объявлять шаблон с параметром-значением типа double, class или void, хотя некоторые компиляторы делают ограничения только на class:


 struct STest{};
 
 template<int d>  class A; //нормально
 template<STest v> class B; //ошибка компиляции
 template<void d> class C; //ошибка компиляции
 template<void* d> class D; //нормально
 template<double d> class E;  //ошибка компиляции
 template<double *d> class F;  //нормально

Шаблонный тип может иметь тип-параметр по умолчанию. В ниже приведенном примере определение интерфейс переменных a и a1 равноценен:


 template<class T = int>
 class A {};
 
 int main()
 {
  A<> a;
  A<int> a1;

  return 0;
 }

Параметр-тип по умолчанию может быть либо последним либо все аргументы шаблона обязаны иметь определенные типы по умолчанию:


 template<class T=int,class T2> class A{}; //ошибка компиляции - нет второго параметра по умолчанию
 template<class T,class T2=int> class B{}; //нормально
 template<class T=double,class T2=int> class C{}; //нормально

В то же время, возможно объявить шаблон за несколько заходов:


 template<class T,class T2=double> class A;
 template<class T=int,class T2> class A;

Это равноценно такому объявлению:


 template<class T=int,class T2=double> class A;

Причем объявление действует в контексте, пока не будет дополнено дополнительным объявлением, т.е.:


 template<class T,class T2=double> class A;
 
 void func1()
 {
  A<> *a;  //ошибка компиляции - отсутствует обязательный параметр
  A<int> *b; //нормально
 }
 
 template<class T=int,class T2> class A;
 
 void func2()
 {
         A<> *a;  //нормально
 }
 
 template<class T,class T2> class A{};

Само собой разумеется, внутри шаблонного класса могут быть определены другие шаблонные классы (вложенные классы) и шаблонные функции:


 template<class T>
 class A
 {
 public:
  template<class X> void anyFunc(const X &x){ A1<X> v; }

 protected:
  template<class T1>
  class A1
  {
  protected:
   T1 *p_t1;
   T *p_t;
  };
 };
 
 int main()
 {
  A<double> a;

  return 0;
 }

Порядок шаблонных аргументов в объявлении и определении обязан совпадать:


 template<class T1,class T2>
 class A
 {
  void func1();
  void func2();
 };
 
 template<class T1,class T2> void A<T1,T2>::func1(){} //нормально
 
 template<class T1,class T2> void A<T2,T1>::func2(){} //ошибка компиляции

Имена шаблонных типов перекрывают глобальные типы поэтому для доступа к глобальным типам и переменным следует использовать оператор ::


 class T{};
 int val;
 
 template<class T,T val> void calcValue(T t)
 {
  T t1 = val;  //здесь работают шаблонные параметры
  ::T t2 = ::val;  //здесь глобальный тип и переменная
 }

Пожалуй, на сегодня хватит. Далее я приведу текст простого шаблонного класса списка указателей, - посмотрите на локальные классы и на способ объявления и определения функций. Что бы Вы могли изменить? Как и почему?



/****************************************************************************************
                          ptrlist.h  -  description
                             -------------------
    begin                : Чтв Июл 24 2003
    copyright            : (C) 2003 by Yuri Gordienko
    email                : iqsoft@cg.ukrtel.net
*****************************************************************************************/

 #include <stdlib.h>

 template<class type>
 class TPtrList
 {
 protected:
  struct SNode{
   type *data;
   SNode *next;
  };

 public:
  class Iterator{
   friend class TPtrList;
  public:
   Iterator():curr(0){}
   Iterator(const Iterator &s):curr(s.curr){}
   bool isValid()const{ return curr!=0; }

   Iterator& operator++(){ if(curr) curr = curr->next; return *this; }
   const Iterator operator++(int){ Iterator tmp(element); ++*this; return tmp; }

   type* operator->()const{ return curr ? curr->data : 0; }
   type* operator*()const{ return curr ? curr->data : 0; }

  protected:
   Iterator(SNode *n):curr(n){}

  private:
   SNode *curr;
  };

  TPtrList(bool autodelete=false):is_autodelete(autodelete),ifirst(0),icurr(0),count_i(0){}
  TPtrList(const TPtrList<type> &s):ifirst(0),icurr(0){}
  virtual ~TPtrList(){ clear(); }

  bool autoDelete()const{ return is_autodelete; }
  void setAutoDelete(bool ad){ is_autodelete=ad; }

  int count()const{ return count_i; }
  bool isEmpty()const{ return !ifirst; }

  void clear();
  bool removeCurrent(){ return icurr ? remove(icurr->data) : false; }

  void append(type *d);
  bool remove(type *d);
  type* find(type *d)const;

  type* first();
  type* last();
  type* next();

  Iterator begin()const{ return Iterator(ifirst); }
  Iterator end()const{ return Iterator(0); }

  const TPtrList& operator=(const TPtrList &s);

 private:
  SNode *ifirst,*ilast;
  SNode *icurr;
  int count_i;
  bool is_autodelete;
 };

 template<class type>
 void TPtrList<type>::clear()
 {
  SNode *p;
  while(ifirst){
   p = ifirst->next;
   if(is_autodelete) delete ifirst->data;
   free(ifirst);
   ifirst = p;
  }
  icurr = 0;
  count_i = 0;
 }

 template<class type>
 void TPtrList<type>::append(type *d)
 {
  if(ifirst){
   ilast->next = (SNode*)malloc(sizeof(SNode));
   ilast = ilast->next;
  }
  else{
   ifirst = (SNode*)malloc(sizeof(SNode));
   ilast = ifirst;
  }
  ilast->data = d;
  ilast->next = 0;
  ++count_i;
 }

 template<class type>
 bool TPtrList<type>::remove(type *d)
 {
  SNode *p = ifirst;
  SNode *p_old = 0;
  while(p){
   if(p->data==d){
    if(is_autodelete) delete p->data;
    if(p->next){
     SNode *p1 = p->next;
     memcpy(p,p1,sizeof(SNode));
     p = p1;
    }
    else{
     if(icurr==p) icurr = 0;
     if(p_old)
      p_old->next = 0;
     else
      ifirst = 0;
     ilast = p_old;
    }
    free(p);
    --count_i;
    return true;
   }
   p_old = p;
   p = p->next;
  }
  return false;
 }

 template<class type>
 type* TPtrList<type>::find(type *d)const
 {
  SNode *p = ifirst;
  while(p){
   if(p->data==d) return p->data;
          p = p->next;
  }
  return 0;
 }

 template<class type>
        inline type* TPtrList<type>::first()
 {
  icurr = ifirst;
  return icurr ? icurr->data : 0;
 }
        
 template<class type>
 inline type* TPtrList<type>::last()
 {
  icurr = ifirst ? ilast : 0;
  return icurr ? icurr->data : 0;
 }
        
 template<class type>
 inline type* TPtrList<type>::next()
 {
  if(icurr){
          icurr = icurr->next;
   if(icurr) return icurr->data;
  }
  return 0;
 }
        
 template<class type>
 const TPtrList<type>& TPtrList<type>::operator=(const TPtrList<type> &s)
 {
         if(this!=&s){
   if(ifirst) clear();
   SNode *p = s.ifirst;
   ifirst = ilast = 0;
   while(p){
           if(!ifirst){
     ifirst = (SNode*)malloc(sizeof(SNode));
     ilast = ifirst;
    }
    else{
            ilast->next = (SNode*)malloc(sizeof(SNode));
     ilast = ilast->next;
    }
    ilast->data = p->data;
        
    p = p->next;
   }
   if(ilast) ilast->next = 0;
   is_autodelete = false;
          count_i = s.count_i;
  }
  return *this;
 }
 
 int main()
 {  
  TPtrList<int> vlist(true);

  //заполнили
  int i;
  for(i=0;i<10;++i)
   vlist.append(new int(i));

  //обойдем и напечатаем через итератор
  TPtrList<int>::Iterator it;
  for(it=vlist.begin();it.isValid();++it)
   printf("%d\n",**it);

  //обойдем и изменим значение через функцию
  for(int *p=vlist.first();p;p=vlist.next())
   *p = i--;
  
  //обойдем и напечатаем через итератор
  for(it=vlist.begin();it.isValid();++it)
   printf("%d\n",**it);
  
  return 0;
 }


На сегодня все. Пожелания, предложения, вопросы прошу на iqsoft@cg.ukrtel.net

В следующем выпуске мы подробно рассмотрим этот пример и детально поговорим о вложенных шаблонных классах, итераторах и шаблонных friend-классах.


(с) Юрий Гордиенко



http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу

В избранное