MyTetra Share
Делитесь знаниями!
Перегрузка префиксного и постфиксного оператора в C++: фиктивный параметр как самое странное решение разработчиков языка
Время создания: 04.12.2023 11:34
Текстовые метки: язык, C++, Си++, перегрузка, унарный, оператор, префикс, постфикс, prefix, postfix, инкремент, декремент, фиктивный, аргумент
Раздел: Компьютер - Программирование - Язык C++ (Си++)
Запись: xintrea/mytetra_syncro/master/base/1701678867cq6k80t09l/text.html на raw.github.com

Если начать разбираться, как в языке C++ прописывается переопределение префиксных и постфиксных операторов типа ++ или --, то сразу можно наткнуться на весьма странный подход к различению этих двух форм операторов.


Проблема состоит в том, что в момент внедрения перегрузки префиксных и постфиксных операторов, в языке Си++ не существовало внятного синтаксиса для различия функций, ответственных за реализацию этих двух форм одного и того же оператора. Разработчикам не очень хотелось вводить в язык дополнительный синтаксис. Поэтому разработчики языка решились на феерическую странность, которая до сих пор удивляет общественность, оставляя открытым вопрос: как такие умные люди могли сделать в языке ТАКИЕ костыли.


Итак, как определяются префиксный и постфиксный оператор? Ниже дан код, демонстрирующий определение двух форм инкремента для некоего класса, представляющего целочисленные числа. Здесь показана форма определения унарного оператора через friend-функцию (Еще существует форма определения унарного оператора через метод класса, и в такой форме будет отсутствовать параметр Integer& i):



// Класс целочисленных чисел

class Integer

{

private:

int value;


public:


// Конструктор, инициализация свойства value

// происходит через список инициализации

Integer(int i): value(i)

{}


// Префиксный инкремент

friend const Integer& operator++(Integer& i);


// Постфиксный инкремент

friend const Integer operator++(Integer& i, int);

...

}



// Префиксная версия

const Integer& operator++(Integer& i) {

i.value++;

return i; // Возвращается значение после инкремента

}


// Постфиксная версия

const Integer operator++(Integer& i, int) {

Integer oldValue(i.value); // Запоминается значение до инкремента

i.value++;

return oldValue; // Возвращается значение до инкремента

}



Здесь видно, что префиксная версия ++ возвращает ссылку на переданный объект, и принимает значение числа по ссылке. А постфиксная версия возвращает значение в виде объекта данного класса, а принимает ДВА значения - значение числа по ссылке и странный фиктивный параметр типа int, который нигде не используется.



Другими словами. В случае, когда встречается выражение ++i, то компилятор вызывает функцию operator++(a). А если компилятор видит i++, то вызывается функция operator++(a, int). То есть, вызывается перегруженная функция operator++(a, int), и именно для этого используется фиктивный параметр int в постфиксной версии.



Итак, как пишут в книгах, с помощью фиктивного параметра int как раз и определяется форма инкрементного (декрементного) оператора. Если его нет - значит префиксная форма. Если есть - значит постфиксная форма. В конце концов, большинство операторов - инфиксные, а значит принимают два значения. А эти редкие унарные операторы используют только одно входящее значение. Так давайте использовать второе, чего добру пропадать. Пусть его наличие будет флагом того, что унарный оператор является постфиксным!


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


Ответ в том, что в Си++ перегружаемые функции не могут различаться одним только типом возвращаемого значения. Это закреплено в стандарте:



Function declarations that differ only in the return type cannot be overloaded


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



Ноги у этого ограничения растут из подсистемы автоматического преобразования и выведения типов. Если будет две функции, у которых одинаковые типы входных параметров, но разные типы возврата, то непонятно, какую версию функции использовать. По типам входных параметров версию функции определить невозможно, они же одинаковые. По выходному параметру можно было бы определить версию функции, но тип "принимающей" переменной может быть неопределенным, если переменная имеет тип auto. Кроме того, в Си++ имеется автоматическое преобразование типов, и возникает вопрос: если для вышеуказанного класса Integer будет существовать преобразование в стандартный int, то по какому пути должно произойти преобразование типа Integer и Integer& ?


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


Так же в этом разделе:
 
MyTetra Share v.0.60
Яндекс индекс цитирования