Почему параметры по умолчанию должны добавляться последними в функциях C ++? - PullRequest
19 голосов
/ 14 июля 2009

Почему параметры по умолчанию должны добавляться последними в функциях C ++?

Ответы [ 9 ]

29 голосов
/ 14 июля 2009

Для упрощения определения языка и обеспечения читабельности кода.

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 ++ от Страуструпа.

21 голосов
/ 14 июля 2009

Если вы определите следующую функцию:

void foo( int a, int b = 0, int c );

Как бы вы вызвали функцию и задали значение для a и c, но оставили бы b по умолчанию?

foo( 10, ??, 5 );

В отличие от некоторых других языков (например, Python), аргументы функций в C / C ++ не могут быть квалифицированы по имени, как показано ниже:

foo( a = 10, c = 5 );

Если бы это было возможно, то аргументы по умолчанию могли бы быть где угодно в списке.

5 голосов
/ 14 июля 2009

Представьте, что у вас есть функция с этим прототипом:

void testFunction(bool a = false, bool b = true, bool c);

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

testFunction(true, false);

Как компилятор должен выяснить, для каких параметров я собирался предоставить значения?

3 голосов
/ 14 июля 2009

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

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

 int foo( int x, int y);
 int foo( int y) {
     return foo( 0, y);
 }

И у вас есть эквивалент:

 int foo( int x = 0, int y);
1 голос
/ 14 июля 2009

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

1 голос
/ 14 июля 2009

Это потому, что он использует относительную позицию аргументов, чтобы найти, каким параметрам они соответствуют.

Он мог бы использовать типы, чтобы определить, что необязательный параметр не задан. Но неявное преобразование может помешать этому. Другая проблема - ошибки программирования, которые можно интерпретировать как выпадение необязательных аргументов вместо ошибки пропущенного аргумента.

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

1 голос
/ 14 июля 2009

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

(Это относится к __cdecl, который обычно используется по умолчанию для объявлений функций VC ++ и __stdcall).

0 голосов
/ 12 декабря 2017

Цзин Цзэн прав. Я хотел бы добавить свои замечания здесь. Когда вызывается функция, аргументы помещаются в стек справа налево. Например, допустим, у вас есть эта произвольная функция.

int add(int a, int b) {
  int c;
  c = a + b;
  return c;
}

Вот кадр стека для функции:

------
  b
------
  a
------
 ret
------
  c
------

Эта диаграмма выше является рамкой стека для этой функции! Как вы можете видеть, сначала b помещается в стек, затем a помещается в стек. После этого адрес возврата функции помещается в стек. Адрес возврата функции содержит местоположение в main (), откуда функция была первоначально вызвана, и после выполнения функции выполнение программы переходит к адресу возврата этой функции. Затем любые локальные переменные, такие как c, помещаются в стек.

Теперь главное, чтобы аргументы помещались в стек справа налево. В основном, любые заданные по умолчанию параметры являются литеральными значениями, которые хранятся в разделе кода исполняемого файла. Когда выполнение программы встречает параметр по умолчанию без соответствующего аргумента, оно помещает это литеральное значение в вершину стека. Затем он смотрит на a и помещает значение аргумента в верхнюю часть стека. Указатель стека всегда указывает на вершину стека, вашей последней помещенной переменной. Поэтому любые литеральные значения, которые вы помещаете в стек в качестве параметров по умолчанию, находятся «позади» указателя стека.

Вероятно, более эффективным для компилятора было сначала быстро помещать произвольные литеральные значения по умолчанию в стек сначала, поскольку они не хранятся в ячейке памяти, и быстро собирать стек. Подумайте, что было бы, если бы переменные сначала были помещены в стек, а затем литералы. Доступ к области памяти для CPU занимает относительно много времени по сравнению с извлечением литерального значения из схемы или регистра CPU. Поскольку для вставки переменных в стек требуется больше времени, чем для литералов, литералам придется ждать, тогда и обратному адресу придется ждать, и локальным переменным также придется ждать. Вероятно, это не очень важно для эффективности, но это всего лишь моя теория, почему аргументы по умолчанию всегда находятся в самых правых позициях заголовка функции в C ++. Это означает, что компилятор был спроектирован как таковой.

0 голосов
/ 25 августа 2015

Это вопрос соглашения о вызовах. Соглашение о звонках: Когда вы вызываете функцию, параметры помещаются в стек справа налево. например,

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); // что правильно!

В заключение, вы должны поставить значение по умолчанию справа налево.

...