Перегрузка операторов. Оправданное и неоправданное использование. Пример перегрузки простейшего оператора. Операторы, которые нельзя перегружать

 

ПЕРЕГРУЗКА ОПЕРАТОРОВ (operator overloading) применяется как альтернатива функциям для пользовательских типов, которым в предметной области свойственен понятный однозначный синтаксис операций. Например, для сложения и умножения матриц вполне естественна и читабельна операторная форма выражений, хотя за этими элементарными на вид операциями скрываются алгоритмы с двойной и тройной вложенностью циклов соответственно:

 

D = A + B * C

 

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

 

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

 

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

 

Нельзя перегружать операторы:

● “.” - доступ к члену структуры/класса;

● “.*” - доступ к члену структуры/класса через указатель на член;

● “::” - оператор разрешения области видимости;

● “?:” - тернарный условный оператор.

 

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

//пример перегрузки унарного оператора “!”

bool CoffeeMachine::operator ! ()

{

if(GetGrainWeidhtLeft()<4)

return 0;

 

return 1;

}

 

13. Внутриклассовые и глобальные перегруженные операторы. Перегрузка операторов сдвига. Применение перегрузки сдвига для взаимодействия с потоками ввода/вывода.

● Внутриклассовый оператор ==:

 

class Date

{ // … без изменений …

public:

// Объявление внутриклассового оператора сравнения на равенство дат

bool operator == ( const Date & d ) const;

};

// Реализация внутриклассового оператора сравнения на равенство дат

inline bool Date::operator == ( const Date & d ) const

{

// Даты равны, когда все их компоненты равны между собой

return m_Year == d.GetYear() &&

m_Month == d.GetMonth() &&

m_Day == d.GetDay() ;

}

● Глобальный оператор ==:

 

// Реализация глобального оператора сравнения на равенство дат

inline bool operator == ( const Date & d1, const Date & d2 )

{

return d1.GetYear() == d2.GetYear() &&

d1.GetMonth() == d2.GetMonth() &&

d1.GetDay() == d2.GetDay() ;

}

 

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

 

Перегрузка операторов сдвига <<, >>

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

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

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

std::ostream& operator << ( std::ostream& o, const Date & d )

{

o << d.GetYear() << ‘/’ << d.GetMonth() << ‘/’ << d.GetDay();

return o;

}

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

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

std::istream & operator >> ( std::istream & i, Date& d )

{ // Считываем строку до пробела

char buf[ 100 ];

i >> buf;

d = Date( buf ); // используем имеющийся конструктор из строки, а затем копируем

return i;

}

int main ()

{ Date d;

std::cin >> d; // ...

}