Перегрузка шаблонов функций

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

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

template T sqrt (T);

template complex sqrt (complex );

double sqrt (double);

void f (complex z)

{ sqrt (2); // sqrt (int)

sqrt (2.0); // sqrt (double)

sqrt ( z ); // sqrt (complex)

}

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

1. В случае, если специализация и обычная функция подходят одинаково отлично, предпочтение отдается простой функции: для sqrt(2.0) выбирается sqrt(double), а не sqrt (double).

2. Ищется комплект специализаций шаблонов функций, каковые примут участие в разрешении перегрузки, с учетом всех шаблонов функции. С вызовом sqrt(z) к рассмотрению будут приняты sqrt (complex) и sqrt (complex).

3. В случае, если смогут быть позваны два шаблона функции, и один из них более специален, чем второй, то он и рассматривается: с вызовом sqrt(z) предпочтение отдается sqrt (complex).

4. Разрешается перегрузка для комплекта шаблонов функций, и для простых функций: для sqrt(2) более правильным соответствием владеет sqrt (int) если сравнивать с sqrt (double).

5. В случае, если ни одного соответствия не отыскано, вызов считается ошибочным. В случае, если процесс заканчивается с одинаково подходящими вариантами, вызов есть неоднозначным и это также неточность.

К примеру:

template max (T, T);

const int s = 7;

void f ()

{ max (1, 2); // max (1, 2)

max (‘a’, ‘b’); // max (‘a’, ‘b’)

max (2.7, 4.9); // max (2.7, 4.9)

max (s, 7); // max (int(s), 7) – употребляется

// тривиальное преобразование

max (‘a’, 1); // неточность: неоднозначность (стандартные

// преобразования не используются

max (2.7, 4); // неточность: неоднозначность (стандартные

// преобразования не используются

}

Две неоднозначности возможно дать добро при помощи явного квалификатора:

void f ()

{ max (‘a’, 1); // max (int (‘a’), 1)

max (2.7, 4); // max (2.7, double (4))

}

Шаблоны классов

Шаблонные классы предоставляют пользователям еще довольно широкие возможности, чем шаблонные функции, разрешая выстроить отдельные классы подобно шаблонам функций. Имя шаблона класса должно быть неповторимым, другими словами перегрузка для шаблона класса не вероятна (в отличие от шаблона функции). Шаблон класса задает параметризованный тип и снабжает скелет обобщенного класса для его последующей реализации посредством типов данных, определенных пользователем. В качестве параметра при задании шаблона класса возможно применять не только тип, но и другие параметры, к примеру, целочисленные значения, но они должны быть константными выражениями.

Класс, генерируемый из шаблона класса, есть совсем обычным классом. Исходя из этого применение шаблона не подразумевает каких-либо дополнительных механизмов времени выполнения сверх того, что употреблялись бы для написания класса вручную.

Объявление шаблона класса имеет следующий неспециализированный вид:

template

class Anyclass

{ // закрытые, защищенные и открытые элементы класса

}; // точка с запятой необходима, как при описании класса,

где Т – неизвестный тип (вероятен любой идентификатор), что нужно задать при применении класса. Как и при шаблонных функций, Т в последствии возможно заменить любым встроенным типом, вторым классом, указателем и т.д. Anyclass – имя шаблонного класса.

В теле шаблона класса тип Т может употребляться для объявления данных-элементов, типов возвращаемых способами значений, прочих элементов и параметров до тех пор пока еще неизвестных типов, к примеру:

Т* ptr; // указатель типа Т

Как и шаблоны функций, шаблоны классов значительно чаще объявляются в заголовочных файлах.

Параметры шаблонов.

Параметрами шаблонов смогут быть: параметры-типы, параметры простых типов, такие как int, и параметры-шаблоны. Конечно, у шаблонов возможно пара параметров. К примеру:

template class Buffer {/*…*/};

Как видно, параметр шаблона возможно использован для определения последующих параметров шаблона. Целые доводы употребляются в большинстве случаев для границ и задания размеров. К примеру:

template

class Buffer

{ T v [n]; // массив типа Т

int sz; // переменная для размера массива

public:

Buffer (): sz (n) { } // конструктор с инициализацией размера

// …

};

Объекты класса Buffer:

Buffer cbuf; // буфер для 127 знаков

Buffer rbuf; // буфер для 8 записей

Довод шаблона возможно константным выражением, адресом объекта либо функции, либо неперегруженным указателем на элемент. Указатель, применяемый в качестве довода шаблона, должен иметь форму of, где of есть именем объекта либо функции, или в форме f, где f есть именем функции. Указатель на элемент должен быть в форме X::of, где of есть именем элемента. В частности, строковый литерал не допустим в качестве довода шаблона. Целый довод шаблона должен быть константой:

