ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В C++ (4-Е ИЗДАНИЕ) (часть 11) онлайн

Исправленная программа STRIMEM

Используя указатель this, можно переделать функцию operator=() в программе

STRIMEM и заставить ее возвращать результат по ссылке. Таким способом можно

осуществить множественные присваивания для класса String, например

S1 = s2 = s3

В то же время мы избежим и создания ложных объектов в виде копий для

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

Ниже представлен листинг программы STRIMEM2.

Листинг 11.22. Программа STRIMEM2

// strimem2.cpp

// Класс String с экономией памяти

// Перегружаемая операция присваивания и указатель this

#include <iostream>

#include <cstring>                                         //для strcpy() и т. д.

using namespace std;

///////////////////////////////////////////////////////////

class strCount                   //Класс-счетчик уникальных строк

                {                                                                                                                                                           private:

                               int count;                                           //собственно счетчик

                               char* str;                                            //указатель на строку

                               friend class String;  //сделаем себя доступными

                               //методы скрытые                                                                     

                               strCount(char* s)                           //конструктор с одним аргументом

                                               {

                                               int length = strlen(s);  //длина строкового                                                                                                       //аргумента

                                               str = new char[length+1]; //занять память                                                                                                        //под строку

                                               strcpy(str, s);//копировать в нее аргументы

                                               count=1;               //считать с единицы

                                               }

//---------------------------------------------------------

                               ~strCount()                                                       //деструктор

                                               { delete[] str; }                 //удалить строку            

                };

 

///////////////////////////////////////////////////////////

class String                                                        //класс String

                {

                private:

                               strCount* psc;                  //указатель на strCount

                public:

                               String()                  //конструктор без аргументов

                                               { psc = new strCount("NULL"); }

//---------------------------------------------------------

                               String(char* s)  //конструктор с одним аргументом

                                               { psc = new strCount(s); }

//---------------------------------------------------------

                               String(String& S)                //конструктор копирования

                                               {               

                                               cout << "\nКОНСТРУКТОР КОПИРОВАНИЯ";

                                               psc = S.psc;

                                               (psc->count)++;

                                               }

//---------------------------------------------------------

                               ~String()                                             //деструктор

                                               {

                                               if(psc->count==1)  //если последний                                                                                                 //пользователь,

                                                               delete psc;         //удалить strCount

                                               else                                       //иначе              

                                                               (psc->count)--;//уменьшить счетчик

                                               }

//---------------------------------------------------------

                               void display()                      //вывод String

                                               {

                                               cout << psc->str;             //вывести строку

                                               cout << " (addr=" << psc << ")";  //вывести адрес             

                                               }

//---------------------------------------------------------

                               String& operator = (String& S)//присвоение String

                                               {

                                               cout << "\nПРИСВАИВАНИЕ";

                                               if(psc->count==1)  //если последний                                                                                       //пользователь,

                                                               delete psc;         //удалить strCount

                                               else                                       //иначе

                                                               (psc->count)--;// уменьшить счетчик

                                               psc = S.psc; //использовать strCount                                                                                  //аргумента

                                               (psc->count)++;              //увеличить счетчик

                                               return *this;      //вернуть этот объект

                                               }

                };

///////////////////////////////////////////////////////////

int main()

{

                String s3 = "Муха по полю пошла, муха денежку нашла";

                cout << "\ns3="; s3.display();    //вывести s3

 

                String s1,s2;                                       //определить объекты String

                s1 = s2 = s3;                                       //присваивания

                cout << "\ns1="; s1.display();    //вывести их

                cout << "\ns2="; s2.display();

                cout << endl;     //ожидать нажатия клавиши

                return 0;

}

 

 

Теперь описателем оператора присваивания является

String& operator = (String& S)//возврат по ссылке

и, как в программе ASSIGN2, функция возвращает указатель на this. Результат ра-

боты программы:

s3=Myxa по полю пошла, муха денежку нашла (addr=0x8f640d3a)

ПРИСВАИВАНИЕ

ПРИСВАИВАНИЕ

s2=Myxa по полю пошла, муха денежку нашла (addr=0x8f640d3a)

s1=Myxa по полю пошла, муха денежку нашла (addr=0x8f640d3a)

Результат работы программы показывает, что в соответствии с выражением

присваивания все три объекта класса String указывают на один и тот же объект

класса strCount.

Следует отметить, что указатель this нельзя использовать в статических ме-

тодах, так как они не ассоциированы с конкретным объектом.

Остерегайтесь неправильных присваиваний

Один из основополагающих так называемых «законов Мёрфи» говорит о том,

что все, что можно случайно сделать не так, обязательно будет кем-нибудь сдела-

но. Это абсолютная правда в отношении программирования, поэтому будьте уве-

рены: если вы переопределили оператор присваивания, кто-нибудь станет его ис-

пользовать для присваивания объектов самим себе. Вот так:

 

alpha = alpha;

Перегружаемый = должен быть морально готов обработать такую ситуацию.

В противном случае не ждите ничего хорошего. Например, в секции main() про-

граммы STRIMEM2, если вы положите объект String равным самому себе, програм-

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

тот же объект strCount). Проблема заключается в том, что согласно коду пере-

гружаемой операции присваивания последний удаляет объект strCount, если счи-

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

Присваивание объекта самому себе убедит оператор в этом прискорбном факте,

хотя на самом деле никто и не собирался удалять никакие объекты.

Чтобы привести все в порядок, нужно ввести проверку на присваивание само-

му себе в начале работы каждого перегружаемого =. В большинстве случаев это

делается путем сравнения адресов объектов, стоящих справа и слева от знака

равенства. Понятно, что если адреса равны, значит, осуществляется присваива-

ние объекта самому себе и необходимо немедленно вернуться из этой функции.

(Если кто-то не понял, в чем проблема, поясняем, что не нужно присваивать

объекты самим себе — они и так уже равны.) Например, в STRIMEM2 с этой це-

лью можно вставить следующие две строчки

if(this == &S)

return *this;

в начало функции operator=(). Это должно решить возникшую проблему.

 

 

28