ПрограммированиеC++ инженер

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

Проходите собеседования с ИИ помощником Hintsage

Ответ.

Перегрузка функций (overloading) — это механизм объявления нескольких функций с одинаковым именем, но разной сигнатурой (числом или типом параметров).

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

В классах часто встречается ситуация:

class Printer { void print(int n, char c = '*') { /* ... */ } void print(const std::string& s) { /* ... */ } }; Printer p; p.print(5); // вызовет print(int n, char c = '*'), c = '*' p.print("Hi"); // вызовет print(const std::string&)

Тонкости:

  • Не стоит одновременно перегружать функцию и различать их только через значения по умолчанию, это приводит к двусмысленности.
void foo(int x, int y = 10); void foo(int x); foo(1); // Ошибка: неясно, какую функцию вызвать

Вопрос с подвохом.

Какое из мест является единственным, где разрешено указывать значение по умолчанию на функцию-член класса?

Ответ: Значение по умолчанию для метода класса разрешено указывать в объявлении метода внутри класса (или в первой декларации вне класса), но не разрешено указывать его и в классе, и в реализации. Иначе возникнет ошибка о повторном определении.

class X { void func(int x = 5); // Можно здесь }; void X::func(int x) { /* ... */ } // Но не здесь!

Примеры реальных ошибок из-за незнания тонкостей темы.


История 1

В банковском ПО произошел сбой на этапе компиляции: часть overloaded-функций с разными значениями по умолчанию мешали однозначному выбору нужной, вызовы были двусмысленными на этапе компиляции — результат, невозможность собрать релиз до устранения ручных правок.


История 2

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


История 3

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