void f (int i)

{ Buffer bx; } // неточность: требуется константное выражение

И напротив, параметр шаблона, не являющийся типом, должен быть константой в теле шаблона, исходя из этого попытка трансформации значения этого параметра ведет к неточности.

Элементы шаблона класса объявляются и определяются равно как и для простого класса. Их возможно выяснить как внутри, так и вне шаблона класса. Элементы шаблона класса со своей стороны являются шаблонами, параметризованными при помощи доводов шаблона.

Пример 48.

Представим объявление шаблона класса Database, реализующего базу данных с маленьким числом записей.

template // шаблон класса с неизвестным типом

class Database // имя класса

{ T* rp; // указатель на записи

int num; // число записей

public:

Database (int на данный момент) // встроенный конструктор

{ rp = new T[num=n]; } // указатель на динамическую память

~Database() // встроенный деструктор

{ delete [ ] rp;} // очистка динамической памяти

void Donothing (); // прототип некоей функции

T Getrecord (int recnum); // прототип функции с параметром

// номера записи, возвращающей ссылку на объект типа Т

};

// описание функций шаблона класса:

// пример шаблона некоей функции из шаблона класса Database:

template // шаблон типа Т

void Database :: Donothing () // заголовок функции

{ // тело функции безлюдное

}

//описание функции-элемента шаблона класса Database:

template // шаблон типа Т

T Database :: Getrecord (int recnum) // способ получения записи

