Esta no es una pregunta de función lambda, sé que puedo asignar una 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 lo two
que parece inútil y lo three
que parece mucho más complicado, pero no lo permite one
.
EDITAR:
A partir de las respuestas, parece que la declaración en el código puede evitar la contaminación del espacio de nombres, lo que esperaba escuchar es por qué se ha permitido la capacidad de declarar funciones pero no se ha permitido la capacidad de definir funciones.
c++
functor
function-declaration
Jonathan Mee
fuente
fuente
one
es una definición de función , las otras dos son declaraciones .Respuestas:
No es obvio por qué
one
no está permitido; Las funciones anidadas se propusieron hace mucho tiempo en N0295 que dice:Obviamente, esta propuesta fue rechazada, pero dado que no tenemos actas de reuniones disponibles en línea para
1993
, no tenemos una fuente posible para la justificación de este rechazo.De hecho esta propuesta se apunta en expresiones Lambda y cierres para C ++ como una posible alternativa:
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 su pregunta:
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 de C ++
3.4.1
[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) }
fuente
::g(parm, 1)
para llamar a la función en el espacio de nombres global? ¿O llamarg(parm, 1.0f);
cuál debería resultar en una mejor coincidencia para el deseadog
?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"; }
En mi opinión, 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 da lugar a un comportamiento indefinido que no será diagnosticado por el compilador.
fuente
some_type f();
y una definición en otra unidad de traducciónanother_type f() {...}
. El compilador no puede decirle que estos no coinciden, y llamarf
con la declaración incorrecta dará un comportamiento indefinido. Por lo tanto, es una buena idea tener solo una declaración, en un encabezado, e incluir ese encabezado donde se define la función, así como donde se usa.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 lamain
función .Eso es razonable si solo desea que el nombre
two
esté disponiblemain()
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; }
sin necesidad de archivos de encabezado. compilar y vincular con
se compilará y ejecutará, y el programa devolverá 0 como se esperaba.
fuente
two
tendría que estar también definido en el archivo causando así la contaminación de todos modos?two()
podría definirse en una unidad de compilación completamente diferente.Puede 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 que permita declaraciones dentro de funciones para manejar otras declaraciones (por ejemplo,
int x;
) dentro de una función de todos modos.Por lo general, 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 en particular dentro de una función probablemente agregaría complejidad adicional, porque el compilador necesitaría una verificación completamente gratuita para ver si ya está mirando el código dentro de una definición de función y, en base a eso, decidir si permitir o prohibir esta en particular. declaración.
Eso deja la cuestión de en qué se diferencia una función anidada. Una función anidada es diferente debido a cómo afecta la generación de código. En lenguajes que permiten funciones anidadas (por ejemplo, Pascal) normalmente se espera que el código de la función anidada tenga acceso directo a las variables de la función en la que está anidada. 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 en la pila algún bloque de espacio para las variables locales. Todas las variables locales se asignan en ese único bloque y cada variable se trata simplemente como un desplazamiento desde el principio (o el 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 (asumiendo que no optimizó el código adicional) podría generar código para esto aproximadamente 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 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 de todas las funciones en las que está anidada. En lugar de solo tener un "stack_pointer" desde el cual calcula un desplazamiento, necesita caminar hacia arriba en 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
bar
está anidado dentro defoo
, entoncesbar
puede buscar la pila en el puntero de la pila anterior para acceder afoo
las variables. ¿Correcto?¡Incorrecto! Bueno, hay casos en los que esto puede ser cierto, pero no es necesariamente el caso. En particular,
bar
podría ser recursivo, en cuyo caso una determinada invocación debar
podría tener que buscar un número casi arbitrario de niveles en la pila para encontrar las variables de la función circundante. En términos generales, debe hacer una de dos cosas: o coloca algunos datos adicionales en la pila, para que pueda buscar una copia de seguridad en la pila en tiempo de ejecución para encontrar el marco de pila de la función que lo rodea, o bien pasa un puntero a el marco de 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 en profundidad, por lo que debe estar listo para pasar una cantidad arbitraria de parámetros ocultos. Eso significa que normalmente termina con algo así como una lista vinculada de marcos de pila a funciones circundantes,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 normalmente) más lento que el acceso a las variables verdaderamente locales. Y, por supuesto, el compilador tiene que generar código para encontrar los marcos de pila correctos, acceder a las variables a través de cualquiera de un número arbitrario de marcos de pila, etc.
Esta es la complejidad que C evitaba al prohibir las funciones anidadas. Ahora bien, es cierto que un compilador de C ++ actual es un tipo de bestia bastante diferente de un compilador de C antiguo de los años 70. Con cosas como herencia virtual múltiple, un compilador de C ++ tiene que lidiar con cosas en 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 de C ++ actual (y algunas, como gcc, ya las admiten).
Al mismo tiempo, tampoco suele aportar mucha utilidad. En particular, si desea definir algo que actúe como una función dentro de una función, puede usar una expresión lambda. Lo que esto crea en realidad es un objeto (es decir, una instancia de alguna clase) que sobrecarga la llamada a la función operator (
operator()
) pero aún brinda capacidades similares a funciones. Sin embargo, hace que capturar (o no) datos del contexto circundante sea más explícito, lo que le permite usar los mecanismos existentes en lugar de inventar un mecanismo completamente nuevo y un conjunto de reglas para su uso.En pocas palabras: aunque inicialmente pueda parecer 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 en realidad mucho más complejas de soportar que las declaraciones anidadas.
fuente
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. Imagina que necesitas usar la
int two(int bar);
función dentro del método principal. Pero se define debajo de lamain()
función, por lo que la declaración de función dentro de la función te obliga a usar 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 apropiados.
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; }
fuente
Esta característica de 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 funciones?) . No sé si los programadores modernos de C utilizan mucho esta función 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 versiones anteriores de C ++ a C (supongo :)).
Gracias al comentario a continuación:
El prototipo de función tiene como alcance 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
.fuente
En realidad, hay un caso de uso que posiblemente sea útil. Si desea asegurarse de que se llame a una determinada función (y su código se compile), sin importar 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://stackoverflow.com/a/929902/3150802 , a través de TeKa, https://stackoverflow.com/a/8821992/3150802 ).
Esto puede ser especialmente útil si tiene que incluir encabezados que no controla, o si tiene una macro de varias líneas que puede usarse en código desconocido.
La clave es que una declaración local reemplaza a las declaraciones anteriores en el bloque 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 dice que ignore los símbolos duplicados. O no enlaza con la biblioteca.fuente
f
definido en tu ejemplo? ¿No terminaría con un error de redefinición de función ya que estos difieren solo por el tipo de retorno?void f()
yint f()
en C ++ porque el valor de retorno de una función no es parte de la firma de la función en C ++. Cambie la segunda declaración aint f(int)
y eliminaré mi voto negativo.i = f();
después de declararvoid f()
. "Sin distinción" es solo la mitad de la verdad ;-). De hecho, utilicé "firmas" de funciones no sobrecargables porque, de lo contrario, toda la circunstancia sería innecesaria en C ++ porque dos funciones con diferentes tipos / números de parámetros podrían coexistir felizmente.Esta no es una respuesta a la pregunta del OP, sino más bien 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 principal contraejemplo de la supuesta inocuidad de las declaraciones de funciones anidadas es el infame Most Vexing Parse . En mi opinión, la extensión de la confusión causada por ella es suficiente para justificar 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 contamina con bastante frecuencia la definición de clase con numerosas funciones privadas, cada una de las cuales se utiliza exactamente en un llamador. Una declaración de función anidada sería mucho más limpia.
fuente
Respondiendo específicamente a esta pregunta:
Porque considera este código:
int main() { int foo() { // Do something return 0; } return 0; }
Preguntas para diseñadores de lenguajes:
foo()
estar disponible para otras funciones?int main(void)::foo()
?fuente
Solo quería señalar que el compilador GCC le permite declarar funciones dentro de funciones. Lea más sobre esto aquí . 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, la encontré ú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 debe llamarse desde main, por lo que lo que hace es declararla como normal. Pero luego te das cuenta de que 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 llamar desde esa función y solo esa función.
Mi punto es que declarar encabezados de función 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 al delegar en alguna otra función de la que solo él es consciente, casi dando la ilusión de un anidado función .
fuente
Las declaraciones de funciones anidadas están permitidas probablemente para 1. Reenviar referencias 2. Para poder declarar un puntero a funciones y pasar otras funciones en un ámbito limitado.
Las definiciones de funciones anidadas no están permitidas probablemente debido a problemas como 1. Optimización 2. Recurrencia (funciones definidas anidadas y adjuntas) 3. Reincorporación 4. Concurrencia y otros problemas de acceso multiproceso.
Desde mi limitado entendimiento :)
fuente