C++. Бархатный путь. Часть 2 - Объявление и определение операторных функций

ОГЛАВЛЕНИЕ

 

Объявление и определение операторных функций

 

При объявлении и определении операторных функций (в том числе и operator=() ), используется синтаксическая конструкция, обозначаемая в терминах формальной грамматики нетерминальным символом ИмяФункцииОперации. Несколько форм Бэкуса-Наура позволяют однозначно определить это понятие:

Имя ::= ИмяФункцииОперации ::= ***** ИмяФункцииОперации ::= operator СимволОперации СимволОперации ::= +|-|*|?|%|^|&|~|!|,|=|<|>|<=|>=|++|--|<<|>>|==|!=|&&| |||+=|-=|*=|<<=|>>=|[]|()|->|->*|new|delete|

Как следует из приведённых БНФ, большинство символов операций языка C++ могут участвовать в создании так называемых имён функций операций или операторных функций. То есть на основе этих символов можно объявлять операторные функции, сокращённая форма вызова которых позволяет создавать видимость применения операций к объектам производных типов.

C++ не накладывает никаких ограничений на семантику этих самых операторных функций. Наша операторная функция operator=() могла бы вообще не заниматься присвоением значений данных-членов. Она могла бы не возвращать никаких значений. Само собой, что тогда выражение вызова этой функции не могло бы быть коммутативным. А единственный параметр можно было бы передавать по значению. Но тогда всякий раз при вызове функции неизбежно должен был бы вызываться конструктор копирования, который бы создавал в области активации функции копию объекта, которую впоследствии должен был бы разрушать деструктор.

Операторная функция operator=(), как и любая другая функция, может быть перегружена. Например, объявление параметра типа int, позволило бы присваивать комплексным числам целочисленные значения. Здесь нет пределов совершенствования. В принципе, механизм операторных функций регламентирует лишь внешний вид заголовка функции (его "операторное" имя, количество параметров, в ряде случаев - возвращаемое значение). Информация о заголовке принципиальна, поскольку от этого зависит форма сокращённого вызова операторной функции.

Ещё несколько замечаний по поводу спецификации возвращаемого значения операторной функции.

Операторная функция operator=() может вообще не возвращать никаких значений. Сокращённая форма вызова

ctVal2 = ctVal1;

с точки зрения транслятора абсолютно корректна и полностью соответствует следующим прототипам:

void ComplexType::operator = (const ComplexType& ctKey); void ComplexType::operator = (ComplexType& ctKey); void ComplexType::operator = (ComplexType ctKey);

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

С другой стороны, уже существующий вариант нашей операторной функции также может быть оптимизирован. Функция может возвращать не ОБЪЕКТ (ЗНАЧЕНИЕ), а ССЫЛКУ на объект.

В этом случае при возвращении значения не будет создано временного объекта. Также не будет вызываться деструктор для его уничтожения. Модификация операторной функции operator=() минимальна - всего лишь дополнительная ptrОперация & в спецификации возвращаемого значения (мы приводим здесь только прототип новой версии функции):

ComplexType& operator = (const ComplexType &);

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

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

:::::
class ComplexType
{
:::::
};
:::::
class rrr // Объявляется новый класс.
{
public:
ComplexType* pComplexVal;
// Собственные версии конструкторов и деструкторов.
rrr ()
{
pComplexVal = new ComplexType;
// Порождение собственного экземпляра объекта ComplexType.
}
~rrr ()
{
if (pComplexVal) = delete pComplexVal;
}
// Наконец, встроенная операторная функция.
ComplexType* operator -> ()
{
cout << "This is operator -> ()..." << endl;
return pComplexVal;
}
};
:::::
// А это уже собственно фрагмент программы…
rrr rrrVal; // Определяем объект - представитель класса rrr.
cout << rrrVal ->real << " real." << endl;
:::::
Сокращённая форма вызова операторной функции operator->() имеет вид rrrVal->real и
интерпретируется транслятором как (rrrVal.operator->())->real, о чём и свидетельствует оператор, содержащий полную 
форму вызова этой операторной функции.
:::::
cout << (rrrVal.operator->())->imag << " imag." << endl;
:::::