{ T* crp = rp; // текущий указатель

if (0

while ( recnum— 0) // цикл, пока число записей 0

crp++; // сдвиг указателя на одну запись

return *crp; // возврат указателя на запись

}

Комментарии:

Разглядим объявление шаблона класса Database более пристально. Природа типа Т малоизвестна, но уже возможно обрисовать указатель rp типа Т и конструктор выделяет динамическую память для массива объектов типа Т, присваивая адрес начала массива указателю rp и устанавливая элемент num равным требуемому числу записей. Деструктор обязан удалить массив с указателем rp посредством оператора delete [ ] rp.

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

В классе объявляется кроме этого способ Getrecord, возвращающий ссылку на объект типа Т, идентифицируемый номером записи recnum. Это еще один пример операции, не требующей знания настоящего типа объекта. В функции указатель rp типа Т присваивается указателю того же типа crp, что ссылается на первую запись, сохраненную в объекте класса Database. Оператор if контролирует, соответствует ли параметр recnum допустимому диапазону значений. В случае, если да, то цикл while сокращает параметр recnum до нуля и смещает указатель crp на одну запись размера sizeof (T) в базе данных. При компиляции вместо Т будет использован тип настоящих объектов. Функция возвращает разыменованное значение указателя crp, другими словами ссылку на любой объект, адресуемый crp.

Потому, что шаблонный класс Database и его шаблонные функции являются объявлениями, включим их в заголовочный файл db.h, дабы воспользоваться им после этого в главной программе.

Пример 49.

Заголовочный файл db.h используем для реализации настоящего класса Record с целью получения базы данных четырьмя методами создания объектов класса посредством шаблона класса. Программу сохраним в файле database.cpp в директории, где находится файл db.h:

# include

#include

#includedb.h

// настоящий класс Record для записей в базе данных:

class Record

{ char name{41}; // символьный массив – private

public:

Record () // конструктор по умолчанию

{ name[0] = 0;} // обнуление базы данных (БД)

Record ( const char* s) // конструктор заполнения БД

{ Assign(s); } // функцией копирования

void Assign (const char* s) // способ копирования строчка s

{ strncpy(name, s, 40); } // как записи в БД

char* Getname () // способ возврата указателя

{ return name; } // на строчок записи в БД

};

void main() // основная функция

{ int rn; // индекс числа записей

clrscr();

// создание объектов посредством шаблона класса Database:

Database db (3); // БД из 3 записей типа Record

Database dbp (3); // БД из 3 указателей типа Record

Database *pdb; // указатель на БД

Database *ppdb; // указатель на БД указателей

// создание БД из записей типа Record:

cout

db.Getrecord(0). Assign (A. Pushkin);

db.Getrecord(1). Assign (M. Lermontov);

db.Getrecord(3). Assign (L. Tolstoy);

for (rn = 0; rn 3; rn++)

cout

// создание БД из указателей типа Record:

cout

dbp.Getrecord(0) = new Record (I. Repin);

dbp.Getrecord(1) = new Record (V. Serov);

dbp.Getrecord(2) = new Record (M. Vrubel);

for (rn = 0; rn 3; rn++)

cout

// создание указателя на БД записей типа Record:

cout

pdb = new Database (3);

pdb-Getrecord (0). Assign (Rafael);

pdb-Getrecord (1). Assign (Leonardo);

pdb-Getrecord (2). Assign (Mikelangelo);

for (rn = 0; rn 3; rn++)

cout

// создание указателя на БД из указателей типа Record:

cout

ppdb = new Database (3);

ppdb-Getrecord(0) = new Record (A. Einshtein);

ppdb-Getrecord(1) = new Record (L. Landau);

ppdb-Getrecord(2) = new Record (A. Sakharov);

for (rn = 0; rn 3; rn++)

cout Getname()

getch();

}

Комментарии к программе:

Заявлен класс Record, что программа применяет для запоминания в Database. Класс Record весьма несложен, в нем заявлен в качестве элемента лишь символьный массив name. В действительности вместо Record возможно любой произвольный класс, поскольку в шаблоне класса Database нет ограничений на тип объектов.

В функции употребляются четыре метода создания объектов посредством шаблона класса. В объявлении

Database db (3);

определяется объект db с именем шаблонного класса Database и задается Record в качестве класса, замещающего Т в шаблоне. Выражение в скобках (3) – это инициализатор, передаваемый конструктору класса Database.

Вместо применения типа Record возможно выяснить базу данных из 100 вещественных значений:

Database dbd (100);

Потому, что класс Database написан для сохранения объектов произвольных типов, он именуется контейнерным классом. В большинстве случаев, наилучшие контейнерные классы являются абсолютно обобщенными.

В функции следующее объявление

Database dbp (3);

определяет объект dbp как указатель класса Database, складывающийся из трех указателей на Record.

Третье объявление

Database *pdb;

определяет pdb как указатель на объект класса Database с незаданным числом объектов типа Record.

Последнее объявление

Database *ppdb;

объединяет прошлые два объявления для указателя на базу данных указателей, каковые ссылаются на объекты типа Record.

В программе объект класса Database употребляется равно как и каждый не шаблонный объект. К примеру, для базы данных первым методом в операторе

db.Getrecord (0). Assign (A. Pushkin);

объектом db вызывается функция Getrecord (0) для доступа к записи с индексом 0. После этого в строчке вызывается способ Assign этого объекта для задания строкового значения.

В программе демонстрируется кроме этого применение вторых объектов класса Database. Воображает интерес второй метод заполнения базы данных посредством указателя следующим оператором:

dbp.Getrecord (0) = new Record (I. Repin);

Потому, что функция Getrecord возвращает ссылку на объект указателю dbp, она может употребляться в левой части оператора присваивания. В этом случае создается новый объект типа Record, а его адрес присваивается в базе данных указателю записи с индексом 0, ссылку на которую возвращает функция Getrecord.

В третьем методе для заполнения базы данных оператором

pdb = new Database (3);

создается указатель pdb типа Database на базу данных из трех объектов-записей класса Record, каковые заполняются способом Assign с применением оператора, к примеру,

pdb-Getrecord (0). Assign (Rafael);

Четвертый метод объединяет два прошлых для указателя ppdb типа Database на массив указателей типа Record:

ppdb = new Database (3);

Посредством указателей создаются новые объекты-записи типа Record:

ppdb-Getrecord(0) = new Record (A. Einshtein);

Принципиально важно то, что во всех этих примерах не требуется информировать компилятор посредством приведения типов о типах данных, применяемых классом Database. Благодаря обобщенной природе шаблонов, возможно избежать приведения типов и создавать классы, каковые смогут поддерживать множество разных типов данных.

Примечание.

Четыре примера объявлений Database из функции main возможно применять в качестве примера для объектов большинства типов шаблонов класса. Создание шаблонов с нуля – достаточно тяжёлая задача. Исходя из этого несложнее всего создать рабочий класс, что обрабатывает настоящие эти, а после этого преобразовать его в универсальный шаблон, заменив настоящие типы данных метками-заполнителями .

Приятели

Дружественные функции

Одним из ответственных правил С++ есть защита данных от несанкционированного применения посредством режимов доступа к элементам класса. В большинстве случаев доступ к собственным (private) элементам класса ограничивается способами этого класса. Но появляются обстановке, в то время, когда нужно, дабы к закрытым элементам данных класса имела доступ функция, не принадлежащая этому классу а также из другого класса. Это возможно сделать, заявив эту функцию посредством главного слова friend (приятель) как дружественную функцию (friend function). К примеру,

class X

{ int n;

public:

friend void fr (void);

};

Функция fr может обращаться к элементу n.

Простое объявление функции-элемента гарантирует три логически различные вещи:

  • функция в праве доступа к закрытой части объявления класса;
  • функция находится в области видимости класса;
  • функция обязана вызываться для объекта класса (имеется указатель this).

Заявив функцию как friend, мы наделяем ее лишь первым свойством. Обстоятельством введения функций-друзей явилась обстановка, в то время, когда одинаковая функция обязана применять закрытые элементы двух либо более классов. Объявление друзей не есть (в общем случае) нарушением принципа инкапсуляции данных, потому, что сам класс разрешает доступ функции-приятелю к закрытой части класса. Это возможно разглядывать как модель отношения людей, в то время, когда отечественные родные приятели смогут приходить к нам в дом, в отличие от чужих людей.

Объявление friend возможно поместить и в закрытой и в открытой части объявления класса. Кроме этого как и функции-элементы, функции-приятели очевидно указываются в объявлении класса, друзъями которого они являются. Исходя из этого они в той же мере являются частью интерфейса класса, как и функции-элементы.

Пример 50.

Представим программу применения функции-приятеля для двух классов (Box, Line). Она обязана изобразить на экране линии и цветные прямоугольники, сравнивая попарно их цвета (color) функцией-втором samecolor(). В случае, если цвета совпадают, выдается сообщение Same color, в противном случае Different color.

#include

#include

class Line; // предварительное объявление класса Line

class Box // класс прямоугольник

{ int color; // цвет рамки

int upx, upy; // координаты левого верхнего угла

int lowx, lowy; // координаты правого нижнего угла

public:

friend int samecolor (Line l, Box b); // функция-приятель

void setcolor (int c); // цвет

void definebox (int x1, int y1, int x2, int y2); // координаты

void showbox (); // вывод на экран

};

class Line // класс линия

{ int color; // цвет линии

int x0, y0; // координаты начала линии

int len; // протяженность линии

public: // прототипы функций:

friend int samecolor (Line l, Box b); // функция-приятель

void setcolor (int c); // цвет рамки

void defineline (int x, int y, int l); // координаты и протяженность линии

void showline (); // вывод линии

};

// Описание способов класса Box:

void Box :: setcolor (int c) // установка цвета

{ color = c;

}

void Box :: definebox (int x1, int y1, int x2, int y2) // координаты

{ upx = x1; upy = y1; lowx = x2; lowy = y2;

}

void Box :: showbox () // способ вывода прямоугольника

{ window(upx, upy,lowx, lowy); // координаты окна

textbackground (color); // установка цвета фона

clrscr(); // чистка окна

textbackground (BLACK); // цвет фона

window (1, 1, 80, 25); // окно экрана

}

// Описание способов класса Line:

void Line :: setcolor (int c) // установка цвета линии

{ color = c;

}

void Line :: defineline (int x, int y, int l) // определение линии

{ x0 = x; y0 = y; len = l; // координаты и протяженность линии

}

void Line :: showline () // способ вывода линии

{ textcolor (color); // цвет линии

gotoxy (x0, y0); // начало линии

for (int k=0; k

cprintf(%c, ‘-‘);

textcolor (WHITE); // смена цвета линии

}

// Описание функции-приятеля:

int samecolor (Line l, Box b)

{ if (l.color == b.cololr) // в случае, если окна и цвета линии однообразны, то

return 1; // возврат 1, в противном случае

return 0; // возврат 0

}

void main () // основная функция

{ Line l; // создание объекта линии

Box b; // создание объекта прямоугольника

textbackground (BLACK); // установка цвета фона

clrscr (); // чистка экрана

b.definebox (5, 5, 25, 10); // задание координат прямоугольника

b.setcolor (RED); // цвет прямоугольника

l.defineline (5, 15, 30); // длины линии и задание координат

l.setcolor (BLUE); // цвет линии

b.showbox (); // вывод прямоугольника

l.showline (); // вывод линии

gotoxy (1,1); // координаты курсора

if (samecolor (l, b)) // в случае, если окна и цвета линии однообразны

cputs (Same colors\n); // сообщение:Цвета однообразны

else cputs (Different colors\n); // в противном случае Цвета разны

getch (); // задержка экрана

}

Комментарии к программе.

В программе дано предварительное объявление класса Line, похожее на прототип (class Line;), которое употребляется в классе Box, перед тем как выяснен класс Line, исходя из этого нужно сделать такое уведомление.

Дружественной возможно не только внешняя функция, как в примере, но и способ другого класса. К примеру:

class X
{ // …

int* next();

};

class Y

{ friend int* X:: next();

// …

};

В примере, рассмотренном выше, возможно было бы заявить функцию samecolor способом класса Box, поменяв его описание:

class Box

{ …

public:

int samecolor ( Line l); // способ класса Box

};

В классе Line нужно заявить функцию-приятеля:

class Line

{ …

public:

friend int Box::samecolor (Line l); // функция-приятель

};

В классе Line использовано полное имя функции Box::samecolor(). Помимо этого, возможно не показывать в качестве довода объект класса Box. Новая функция-приятель Box::samecolor() примет вид:

int Box::samecolor ( Line l )

{ if (l.color == color) // употребляется указатель this — color

return 1;

return 0;

}

Дружественные классы

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

class X {…};

class Y

{ …

friend class X; // дружественный класс

};

Ясно, что классы-приятели должны употребляться лишь для отражения тесно связанных концепций. Объявление класса Х втором предполагает, что закрытые и защищенные элементы класса Y смогут употребляться в классе Х, другими словами любой способ класса Х может иметь доступ к закрытым элементам класса Y. Довольно часто существует выбор между реализацией класса в качестве элемента (положенного класса) либо в качестве приятеля.

Дружба классов не наследуется и не транзитивна (как и в жизни людей). В случае, если некто В есть втором А, а D – приятель В, то из этого не нужно, что D – приятель А.

К примеру:

class А

{ friend class В;

int a;

};

class B

{ friend class C;

};

class C

{ void f (A* p)

{ p- a++; // неточность: С – не приятель класса А, не смотря на то, что он приятель класса В

}

};

class D: public B

{ void f (A* p)

{ p- a++; // неточность: D – не приятель А, не смотря на то, что он производный от В

// приятеля класса А

}

};

Время от времени возможно встретить неизвестную операцию, заявленную как дружественная, но, по большому счету говоря, функции-приятели должны употребляться экономно – если они присутствуют в проекте, то обычно это показатель того, что иерархия классов испытывает недостаток в исправлении. Объявление спецификатора friend снижает одно из главных преимуществ ООП – инкапсуляцию данных и функций и модульность. (Если вы предоставили собственную квартиру на время отпуска другу, то, возвратившись, вы имеете возможность застать ее не в том порядке, каком оставляли.) Лучше, дабы как возможно больше классов были друг другу чужаками. Объявление friend должно употребляться лишь тогда, в то время, когда это вправду нужно, к примеру, при переплетенной иерархии классов. В частности, в случае, если появляется необходимость сделать целый класс дружественным по отношению к второму классу, направляться рассмотреть возможность создания неспециализированного производного класса с целью доступа через его посредство к требуемым элементам.

При работе с дружественными классами направляться учитывать следующие события.

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

Пример 51.

Разглядим программу, демонстрирующую, как дружественный класс приобретает доступ к закрытым и защищенным элементам другого.

#include

class Pal

{ friend class Friend; // Friend – приятель класса Pal

private:

int x; // дешёв в Pal и Friend

protected:

void dx () { x *= x;} // дешёв в Pal и Friend

public:

Pal () { x=100; } // конструктор – дешёв всем пользователям

Pal ( int n ) { x = n; } // конструктор – дешёв всем пользователям

};

class Friend // класс-приятель класса Pal

{ private:

Pal palob; // объект класса Pal дешёв способам Friend

public:

void Showvalues (); // способ дешёв всем пользователям

};

void Friend::Showvalues () // способ вывода дешёв всем пользователям

{ Pal apal (1234); // иницирован объект класса Pal

cout

palob.dx ();

cout

cout

}

void main ()

{ Friend afriend; // создан объект класса Friend

afriend.Showvalues (); // вывод данных объектов класса Pal

}

Результаты программы:

Before, palob = 100

After, palob = 10000

apal.x = 1234

Комментарий к программе:

Программа начинается с объявления класса Pal (друг). Класс Friend объявляется как приятель класса Pal. Значит, способы класса Friend имеют доступ ко всем элементам класса Pal. А класс Pal не имеет доступа к закрытым и защищенным элементам класса Friend. В классе Friend объявляется закрытый объект класса Pal с именем palob.

Запустив на исполнение программу, возможно убедиться, что, не смотря на то, что класс Friend не пребывает в родстве с Pal, способ Friend::Showvalues имеет яркий доступ к закрытому элементу х и к защищенному способу dx класса Pal. Если бы Friend не был втором Pal, компилятор бы не дал право на существование таким выражениям, как palob.dx и apal.x.

Перегрузка и шаблоны функций.


Интересные записи:

Понравилась статья? Поделиться с друзьями: