Взаимно дружественные классы 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Взаимно дружественные классы



Друзья

Одно из основных преимуществ ООП — инкапсуляция данных в классе.

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

В C++ можно обойти правила инкапсуляции с помощью друзей.

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

Классы в C++ позволяют объявлять два вида друзей.

Один класс как единое целое может быть другом другого класса, или же другом может объявляться только одна функция.

Если друзья имеют брата-близнёца в C++, так это оператор goto. Подобно goto, друзья позволяют вам пренебречь правилами, призванными помочь вам написать надежный код. Не подумайте, что следующий раздел поощряет использование друзей.

Асы в программировании на C++ пользуются друзьями только в случае крайней необходимости.

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

В классе можно объявить другой класс дружественным. Один класс (в котором объявляется друг) дает другому классу (другу) возможность доступа ко всем закрытым и защищенным членам первого класса.

Открытые члены всегда доступны,

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

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

 

Пример: объявили следующий класс:

class AClass { private:

double value;

// Закрытый член класса

public:

AClass() { value = 3.14159; }

};

в классе не предусмотрены средства

просмотра значения value объекта

класса. Закрытый член находится

в полной безопасности

Класс AClass содержит один закрытый член value типа double. Конструктор класса присваивает этому чле­ну значение 3.14159. За исключением этого действия, в классе не предусмотрены даже средства просмотра значения value объекта класса. Закрытый член находится в полной безопасности, как медвежонок под защи­той мамаши-медведицы.

Далее, предположим, что объявили еще один класс, содержащий членом объект класса AClass:

class BClass { private:

AClass anObject;

// член объекта класса AClass

// закрыт в классе BClass

public:

void ShowValue(void)

{ cout << anObject.value; }//???

// объявление не скомпилируется, //т.к. член value закрыт в классе //AClass и только функции-члены //AClass имеют к нему доступ.

 

Член anObject типа AClass закрыт в классе BClass. Функция-член ShowValueO пытается отобразить value из anObject. Однако объявление не скомпилируется, поскольку член value закрыт в классе AClass и только функции-члены AClass имеют к нему доступ.

Замена в классе AClass спецификатора доступа private на protected для члена value не решает проблему. Защищенный член доступен в классе, в котором он объявлен, и в любых производных из него классах. Не­смотря на то что класс BClass обладает объектом типа AClass, два этих класса не связаны между собой отно­шением родства, и члены BClass не имеют доступа к закрытым и защищенным членам AClass.

Можно сделать член value

открытым в классе AClass, но это сделает value доступным и во всех операторах.

Лучшее решение — сделать класс BClass другом класса AClass. Объекты BClass будут иметь доступ к value и всем другим закрытым и защищенным членам, а операторы вне двух классов будут лишены возможно­сти доступа к запрещенным частям класса.

Для этого следует указать ключевое слово friend внутри класса, к которому необходимо получить доступ в другом классе.

В данном примере класс BClass получит доступ к за­крытому члену value внутри AClass. Таким образом, для того, чтобы класс BClass получил доступ к этим за­крытым данным в классе AClass, следует первый класс объявить дружественным второму.

Пример: новое объявле­ние класса AClass:

class AClass {

friend class BClass;

//класс BClass – друг класса AClass

private:

double value;

//доступен членам классов AClass и BClass

public:

AClass() { value = 3.14159;}

};

Единственное отличие от предыдущего объявления — строка friend class BClass, дающая указание компи­лятору предоставить классу BClass доступ к защищенным и закрытым членам класса AClass. Другие операто­ры в других классах и в основной программе, как и раньше, не имеют доступа к объявленным закрытыми и защищенными членам класса AClass.

Только BClass может частным образом проникнуть в закрытую "комнату" AClass.

Можно объявить любое число классов друзьями. Единственное ограничение — ключевое слово friend д.б. внутри объявления класса.

 

Полезно запомнить еще некоторые факты.

• В классе д.б. перечислены все его друзья.

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

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

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

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

 

Взаимно дружественные классы

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

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

Чтобы два класса стали взаимно

дружественными, в каждом из классов следует указать другой дружест­венным, что предоставит доступ каждому из них к закрытым и защищенным членам другого класса. Пример: Чтобы AClass и BClass стали взаимно дружественными, их следует объявить таким образом:

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

//объявление класса

class BClass;

class AClass {

friend class BClass;

};

class BClass {

friend class AClass;

};

Все функции-члены в каждом из классов теперь имеют доступ к закрытым и защищенным членам объекта другого класса.

Если в первом классе имеется ссылка по имени на второй класс, например в параметре функ­ции-члена, то может понадобиться предварительное неполное объявление класса, как это сделано в данном случае для BClass. Это позволит в AClass объявлять данные-члены и параметры функций-членов типа BClass несмотря на то, что полное объявление BClass следует позже.

 

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

Дружественные ф-ции похожи (однако менее обременительны) на дружественные классы.

Объявление функции другом класса открывает ей доступ к закрытым и защищенным членам объекта класса.

Дружествен­ные функции могут быть обычными функциями C++ или членами класса.

Обычно дружественная функция объявляется с параметрами-классами, с которыми она "водит" дружбу. Внутри дружественной функции операторы имеют доступ к обычно скрытым членам объекта класса, передан­ного аргументом функции.

В листинге 4.2 демонстрируется объявление и использование типичной дружественной двум классам функции.

В FRIENDFN объявляются два класса, One и Two. Неполное объявление класса в строке 3 позволяет ссы­латься в One на Two. В строках 6 и 14 объявляется дружественная функция с именем ShowO. Поскольку в каждом классе функция ShowO объявляется дружественной, операторам ShowO разрешается доступ к закры­тым и защищенным членам классов One и Two. (Классы, приведенные здесь, не имеют закрытых секций, но даже если бы они их имели, дружественная функция все равно имела бы доступ к членам этих секций.)

Листинг 4.2. FRIENDFN.CPP

(использование дружественных

двум классам функций)


 

1: «include <iostream.h>

2: class Two;

// Неполное объявление класса

3: class One {

// дружественная ф-ция с именем Show()

4: friend void Show(0ne &c1, Two &c2);

5: private:

6: char *s1;

// Доступен классу One и функции Show()

9: public:

10: One() { s1 = "Testing "; }

11: };

12:

13: class Two {

14: friend void Show(0ne &c1, Two &c2);

15: private:

16: char *s2;

// Доступен классу Two и функции Show

17: public:

18: Two() { s2 = "one, two, three"; }

19: };

20:

Main()

22: {

23: One c1;

24; Two c2;

25:

26: Show(c1, c2);

27: return 0;

28: }

29:

//Show() дружественна обоим классам

30: void Show(0ne &c1, Two &c2)

31: {

32: cout «c1.s1 «c2.s2 «'\n';

33: }

//операторы внутри Show() имеют //доступ к защищенным и за­крытым //членам объектов классов-аргументов, //переданных функции

Функция Show() объявлена с двумя ссылочными параметрами cl и с2 двух типов классов. Поскольку функция ShowO дружественна обоим классам, операторы внутри ShowO имеют доступ к защищенным и за­крытым членам объектов классов-аргументов, переданных функции.

Например, в реализации функции ShowO (строки 30-33) отображаются значения строк, адресованных указателями si и s2. Поскольку функция' ShowO дружественна обоим классам, она может ссылаться непо­средственно на данные-члены si и s2, закрытые в своих классах. Другие "недружественные" функции не име­ют такого доступа к скрытым членам этих классов.

 

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

Обычно в классе объявляется дружественной функция-член другого класса.

Эта ф-ция-член имеет доступ к закрытым и защищенным членам класса, в котором она объявлена дружественной.

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

 

ПРИМЕР:

Листинг 4.3. FRIENDMF.СРР (использование дружественных функций-членов)

include <iostream.h>

// Неполное объявление класса

class One;

class Two {

private:

// Доступен членам класса Two

char *s2;

public:

Two() { s2 = "one, two, three"; }

//!! Сначала прототип ф-ции-члена

void Show(0ne &c1);

}

class One {

//!!потом дружественная ф-ция-член Show

friend void Two::Show(0ne &d);

private:

// Доступен в классе One и в Two::Show

char *s1;

public:

0ne() { s1 = "Testing ";

};

Main()

{

One c1;

Two c2;

c2.Show(c1);

return 0;

}

void Two::Show(One &c1)

{ cout << c1.s1 <<s2 << '\n';}

//Ф-ция Show - член класса Two, имеет

// доступ ко всем членам класса Two.

Как и в модуле FRIENDFN (листинг 15.2), в новой программе FRIENDMF объявляется два класса, One и Two. В классе One объявляется аналогичная дружественная функция-член (строка 14) с помощью имени класса и оператора разрешения области видимости (Two::), чтобы указать компилятору местонахождение функции ShowO.

Порядок объявления двух классов изменился по сравнению с приведенным в предыдущем листинге на об­ратный, поскольку класс, в котором содержится прототип функции-члена, должен объявляться до класса, указывающего функцию-член дружественной. Для класса One, объявляющего дружественной функцию Two::Show(), объявление класса Two уже должно быть доступно компилятору.

Еще одно отличие состоит в способе доступа к закрытым данным обоих классов в функции ShowO (строки 30-33). Объявлен только один ссылочный параметр One &cl. Поскольку функция ShowO — член класса Two, она имеет непосредственный доступ ко всем членам класса Two. Выражение cl.sl в операторе вывода в поток в строке 32 не является недозволенным, так как функция ShowO дружественна классу One, которому принадлежит закрытый член si. Функция-член ShowO может ссылаться на s2 непосредственно, поскольку этот член принадлежит классу Two, в котором объявлена ShowO.

Поскольку функция ShowO — член класса Two, в ней определен указатель this, ссылающийся на объект класса, для которого была вызвана функция-член. Поэтому в программе должен объявляться объект класса Two (строка 24), затем вызываться функция ShowO для этого объекта (строка 26).

Перегрузка операторов

Тема перегрузки операторов едва ли не стала предметом суеверного поклонения.

Перегрузка операторов позволяет определять действия для объектов класса в выражениях, использующих обычные операторы, такие как сложение (+) или вычитание (-).

Пример: Можно объявить класс, подобный TClass, а затем и несколько объектов этого класса с соответствующим образом переопределенными операторами:

TClass c1, с2, сЗ;

 

Можно использовать эти объекты в выражениях вида:

c3 = с1 + с2;

 

Для лучшего понимания сути перегрузки операторов полезно провести ревизию того, что вам известно об операторах вообще. Конечно, обычный оператор знак плюс (+) суммирует два значения. Знак минус (—) осуществляет операцию вычитания.

(+)() Эти и другие символы называются бинарными операторами, т.к. им необходимы два аргумента.

Другие, например, оператор отрицания (!), — унарные, им требуется только один аргумент. Еще один пример — унарный минус (-). Выражение -count меняет знак значения count.

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

Перегрузка операторов объявляется так же, как и обычная дружественная функция или функция-член класса.

 

Main()

{

TStrOp a = "1234";

TStrOp b = "4321";

cout << "\nValue of a == " <<a.GetValue();

cout <<"\nValue of b == " <<b.GetValue();

cout <<"\na + b + 6 == "<< (a+b+6)<<"\n";

cout <<"\na - b + 10 == " << (a-b+10)<<"\n";

getch();

return 0;

}

TStrOp:: TStrOp(const char *s) {

strncpy(value, s, 11);

value[11] = 0;

}

long TStrOp::operator+(TStrOp b) {

return (atol(value) + atol(b.value));

}

long TStrOp::operator-(TStrOp b)

{ return (atol(value) - atol(b.value));

}

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

//Для сложения двух строковых //значений operator+() суммирует //длинные целые значения, //эквивалентные выражениям

// this->value и b.value.

Рассмотрим перегруженные операторные функции-члены, объявленные в строках 12-13 и реализованные в строках 32-33. Поскольку эти функции — члены класса TStrOp, они уже имеют доступ к защищенным и за­крытым членам класса, таким образом, не надо их указывать друзьями класса. Кроме того, эти функции-члены получают указатель this, ссылающийся на объект, для которого они были вызваны. Следовательно, функ­циям необходим всего лишь один параметр, а не два, как ранее. Для сложения двух строковых значений operator+() суммирует длинные целые значения, эквивалентные выражениям this->value и b.value.

Функциям-операторам и функциям-членам необходимо соответствующее число параметров. Вы не сможете объявить operator+О в строке 12 следующим образом:

long operator+(TStrOp a, TStrOp b); //???

Эта строка не скомпилируется, поскольку operator+О — член класса TStrOp и как функция-член получает указатель this, ссылающийся на объект класса. С учетом указателя this и аргументов а и b всего функция име­ет три параметра, а перегруженному бинарному оператору, подобному operator+О, необходимы только два. Для сложения двух объектов класса TStrOp функция operator+О суммирует *this.value и b.value (строка 37). Ей не нужен третий параметр. Функция-член operator-О также получает указатель this и, следовательно, нуж­дается только в одном аргументе для перегрузки бинарного оператора вычитания (строки 40-43).

Main()

{

TStrOp a = "1234";

TStrOp b = "4321";

cout << "\nValue of a == " <<a.GetValue();

cout <<"\nValue of b == " <<b.GetValue();

cout <<"\na + b + 6 == "<< (a+b+6)<<"\n";

cout <<"\na - b + 10 == " << (a-b+10)<<"\n";

cout <<"\n-a== "<< (-a)<<"\n";

getch();

return 0;

}

TStrOp:: TStrOp(const char *s) {

strncpy(value, s, 11);

value[11] = 0;

}

long TStrOp::operator+(TStrOp b) {

return (atol(value) + atol(b.value));

}

long TStrOp::operator-(TStrOp b)

{ return (atol(value) - atol(b.value));

}

long operator-(TStrOp a){

return (-atol(a.value));

}

 

Но­вая функция, не являющаяся членом класса, не получит указателя this.

Для реализации новой функции пере­грузки унарного минуса, не являющейся членом класса, добавьте следующие строки после функции main():

long operator-(TStrOp a)

return - atol(a.value);

Поскольку унарный- оператор "минус" — друг класса, оператор return имеет непосредственный доступ к закрытому члену параметра а, имеющему тип класса TStrOp. Теперь функция закончена, и компилятор может вычислить выражения, использующие унарный минус и объекты этого типа класса. Например, добавьте сле­дующий оператор в функцию main О (лучше всего после строки 20):

cout «"\n-a == " << -а;

 

Скомпилируйте и запустите на выполнение модифицированную программу. Как вы видите, выражение -г заменено на операцию отрицания длинного целого представления строкового члена а. Вы научили C++ выполнять операцию отрицания над длинными целыми значениями, представленными в строковой форме!

можно объявить перегруженные унарные операторы функциями-членами.

Беря за основу первоначальную копию STROPS2.CPP, добавьте следующее объявление над строкой 14 (перед закрывающей скобкой класса TStrOp):

long operator-(void);

#include <iostream.h>

#include <stdlib.h>

#include <string.h>

#include <conio.h>

class TStrOp {

private:

char value[12];

public:

TStrOp() { value[0] = 0; }

TStrOp(const char *s);

long GetValue(void)

{ return atol(value); }

long operator+(TStrOp b);

long operator-(TStrOp b);

long operator-(void);

};

Main()

{

TStrOp a = "1234";

TStrOp b = "4321";

cout << "\nValue of a == " <<a.GetValue();

cout <<"\nValue of b == " <<b.GetValue();

cout <<"\na + b + 6 == "<< (a+b+6)<<"\n";

cout <<"\na - b + 10 == " << (a-b+10)<<"\n";

cout <<"\n-a== "<< -a<<"\n";

getch();

return 0;

}

TStrOp:: TStrOp(const char *s) {

strncpy(value, s, 11);

value[11] = 0;

}

long TStrOp::operator+(TStrOp b) {

return (atol(value) + atol(b.value));

}

long TStrOp::operator-(TStrOp b)

{ return (atol(value) - atol(b.value));

}

long TStrOp::operator-(void){

return (-atol(value));

// аналогично -atol(this->value)

}

Это объявление функции-члена похоже на объявление предыдущей дружественной унарной функции.

По­скольку все функции-члены получают указатель this, новая функция-член operator-() объявляется без пара­метров и оперирует непосредственно длинным целым эквивалентом значения this->value. Это демонстрируется в реализации функции, которую вы можете вставить после функции main():

long TStrOp::operator-(void)

return -atol(value); // аналогично -atol(this->value)

Перегруженная функция-член возвращает длинное целое значение value с обратным знаком. Его можно ис­пользовать в выражениях, подобных следующему, которые можно вставить в функцию main():

cout «"\п-а == " << -а;

Преобразование типов

можно определить свои собственные правила преобразования типов данных для автоматического пре­образования объектов в другие типы. Например, предположим, что объявили и инициализировали объект класса TStrOp из STROPS2.CPP (листинг 15.5) следующим образом:

TStrOp myValue = "9876";

...

Класс TStrOp запоминает строку, которая может использоваться как эквивалент длинного целого значения в операциях сложения и вычитания. можно логически попытаться присвоить объект myValue переменной длинного целого типа:

long х = myValue; //???

Конечно, этот оператор не скомпилируется, поскольку в классе TStrOp не предусмотрено преобразование строки в длинное целое значение.

Решение этой проблемы заключается в использовании перегрузки операции преобразования типов для преобразования объекта класса TStrOp к другому типу данных.

Операторы преобразования принимают форму оператора TYPE, где TYPE — тип данных, к которому необходимо преобразовать объект класса.

Например, для задания правила преобразования типа объектов класса TStrOp в длинное целое надо вставить следующую встраиваемую функцию внутрь открытой секции класса:

operator long() { return atol(value); }

#include <iostream.h>

#include <stdlib.h>

#include <string.h>

#include <conio.h>

class TStrOp {

private:

char value[12];

public:

TStrOp() { value[0] = 0; }

TStrOp(const char *s);

long GetValue(void)

{ return atol(value); }

long operator+(TStrOp b);

long operator-(TStrOp b);

long operator-(void);

//оп-ор преобразования типов

operator long(){return atol(value);}

};

Main()

{

TStrOp a = "1234";

TStrOp b = "4321";

long x=a;

cout << "\nValue of a == " <<a.GetValue();

cout <<"\nValue of b == " <<b.GetValue();

cout <<"\na + b + 6 == "<< (a+b+6)<<"\n";

cout <<"\na - b + 10 == " << (a-b+10)<<"\n";

cout <<"\n-a== "<< -a<<"\n";

cout <<"\n x== "<< x<<"\n";

getch();

return 0;

}

TStrOp:: TStrOp(const char *s) {

strncpy(value, s, 11);

value[11] = 0;

}

long TStrOp::operator+(TStrOp b) {

return (atol(value) + atol(b.value));

}

long TStrOp::operator-(TStrOp b)

{ return (atol(value) - atol(b.value));

}

long TStrOp::operator-(void){

return (-atol(value));

}

Подобное использование оператора ключевого слова специально обеспечивается в C++ для создания новых правил преобразования типов. С новым правилом появилась возможность присвоить объект типа TStrOp пере­менной длинного целого типа, передав объект TStrOp в качестве параметра функции, возвращающей длинное целое, и т.д.

Main()

{ clrscr();

PArr pa(10, 20, 30, 40);

for (int i=0; i <= 3;i++)

cout<< "pa[" <<i <<"] == "<<pa[i] <<"\n";

getch();

return 0;

}

Оператор вызова функции

Перегрузка вызова функции operator() делает объект класса похожим на функцию, которую можно вы­звать.

Перегруженная функция operator() может возвращать значения заданных типов или вовсе ничего не возвращать.

В ней также могут объявляться параметры.

Она не может быть статическим членом класса.

Пример4.20 класса с перегруженным оператором вызова функции, возвращающим целочисленное значение:

#include <iostream.h>

#include <conio.h>

class TAClass {

int х;

public:

//Перегруженная функция-член

int operator() (void);

TAClass (int n) { x = n; } };

//В классе TAnyClass перегружается //оператор вызова функции с //помощью объявления int //operator()(void);

//Можно заменить void списком //необходимых параметров. //Перегруженная функция-член

int TAClass::operator()(void)

{return x; }

В этом примере функция operatorO возвращает целочисленные значения члена х объекта TAClass.

main() {clrscr();

TAClass object = 100;

int q = object(); // вызов функции!

//выполняется оператор

// object. Operator()();

cout << q;

getch();

return 0; }

 

Второй оператор похож на вызов функции с именем objectO, но на самом деле выполняется оператор object. Operator()();.

 

 

TAClass(int xx, int yy)

{ x = xx; y = yy; }

TAClass* operator->();

//Функция-член operator->() //возвращает объект, ссылку или //указатель на объект класса.

//В данном случае возвращает //указатель на класс TAClass.

int GetX(void) { return x; }

int GetY(void) { return y; }

};

TAClass* TAClass:: operator->()

{

cout << "\n Доступное данное: ";

//указатель, ссылающийся на объект, //для которого была вызвана //функция-член

return this;

}

Main()

{

TAClass t(100, 200);

cout << t->GetX() << '\n';

cout << t->GetY() << '\n';

// На самом деле вып-ся так:

//cout«(t.operator->())->GetX() //«'\n';

//cout«(t.operator->())->GetY() //«'\n';

getch();

return 0;

}

 

 

Результат:

Accessing member: 100

Accessing member: 200

 

Функция-член operator->() должна возвращать объект, ссылку или указатель на объект класса. В данном I случае она возвращает указатель на класс TAnyClass.

Оператор реализуется следующим образом:

I

TAClass* TAClass::operator->()

{

cout «"\nAccessing member: ";

return this;

}

В данном примере демонстрируется использование перегрузки оператора -> в качестве отладочного трюка [ Перегруженный оператор выводит короткое сообщение перед тем, как вернуть указатель this, ссылающийся на объект, для которого была вызвана функция-член. В основной программе перегруженный оператор может применяться для объектов класса TAClass следующим образом:

main()

{

TAnyClass t(100, 200);

cout «t->GetX() «'\n';

cout «t->GetY() «'\n'

return 0;

}

В двух операторах вывода в поток используется перегруженный оператор -> для доступа к функциям-чле­нам GetXO и GetY() объекта t класса TAnyClass, На самом деле, эти операторы выполняются так, как приве­денные ниже:

cout «(t.operator->())->GetX() «'\n';

cout «(t.operator->())->GetY() «'\n';

Поэтому программа отобразит метки перед значениями, возвращаемыми функциями GetXO и GetY():

Accessing member: 100

Accessing member: 200

 

Int operator--()

{ return --x; }

Int operator--(int)

{ return x--;}

int GetX(void) { return x;}

};

main() {

TAClass t(100);

clrscr();

cout<<"t=="<<t.GetX() <<";++t==" << ++t<<"\n";

cout<<"t=="<<t.GetX()<< ";t++==" << t++<<"\n";

cout<<"t ==" << t.GetX() << ";--t==" << --t<<"\n";

cout<<"t=="<<t.GetX()<<";t--==" << t--<<"\n";

getch();

return 0;

}

Результат:

t=101; ++t=101

T=102; t++=101

T=101; --t=101

T=100; t--=101

Другие вопросы, касающиеся перегрузки операторов

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

• C++ "не понимает" семантики перегруженного оператора. Вы должны обеспечить осмысленные функции перегрузки операторов.

• C++ не может выводить сложные операторы из простых. В классе, в котором объявлены функции пере­грузки операторов operator*() и operator=(), C++ не сможет вычислить выражение а *= b до тех пор, пока вы не перегрузите operator*=().

• Никогда не изменяйте синтаксис перегруженных операторов. Бинарные операторы должны оставаться би­нарными, унарные — унарными. Например, нельзя создать унарное деление, поскольку такой встроенной возможности в C++ не существует.

• Нельзя изобретать новые операторы. Вы можете перегружать только операторы, перечисленные в табл. 15.1.

• Нельзя перегружать символы препроцессора # и ##.

ПРЕДУПРЕЖДЕНИЕ

Никогда не вызывайте функцию free() для освобождения памяти, выделяемой с помощью оператора new, и никогда не вызывайте mallocO для выделения памяти, освобождаемой позднее с помощью delete. Использование способов управления распределением памяти ANSI С и C++ вперемежку может в некоторых случаях сработать, однако программа, скорее всего, будет завершаться аварийно, если new и delete перегружаются в классе или если Borland изменит внутреннюю реализацию этих операторов в будущих версиях компилятора.

Перегрузка оператора new

Вы можете перегружать new точно так же, как и операторы, подобные + и =. Перегрузка new в объявле­нии класса указывает компилятору, что отныне на вас лежит ответственность за поддержку запросов выделе­ния памяти для объектов этого класса.

Для перегрузки new следует использовать прототип функции вида

void * operator new(size_t size);.

В даль­нейшем обращения к оператору new для выделения памяти объектам класса будут перенаправлены перегруженнойфункции.

Функция должна возвращать адрес области памяти, выделенной объекту. Если необходимо­го свободного пространства нет, функция должна возвращать null (нуль).

В листинге 4.7 приводится простой, но полный пример перегрузки оператора new для выделения памяти объектам. Вместо кучи программа запоминает их в глобальном буфере.

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

ЗАМЕЧАНИЕ

В строке 6 OVERNEW.CPP приводится команда #pragma warn -aus. Она предписывает компилятору выключить предупреждение о том, что в программе не используются переменные, объявленные исключительно в демонстрационных целях.

Функция перегрузки оператора new, объявленная в строке 13 и реализованная в строках 38-48, проверяет наличие пространства в глобальном буфере. Если его нет, функция возвращает 0, что является основанием для возвращения оператором new значения null. (В программе не проверяется это условие, но, конечно, вы должны проверять его в реальных приложениях.) Если свободное пространство есть, глобальный индекс уве­личивается на размер запрашиваемой памяти, который передается функции в параметре size. Затем функция возвращает адрес выделяемого участка памяти.

Листинг 4.7. OVERNEW.CPP (перегрузка new)

//4_7.cpp

#include <iostream.h>

#include <conio.h>

// Следующая директива "pragma" выключает //предупреждение компилятора о том, что

// в программе объявляются и не используются //демонстрационные переменные

#pragma warn -aus

class BNew { private:

int x;

public:

BNew();

// Функция перегрузки оператора new

void * operator new(size_t size);

};

char buf[512]; int index=0;

main() {

cout << "\nCreating local instance";

BNew b1;

cout << "\nAllocating space via new";

BNew *b2 = new BNew;

BNew *b3 = new BNew;

BNew *b4 = new BNew;

BNew *b5 = new BNew;

getch();

return 0;

}

BNew::BNew()

{

cout << "\n constructor";

x = index;

}

void *BNew::operator new(size_t size)

{

cout << "\n lnside overloaded new. Size = "<< size;

if (index >= 512 - sizeof(BNew))

{getch();

return 0;}

else {

int k = index;

index += sizeof(BNew);

return &buf[k];

}}

Перегрузка delete

Оператор delete — обратная сторона медали распределения памяти.

Можно пере­грузить оператор delete для отслеживания удаления объектов, адресованных указателями.

Прототип функции перегрузки оператора delete должен иметь вид

void operator delete(void *p);

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

void operator delete(void *p, size_t size);

В соответствии с вторым объявлением C++ будет дополнительно передавать функции число байтов в уда­ляемом объекте.

Для добавления функции перегрузки оператора delete в класс BNew в модуле OVERNEW (листинг 4.7) объявить функцию-член в открытой секции класса:

void BNew::operator delete(void *p)

cout << "\nDeleting object at " «p;

Оператора вывода отображает адрес каждого удаляемого объекта.

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

также потребуется несколько операторов для удаления нескольких объектов. Вставьте следующие строки в функцию mainO непосредственно перед оператором return:

delete Ь2; delete ЬЗ; delete Ь4; delete Ь5;

//4_7А.cpp

#include <iostream.h>

#include <conio.h>

class BNew { private:

int x; public:

BNew();

void * operator new(size_t size);

void operator delete(void *p);

};

char buf[512]; int index=0;

main() {

clrscr();

cout << "\nCreating local instance";

BNew b1;

cout << "\nAllocating space via new";

BNew *b2 = new BNew;

BNew *b3 = new BNew;

BNew *b4 = new BNew;

BNew *b5 = new BNew;

delete b2;

delete b3;

getch();

return 0;

}

BNew::BNew()

{

cout << "\nlnside constructor";

x = index;

}

void *BNew::operator new(size_t size)

{

//cout << "\nlnside overloaded new. Size = "<< size;

//cout << "\n index = "<< index;

if (index >= (512 - sizeof(BNew)))

{getch();

return 0;}

else {

int k = index;

index += sizeof(BNew);

//cout << "\n k = "<< k;

return &buf[k];

}

}

void BNew::operator delete(void *p)

{

cout << "\n delete "<< p;

}

 

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

ЗАМЕЧАНИЕ

Чтобы использовать средства управления памятью C + + для выделения пространства в куче для объектов, в которых перетружен оператор new, поставьте перед оператором двойное двоеточие. Например, и строке

BNew *х =::new BrandNew игнорируется перегруженный в объектах класса BNew оператор new. Аналогично,::delete служит для обращения к оператору delete, используемому по умолчанию.

Установка обработчика ошибок оператора new

Обычно, если new не может выполнить запрос выделения памяти, оператор возвращает null.



Поделиться:


Последнее изменение этой страницы: 2016-08-01; просмотров: 779; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.144.115.16 (0.43 с.)