Почему параметры по умолчанию должны добавляться последними в функциях C++?
Почему параметры по умолчанию должны добавляться последними в функциях C++?
9 ответов
упростить определение языка и сохранить код читаемым.
void foo(int x = 2, int y);
чтобы вызвать это и воспользоваться значением по умолчанию, вам понадобится такой синтаксис:
foo(, 3);
что, вероятно, было слишком странно. Другой альтернативой является указание имен в списке аргументов:
foo(y : 3);
новый символ должен быть использован, потому что это уже что-то значит:
foo(y = 3); // assign 3 to y and then pass y to foo.
подход к присвоению имен был рассмотрен и отклонен ИСО комитет, поскольку они не согласны с введением нового значения имен параметров вне определения функции.
если вас интересует больше обоснований дизайна C++, прочитайте дизайн и эволюция C++ по Страуструп.
Если вы определяете следующую функцию:
void foo( int a, int b = 0, int c );
как бы вы назвали функцию и предоставили значение для a и c, но оставили b по умолчанию?
foo( 10, ??, 5 );
В отличие от некоторых других языков (например, Python), аргументы функций в C/C++ не могут быть квалифицированы по имени, например:
foo( a = 10, c = 5 );
Если это возможно, то аргументы по умолчанию могут быть в любом месте списка.
представьте, что у вас была функция с этим прототипом:
void testFunction(bool a = false, bool b = true, bool c);
теперь предположим, что я вызвал функцию следующим образом:
testFunction(true, false);
как компилятор должен выяснить, для каких параметров я хотел бы предоставить значения?
как указывает большинство ответов, наличие параметров по умолчанию потенциально в любом месте списка параметров увеличивает сложность и неоднозначность вызовов функций (как для компилятора, так и, возможно, что более важно для пользователей функции).
одна хорошая вещь о C++ заключается в том, что часто есть способ сделать то, что вы хотите (даже если это не всегда хорошая идея). Если вы хотите иметь аргументы по умолчанию для различных позиций параметров, вы почти наверняка можете сделать это запись перегрузок, которые просто разворачиваются и вызывают полностью параметризованную функцию inline:
int foo( int x, int y);
int foo( int y) {
return foo( 0, y);
}
и там у вас есть эквивалент:
int foo( int x = 0, int y);
Как правило параметры функции обрабатываются компилятором и помещаются в стек справа налево. Поэтому имеет смысл сначала оценить любые параметры со значениями по умолчанию.
(Это применяется к _ _ cdecl, который имеет тенденцию быть по умолчанию для объявлений функций VC++ и __stdcall).
Its, потому что он использует относительное положение аргументов, чтобы найти, каким параметрам они соответствуют.
Он мог использовать типы, чтобы определить, что необязательный параметр не был задан. Но неявное преобразование может помешать этому. Другой проблемой были бы ошибки программирования, которые можно было бы интерпретировать как необязательные аргументы, а не отсутствующие ошибки аргументов.
чтобы любой аргумент стал необязательным, должен быть способ определить аргументы, чтобы убедиться, что нет ошибки программирования или удалить двусмысленности. Это возможно на некоторых языках, но не на C++.
еще одна вещь, которую Комитет по стандартам должен был рассмотреть, заключалась в том, как параметры по умолчанию взаимодействуют с другими функциями, такими как перегруженные функции, разрешение шаблонов и поиск имен. Эти функции взаимодействуют очень сложными и трудными для описания способами. Если параметры по умолчанию будут отображаться в любом месте, это только увеличит сложность.
Это вопрос о Конвенции вызова. Конвенции Вызова : При вызове функции параметры помещаются в стек справа налево. например,
fun(int a, int b, int c);
стек такой: ля си с Итак, если вы установите значение по умолчанию слева направо следующим образом:
fun(int a = 1, int b = 2, int c);
и звонок такой:
fun(4,5);
ваш вызов означает набор a = 4, b = 5 и c = нет значения; // который неправильно!
если вы объявляете функцию следующим образом:
fun(int a, int b = 2, int c = 3);
и звонок такой:
fun(4, 5);
ваш вызов означает набор a = 4, b = 5 и C = значение по умолчанию(3); // что правильно!
В заключение, вы должны поставить значение по умолчанию справа налево.
Цзин Цзэн прав. Здесь я хотел бы добавить свои замечания. При вызове функции аргументы передаются в стек справа налево. Например, предположим, что у вас есть эта произвольная функция.
int add(int a, int b) {
int c;
c = a + b;
return c;
}
вот кадр стека для функции:
------
b
------
a
------
ret
------
c
------
эта диаграмма выше является рамкой стека для этой функции! Как вы можете видеть, сначала b помещается в стек, затем a помещается в стек. После этого функция return address будет нажата в стек. Обратный адрес функции содержит местоположение в main (), откуда функция была первоначально вызвана, и после выполнения функции выполнение программы переходит на обратный адрес этой функции. Затем все локальные переменные, такие как c, помещаются в стек.
теперь главное, что аргументы помещаются в стек справа налево. В основном любые параметры по умолчанию, которые предоставляются, являются литеральными значениями, которые хранятся в коде секции исполняемого файла. Когда выполнение программы встречает параметр по умолчанию без соответствующего аргумента, оно помещает это литеральное значение в верхнюю часть стека. Затем он смотрит на a и помещает значение аргумента в верхнюю часть стека. Указатель стека всегда указывает на верхнюю часть стека, вашу самую последнюю нажатую переменную. Таким образом, любые литеральные значения, которые вы вставили в стек в качестве параметров по умолчанию, "позади" указателя стека.
вероятно, это было более эффективно для компилятор быстро сначала помещает произвольные литеральные значения по умолчанию в стек, поскольку они не хранятся в памяти, и быстро создает стек. Подумайте о том, что было бы, если бы переменные были сначала помещены в стек, а затем литералы. Доступ к хранилищу памяти для ЦП занимает относительно много времени по сравнению с извлечением литерального значения из схемы или регистра ЦП. Поскольку это занимает больше времени, чтобы подтолкнуть переменных в стек и литералы, литералы должны были бы ждать, тогда обратный адрес должен был бы ждать, и локальные переменные также должны были бы ждать. Это, наверное, не большая проблема в эффективности, но это только моя теория, почему аргументы по умолчанию всегда в правой части заголовка функции в C++. Это означает, что компилятор был разработан как таковой.