¿Por qué deberían agregarse los parámetros predeterminados al último en las funciones de C ++?

¿Por qué deberían agregarse los parámetros predeterminados al último en las funciones de C ++?

Para simplificar la definición del lenguaje y mantener el código legible.

void foo(int x = 2, int y); 

Para llamar eso y aprovechar el valor predeterminado, necesitaría una syntax como esta:

 foo(, 3); 

Lo cual probablemente se sintió demasiado raro. Otra alternativa es especificar nombres en la lista de argumentos:

 foo(y : 3); 

Debería usarse un nuevo símbolo porque esto ya significa algo:

 foo(y = 3); // assign 3 to y and then pass y to foo. 

El comité de ISO consideró y rechazó el planteamiento de nomenclatura porque no se sentían cómodos con la introducción de una nueva significación para los nombres de los parámetros fuera de la definición de la función.

Si está interesado en más fundamentos de diseño de C ++, lea El diseño y la evolución de C ++ de Stroustrup.

Si defines la siguiente función:

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

¿Cómo llamarías a la función y suministrarías un valor para a y c, pero dejar b como el valor predeterminado?

 foo( 10, ??, 5 ); 

A diferencia de algunos otros lenguajes (por ejemplo, Python), los argumentos de función en C / C ++ no pueden calificarse por nombre, como el siguiente:

 foo( a = 10, c = 5 ); 

Si eso fuera posible, entonces los argumentos predeterminados podrían estar en cualquier lugar de la lista.

Imagina que tienes una función con este prototipo:

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

Ahora supongamos que llamé a la función así:

 testFunction(true, false); 

¿Cómo se supone que el comstackdor descifra los parámetros a los que me refiero para proporcionar valores?

Como señala la mayoría de las respuestas, tener parámetros predeterminados potencialmente en cualquier parte de la lista de parámetros aumenta la complejidad y la ambigüedad de las llamadas a funciones (tanto para el comstackdor como, probablemente, más importante para los usuarios de la función).

Una cosa buena de C ++ es que a menudo hay una manera de hacer lo que quieres (incluso si no siempre es una buena idea). Si desea tener argumentos predeterminados para varias posiciones de parámetros, casi puede hacerlo escribiendo sobrecargas que simplemente den la vuelta y llamen a la función totalmente parametrizada en línea:

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

Y ahí tienes el equivalente de:

  int foo( int x = 0, int y); 

Como regla general, el comstackdor procesa los parámetros de la función y los coloca en la stack de derecha a izquierda. Por lo tanto, tiene sentido que los parámetros con valores predeterminados se evalúen primero.

(Esto se aplica a __cdecl, que tiende a ser el predeterminado para las declaraciones de funciones de VC ++ y __stdcall).

Es porque usa la posición relativa de los argumentos para encontrar a qué parámetros corresponden.

Podría haber utilizado los tipos para identificar que no se proporcionó un parámetro opcional. Pero la conversión implícita podría interferir con eso. Otro problema sería errores de progtwigción que podrían interpretarse como abandono de argumentos opcionales en lugar de falta de error de argumento.

Para permitir que cualquier argumento sea opcional, debe haber una forma de identificar los argumentos para asegurarse de que no haya errores de progtwigción o para eliminar ambigüedades. Esto es posible en algunos idiomas, pero no en C ++.

Otra cosa que el comité de estándares tuvo que considerar fue cómo los parámetros predeterminados interactúan con otras características, como funciones sobrecargadas, resolución de plantillas y búsqueda de nombres. Estas características interactúan en formas muy complejas y difíciles de describir. Hacer que los parámetros predeterminados puedan aparecer en cualquier lugar solo boostía la complejidad.

Es una cuestión de convención de llamadas. Convención de llamada: cuando llama a una función, los parámetros se envían en stack de derecha a izquierda. p.ej

 fun(int a, int b, int c); 

la stack es así: a b c entonces, si configura el valor predeterminado de izquierda a derecha de esta manera:

 fun(int a = 1, int b = 2, int c); 

y llame así:

 fun(4,5); 

su llamada significa establecer a = 4, b = 5 yc = sin valor; // ¡Cuál está mal!

si declaras la función así:

fun(int a, int b = 2, int c = 3);

y llame así: fun(4, 5);

su llamada significa establecer a = 4, b = 5 yc = valor predeterminado (3); // ¡lo cual está bien!

En conclusión, debe colocar el valor predeterminado de derecha a izquierda.

Jing Zeng es correcto. Me gustaría agregar mis comentarios aquí. Cuando se llama a una función, los argumentos se colocan en la stack de derecha a izquierda. Por ejemplo, digamos que tienes esta función arbitraria.

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

Aquí está el marco de stack para la función:

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

¡Este diagtwig de arriba es el marco de stack para esta función! Como puede ver, primero se empuja la b sobre la stack, luego se empuja a la stack. Después de eso, la dirección de retorno de la función se coloca en la stack. La dirección de retorno de la función mantiene la ubicación en main () desde donde se llamó originalmente a la función, y después de que la función se ejecuta, la ejecución del progtwig va a la dirección de retorno de esa función. Luego, cualquier variable local como c se inserta en la stack.

Ahora, la clave es que los argumentos se colocan en la stack de derecha a izquierda. Básicamente, cualquier parámetro predeterminado que se suministre son valores literales, que se almacenan en la sección de códigos de un archivo ejecutable. Cuando la ejecución del progtwig encuentra un parámetro predeterminado sin un argumento correspondiente, empuja ese valor literal en la parte superior de la stack. Luego mira a y empuja el valor del argumento a la parte superior de la stack. El puntero de la stack siempre apunta a la parte superior de la stack, la variable empujada más recientemente. Por lo tanto, cualquier valor literal que haya agregado a la stack como parámetros predeterminados está “detrás” del puntero de la stack.

Probablemente fue más eficiente para el comstackdor para primero empujar primero los valores literales predeterminados arbitrarios en la stack, ya que no están almacenados en una ubicación de memoria, y construir la stack rápidamente. Piensa en lo que habría sido si las variables se hubieran insertado primero en la stack y luego en los literales. El acceso a una ubicación de memoria para la CPU ocupa un tiempo relativamente largo en comparación con extraer un valor literal de un circuito o registro de CPU. Dado que lleva más tiempo insertar variables en la stack en lugar de literales, los literales tendrían que esperar, luego la dirección de retorno tendría que esperar y las variables locales tendrían que esperar también. Probablemente no sea una gran preocupación en cuanto a la eficiencia, pero esa es solo mi teoría de por qué los argumentos predeterminados siempre están en las posiciones más a la derecha de un encabezado de función en C ++. Significa que el comstackdor fue diseñado como tal.