uniones - ¿Por qué no se deben declarar los métodos de estructuras en C++?
typedef struct c ejemplo (3)
" ¿ Por qué puedo llevar a cabo un concepto similar en una estructura, pero no obtener un grito por ello?"
En una definición de struct
o class
, presenta la interfaz pública a una clase y es mucho más fácil de entender, buscar y mantener / actualizar esa API si se presenta en:
- un orden predecible, con
- desorden mínimo.
Para un orden predecible, las personas tienen sus propios estilos y hay un poco de "arte" involucrado, pero por ejemplo yo uso cada especificador de acceso como mucho una vez y siempre public
antes protected
antes que private
, luego dentro de esos normalmente pongo typedef
s, const
data, constructores, destructores, funciones mutantes / no const, funciones const
, static
s, friend
s ....
Para minimizar el desorden, si una función está definida en la clase, también podría ser sin una declaración previa. Tener ambos tiende solo a ofuscar la interfaz.
Esto es diferente de las funciones que no son miembros de una clase, donde las personas que les gusta la programación descendente usan declaraciones de funciones y ocultan las definiciones más adelante en el archivo, en que:
las personas que prefieren un estilo de programación de abajo hacia arriba no apreciarán que se les obligue a tener declaraciones separadas en las clases o a abandonar la práctica de agrupar frecuentemente por especificador de acceso
Las clases tienen estadísticamente más probabilidades de tener muchas funciones muy cortas, en gran parte porque proporcionan encapsulación y envuelven muchos accesos triviales de miembros de datos o proporcionan sobrecarga de operador, operadores de colada, constructores implícitos y otras características de conveniencia que no son relevantes para no OO. funciones no miembros. Eso hace que una separación forzada constante de declaraciones y definiciones sea más dolorosa para muchas clases (no tanto en las interfaces públicas donde las definiciones pueden estar en un archivo separado, pero definitivamente para, por ejemplo, clases en espacios de nombres anónimos que soportan la unidad de traducción actual).
La mejor práctica es que las clases no se llenen en una interfaz muy amplia ... generalmente desea un núcleo funcional y algunas funciones de conveniencia discrecionales, después de lo cual vale la pena considerar qué se puede agregar como funciones no miembro. El
std::string
menudo se dice que tiene demasiadas funciones de miembro, aunque personalmente creo que es bastante razonable. Aún así, esto también difiere de un archivo de cabecera que declara una interfaz de biblioteca, donde se puede esperar que la funcionalidad exhaustiva se acumule, haciendo que sea más deseable una separación inclusoinline
implementación eninline
.
Tome, por ejemplo, el siguiente código:
#include <iostream>
#include <string>
int main()
{
print("Hello!");
}
void print(std::string s) {
std::cout << s << std::endl;
}
Cuando intento construir esto, obtengo lo siguiente:
program.cpp: In function ‘int main()’:
program.cpp:6:16: error: ‘print’ was not declared in this scope
Lo cual tiene sentido.
Entonces, ¿por qué puedo llevar a cabo un concepto similar en una estructura, pero no obtener un grito por ello?
struct Snake {
...
Snake() {
...
addBlock(Block(...));
}
void addBlock(Block block) {
...
}
void update() {
...
}
} snake1;
¡No solo no recibo advertencias, sino que el programa realmente compila! ¡Sin error! ¿Es esta la naturaleza de las estructuras? ¿Que esta pasando aqui? Claramente, se addBlock(Block)
antes de que se declarara el método.
C ++ estándar 3.4.1:
.4:
Un nombre utilizado en ámbito global, fuera de cualquier función, clase o espacio de nombres declarado por el usuario, se declarará antes de su uso en el ámbito global.
Esta es la razón por la cual las variables y funciones globales no pueden usarse antes de una declaración previa.
.5:
Un nombre utilizado en un espacio de nombres declarado por el usuario fuera de la definición de cualquier función o clase deberá declararse antes de su uso en ese espacio de nombres o antes de su uso en un espacio de nombres que contenga su espacio de nombres.
Lo mismo que acaba de escribir nuevamente, ya que el párrafo .4 restringió explícitamente su frase a "global", este párrafo ahora dice "por cierto, es cierto también en la gente de nombre ..."
.7:
Un nombre utilizado en la definición de una clase X fuera de un cuerpo de función miembro o definición de clase anidada29 debe declararse de una de las siguientes maneras: - antes de su uso en la clase X o ser miembro de una clase base de X (10.2) , o - si X es una clase anidada de clase Y (9.7), antes de la definición de X en Y, o será un miembro de una clase base de Y (esta búsqueda se aplica a su vez a las clases adjuntas de Y, empezando por la clase envolvente más interna), 30 o - si X es una clase local (9.8) o es una clase anidada de una clase local, antes de la definición de clase X en un bloque que encierra la definición de clase X, o - si X es una miembro del espacio de nombres N, o es una clase anidada de una clase que es miembro de N, o es una clase local o una clase anidada dentro de una clase local de una función que es miembro de N, antes de la definición de clase X en namespace N o en uno de los espacios de nombres adjuntos de N.
Creo que esto habla de todo el código que no se encuentra en el código ejecutado por la CPU (por ejemplo, el código declarativo).
y finalmente la parte interesante:
3.3.7 Alcance de clase [basic.scope.class]
1 Las siguientes reglas describen el alcance de los nombres declarados en las clases.
1) El alcance potencial de un nombre declarado en una clase consiste no solo en la región declarativa que sigue al punto de declaración del nombre, sino también en todos los cuerpos de funciones, inicializadores ortográficos de miembros de datos no estáticos y argumentos predeterminados en esa clase (incluyendo tales cosas en clases anidadas).
2) Un nombre N usado en una clase S se referirá a la misma declaración en su contexto y cuando se vuelva a evaluar en el alcance completo de S. No se requiere diagnóstico para una violación de esta regla.
3) Si la reordenación de las declaraciones de miembros en una clase produce un programa válido alternativo bajo (1) y (2), el programa está mal formado, no se requiere diagnóstico.
particularmente, en el último punto usan una forma negativa para definir que "cualquier pedido es posible" porque si el reordenamiento cambiara la búsqueda, entonces hay un problema. es una forma negativa de decir "puedes reordenar cualquier cosa y está bien, no cambia nada".
efectivamente diciendo, en una clase, la declaración se busca en una compilación de dos fases.
Una struct
en C ++ es en realidad una definición de class
donde su contenido es public
, a menos que se especifique lo contrario incluyendo una sección protegida: o privada.
Cuando el compilador ve una class
o struct
, primero digiere todas las declaraciones dentro del bloque ( {}
) antes de operar en ellas.
En el caso del método regular, el compilador aún no ha visto el tipo declarado.