una - funciones que retornan valores en c++
¿Por qué no puedo definir una función dentro de otra función? (11)
Bueno, la respuesta es "razones históricas". En C, podría tener declaraciones de funciones en el alcance del bloque, y los diseñadores de C ++ no vieron el beneficio de eliminar esa opción.
Un ejemplo de uso sería:
#include <iostream>
int main()
{
int func();
func();
}
int func()
{
std::cout << "Hello/n";
}
OMI, esta es una mala idea porque es fácil cometer un error al proporcionar una declaración que no coincide con la definición real de la función, lo que lleva a un comportamiento indefinido que el compilador no diagnosticará.
Esta no es una pregunta de función lambda, sé que puedo asignar un lambda a una variable.
¿Cuál es el punto de permitirnos declarar, pero no definir una función dentro del código?
Por ejemplo:
#include <iostream>
int main()
{
// This is illegal
// int one(int bar) { return 13 + bar; }
// This is legal, but why would I want this?
int two(int bar);
// This gets the job done but man it''s complicated
class three{
int m_iBar;
public:
three(int bar):m_iBar(13 + bar){}
operator int(){return m_iBar;}
};
std::cout << three(42) << ''/n'';
return 0;
}
Entonces, lo que quiero saber es ¿por qué C ++ permitiría
two
que parecen inútiles y
three
que parecen mucho más complicados, pero rechazan
one
?
EDITAR:
Según las respuestas, parece que la declaración en el código puede evitar la contaminación del espacio de nombres, aunque esperaba escuchar por qué se ha permitido la capacidad de declarar funciones, pero no se ha permitido la capacidad de definir funciones.
En el ejemplo que da,
void two(int)
se declara como una función externa, y esa declaración
solo es válida dentro del alcance de la función
main
.
Eso es razonable si solo desea que el nombre
two
esté disponible dentro de
main()
para evitar contaminar el espacio de nombres global dentro de la unidad de compilación actual.
Ejemplo en respuesta a comentarios:
main.cpp:
int main() {
int foo();
return foo();
}
foo.cpp:
int foo() {
return 0;
}
No hay necesidad de archivos de encabezado. compilar y vincular con
c++ main.cpp foo.cpp
compilará y se ejecutará, y el programa devolverá 0 como se esperaba.
En realidad, hay un caso de uso que es posiblemente útil. Si desea asegurarse de que se llama a una determinada función (y su código se compila), no importa lo que declare el código circundante, puede abrir su propio bloque y declarar el prototipo de la función en él. (La inspiración es originalmente de Johannes Schaub, https://.com/a/929902/3150802 , a través de TeKa, https://.com/a/8821992/3150802 ).
Esto puede ser particularmente útil si tiene que incluir encabezados que no controle, o si tiene una macro multilínea que puede usarse en código desconocido.
La clave es que una declaración local sustituye a las declaraciones anteriores en el bloque de encierro más interno. Si bien eso puede introducir errores sutiles (y, creo, está prohibido en C #), se puede usar conscientemente. Considerar:
// somebody''s header
void f();
// your code
{ int i;
int f(); // your different f()!
i = f();
// ...
}
La vinculación puede ser interesante porque es probable que los encabezados pertenezcan a una biblioteca, pero supongo que puede ajustar los argumentos del vinculador para que
f()
se resuelva en su función cuando se considere esa biblioteca.
O le dices que ignore los símbolos duplicados.
O no se vincula con la biblioteca.
Esta característica del lenguaje fue heredada de C, donde sirvió para algún propósito en los primeros días de C (¿quizás el alcance de la declaración de función?) . No sé si los programadores modernos de C usan mucho esta característica y lo dudo sinceramente.
Entonces, para resumir la respuesta:
no hay ningún propósito para esta característica en C ++ moderno (que yo sepa, al menos), está aquí debido a la compatibilidad con C ++ - a-C (supongo :)).
Gracias al comentario a continuación:
El prototipo de la función está limitado a la función en la que se declara, por lo que uno puede tener un espacio de nombres global más ordenado, haciendo referencia a funciones / símbolos externos sin
#include
.
Esta no es una respuesta a la pregunta de OP, sino una respuesta a varios comentarios.
No estoy de acuerdo con estos puntos en los comentarios y respuestas: 1 que las declaraciones anidadas son supuestamente inofensivas, y 2 que las definiciones anidadas son inútiles.
1 El contraejemplo principal para la supuesta inocuidad de las declaraciones de funciones anidadas es el infame Parse más irritante . OMI, la difusión de la confusión causada por ella es suficiente para garantizar una regla adicional que prohíbe las declaraciones anidadas.
2 El primer contraejemplo de la supuesta inutilidad de las definiciones de funciones anidadas es la necesidad frecuente de realizar la misma operación en varios lugares dentro de exactamente una función. Hay una solución obvia para esto:
private:
inline void bar(int abc)
{
// Do the repeating operation
}
public:
void foo()
{
int a, b, c;
bar(a);
bar(b);
bar(c);
}
Sin embargo, esta solución a menudo contamina la definición de clase con numerosas funciones privadas, cada una de las cuales se usa exactamente en una persona que llama. Una declaración de función anidada sería mucho más limpia.
La primera es una definición de función, y no está permitida. Obviamente, wt es el uso de poner una definición de una función dentro de otra función.
Pero los otros dos son solo declaraciones.
Imagine que necesita usar
int two(int bar);
funcionar dentro del método principal.
Pero se define debajo de la función
main()
, por lo que esa declaración de función dentro de la función hace que use esa función con declaraciones.
Lo mismo se aplica al tercero. Las declaraciones de clase dentro de la función le permiten usar una clase dentro de la función sin proporcionar un encabezado o referencia apropiado.
int main()
{
// This is legal, but why would I want this?
int two(int bar);
//Call two
int x = two(7);
class three {
int m_iBar;
public:
three(int bar):m_iBar(13 + bar) {}
operator int() {return m_iBar;}
};
//Use class
three *threeObj = new three();
return 0;
}
Las declaraciones de funciones anidadas se permiten probablemente para 1. Reenviar referencias 2. Para poder declarar un puntero a funciones y pasar otras funciones en un alcance limitado.
Las definiciones de funciones anidadas no están permitidas probablemente debido a problemas como 1. Optimización 2. Recursión (funciones definidas anidadas y anidadas) 3. Reincidencia 4. Concurrencia y otros problemas de acceso multiproceso.
Desde mi comprensión limitada :)
No es obvio por qué
one
no está permitido;
funciones anidadas fueron propuestas hace mucho tiempo en
N0295
que dice:
Discutimos la introducción de funciones anidadas en C ++. Las funciones anidadas se entienden bien y su introducción requiere poco esfuerzo por parte de los vendedores del compilador, los programadores o el comité. Las funciones anidadas ofrecen ventajas significativas, [...]
Obviamente, esta propuesta fue rechazada, pero dado que no tenemos las actas de las reuniones disponibles en línea para
1993
, no tenemos una posible fuente para justificar este rechazo.
De hecho, esta propuesta se observa en las expresiones y cierres de Lambda para C ++ como una posible alternativa:
Un artículo [Bre88] y la propuesta N0295 al comité de C ++ [SH93] sugieren agregar funciones anidadas a C ++. Las funciones anidadas son similares a las expresiones lambda, pero se definen como declaraciones dentro del cuerpo de una función, y el cierre resultante no se puede usar a menos que esa función esté activa. Estas propuestas tampoco incluyen agregar un nuevo tipo para cada expresión lambda, sino implementarlas más como funciones normales, lo que incluye permitir que un tipo especial de puntero de función se refiera a ellas. Ambas propuestas son anteriores a la adición de plantillas a C ++, por lo que no mencionan el uso de funciones anidadas en combinación con algoritmos genéricos. Además, estas propuestas no tienen forma de copiar variables locales en un cierre, por lo que las funciones anidadas que producen son completamente inutilizables fuera de su función de cierre.
Teniendo en cuenta que ahora tenemos lambdas, es poco probable que veamos funciones anidadas ya que, como se describe en el documento, son alternativas para el mismo problema y las funciones anidadas tienen varias limitaciones en relación con las lambdas.
En cuanto a esta parte de tu pregunta:
// This is legal, but why would I want this? int two(int bar);
Hay casos en los que esta sería una forma útil de llamar a la función que desea.
El borrador de la sección estándar
3.4.1
C ++
[basic.lookup.unqual]
nos da un ejemplo interesante:
namespace NS {
class T { };
void f(T);
void g(T, int);
}
NS::T parm;
void g(NS::T, float);
int main() {
f(parm); // OK: calls NS::f
extern void g(NS::T, float);
g(parm, 1); // OK: calls g(NS::T, float)
}
Puedes hacer estas cosas, en gran parte porque en realidad no son tan difíciles de hacer.
Desde el punto de vista del compilador, tener una declaración de función dentro de otra función es bastante trivial de implementar.
El compilador necesita un mecanismo para permitir que las declaraciones dentro de las funciones manejen otras declaraciones (por ejemplo,
int x;
) de todos modos dentro de una función.
Normalmente tendrá un mecanismo general para analizar una declaración. Para el tipo que escribe el compilador, realmente no importa en absoluto si ese mecanismo se invoca al analizar el código dentro o fuera de otra función; es solo una declaración, por lo que cuando ve lo suficiente para saber que hay una declaración, invoca la parte del compilador que se ocupa de las declaraciones.
De hecho, prohibir estas declaraciones particulares dentro de una función probablemente agregaría una complejidad adicional, porque el compilador necesitaría una verificación totalmente gratuita para ver si ya está mirando el código dentro de una definición de función y en función de eso decidir si permitir o prohibir este particular declaración.
Eso deja la pregunta de cómo una función anidada es diferente. Una función anidada es diferente debido a cómo afecta la generación de código. En los lenguajes que permiten funciones anidadas (por ejemplo, Pascal), normalmente espera que el código en la función anidada tenga acceso directo a las variables de la función en la que está anidado. Por ejemplo:
int foo() {
int x;
int bar() {
x = 1; // Should assign to the `x` defined in `foo`.
}
}
Sin funciones locales, el código para acceder a las variables locales es bastante simple. En una implementación típica, cuando la ejecución ingresa a la función, se asigna un bloque de espacio para variables locales en la pila. Todas las variables locales se asignan en ese bloque único, y cada variable se trata simplemente como un desplazamiento desde el principio (o final) del bloque. Por ejemplo, consideremos una función como esta:
int f() {
int x;
int y;
x = 1;
y = x;
return y;
}
Un compilador (suponiendo que no optimizó el código extra) podría generar código para esto más o menos equivalente a esto:
stack_pointer -= 2 * sizeof(int); // allocate space for local variables
x_offset = 0;
y_offset = sizeof(int);
stack_pointer[x_offset] = 1; // x = 1;
stack_pointer[y_offset] = stack_pointer[x_offset]; // y = x;
return_location = stack_pointer[y_offset]; // return y;
stack_pointer += 2 * sizeof(int);
En particular, tiene una ubicación que apunta al comienzo del bloque de variables locales, y todo el acceso a las variables locales es como compensaciones desde esa ubicación.
Con las funciones anidadas, ese ya no es el caso; en cambio, una función tiene acceso no solo a sus propias variables locales, sino a las variables locales a todas las funciones en las que está anidada. En lugar de simplemente tener un "stack_pointer" desde el cual calcula un desplazamiento, necesita retroceder por la pila para encontrar los stack_pointers locales a las funciones en las que está anidado.
Ahora, en un caso trivial, eso tampoco es tan terrible: si la
bar
está anidada dentro de
foo
, entonces la
bar
puede buscar la pila en el puntero de la pila anterior para acceder a las variables de
foo
.
¿Derecho?
¡Incorrecto!
Bueno, hay casos en que esto puede ser cierto, pero no es necesariamente el caso.
En particular, la
bar
podría ser recursiva, en cuyo caso una invocación dada de la
bar
podría tener que mirar un número casi arbitrario de niveles para respaldar la pila para encontrar las variables de la función circundante.
En términos generales, debe hacer una de estas dos cosas: o coloca algunos datos adicionales en la pila, para que pueda buscar una copia de seguridad de la pila en el tiempo de ejecución para encontrar el marco de la pila de la función que lo rodea, o bien puede pasar un puntero a el marco de la pila de la función circundante como un parámetro oculto para la función anidada.
Ah, pero tampoco hay necesariamente una sola función circundante: si puede anidar funciones, probablemente pueda anidarlas (más o menos) arbitrariamente profundas, por lo que debe estar listo para pasar un número arbitrario de parámetros ocultos.
Eso significa que generalmente terminas con algo así como una lista vinculada de marcos de pila a funciones circundantes, y el acceso a las variables de funciones circundantes se realiza recorriendo esa lista vinculada para encontrar su puntero de pila y luego accediendo a un desplazamiento desde ese puntero de pila.
Eso, sin embargo, significa que el acceso a una variable "local" puede no ser un asunto trivial. Encontrar el marco de pila correcto para acceder a la variable puede no ser trivial, por lo que el acceso a las variables de las funciones circundantes también es (al menos generalmente) más lento que el acceso a variables verdaderamente locales. Y, por supuesto, el compilador tiene que generar código para encontrar los cuadros de pila correctos, acceder a las variables a través de cualquiera de un número arbitrario de cuadros de pila, y así sucesivamente.
Esta es la complejidad que C estaba evitando al prohibir funciones anidadas. Ahora, es cierto que un compilador de C ++ actual es una bestia bastante diferente de un compilador de C vintage de 1970. Con cosas como la herencia virtual múltiple, un compilador de C ++ tiene que lidiar con cosas de esta misma naturaleza general en cualquier caso (es decir, encontrar la ubicación de una variable de clase base en tales casos también puede ser no trivial). Sobre una base porcentual, admitir funciones anidadas no agregaría mucha complejidad a un compilador actual de C ++ (y algunas, como gcc, ya las admiten).
Al mismo tiempo, rara vez agrega mucha utilidad tampoco.
En particular, si desea definir algo que
actúa
como una función dentro de una función, puede usar una expresión lambda.
Lo que esto realmente crea es un objeto (es decir, una instancia de alguna clase) que sobrecarga el operador de llamada a la función (
operator()
) pero que aún ofrece capacidades similares a las funciones.
Sin embargo, hace que la captura (o no) de datos del contexto circundante sea más explícita, lo que le permite utilizar los mecanismos existentes en lugar de inventar un mecanismo completamente nuevo y un conjunto de reglas para su uso.
En pocas palabras: aunque inicialmente parezca que las declaraciones anidadas son difíciles y las funciones anidadas son triviales, más o menos lo contrario es cierto: las funciones anidadas son mucho más complejas de soportar que las declaraciones anidadas.
Respondiendo específicamente esta pregunta:
Según las respuestas, parece que la declaración en el código puede evitar la contaminación del espacio de nombres, aunque esperaba escuchar por qué se ha permitido la capacidad de declarar funciones, pero no se ha permitido la capacidad de definir funciones.
Porque considere este código:
int main()
{
int foo() {
// Do something
return 0;
}
return 0;
}
Preguntas para diseñadores de idiomas:
-
¿Debería
foo()
estar disponible para otras funciones? -
Si es así, ¿cuál debería ser su nombre?
int main(void)::foo()
? - (Tenga en cuenta que 2 no sería posible en C, el creador de C ++)
- Si queremos una función local, ya tenemos una forma: convertirla en un miembro estático de una clase definida localmente. Entonces, ¿deberíamos agregar otro método sintáctico para lograr el mismo resultado? ¿Por qué hacer eso? ¿No aumentaría la carga de mantenimiento de los desarrolladores del compilador C ++?
- Y así...
Solo quería señalar que el compilador GCC le permite declarar funciones dentro de funciones. Lea más sobre esto here . También con la introducción de lambdas a C ++, esta pregunta es un poco obsoleta ahora.
La capacidad de declarar encabezados de función dentro de otras funciones, me pareció útil en el siguiente caso:
void do_something(int&);
int main() {
int my_number = 10 * 10 * 10;
do_something(my_number);
return 0;
}
void do_something(int& num) {
void do_something_helper(int&); // declare helper here
do_something_helper(num);
// Do something else
}
void do_something_helper(int& num) {
num += std::abs(num - 1337);
}
¿Qué tenemos aquí? Básicamente, tiene una función que se supone que se llama desde main, por lo que lo que hace es que la declare como normal. Pero luego te das cuenta, esta función también necesita otra función para ayudarlo con lo que está haciendo. Entonces, en lugar de declarar esa función auxiliar por encima de main, la declaras dentro de la función que la necesita y luego se puede invocar desde esa función y solo esa función.
Mi punto es que declarar encabezados de funciones dentro de funciones puede ser un método indirecto de encapsulación de funciones, que permite que una función oculte algunas partes de lo que está haciendo delegando a otra función de la que solo es consciente, casi dando la ilusión de un anidado función