La compañía en la que trabajo está inicializando todas sus estructuras de datos a través de una función de inicialización como esta:
//the structure
typedef struct{
int a,b,c;
} Foo;
//the initialize function
InitializeFoo(Foo* const foo){
foo->a = x; //derived here based on other data
foo->b = y; //derived here based on other data
foo->c = z; //derived here based on other data
}
//initializing the structure
Foo foo;
InitializeFoo(&foo);
Obtuve algunos intentos de inicializar mis estructuras de esta manera:
//the structure
typedef struct{
int a,b,c;
} Foo;
//the initialize function
Foo ConstructFoo(int a, int b, int c){
Foo foo;
foo.a = a; //part of parameter input (inputs derived outside of function)
foo.b = b; //part of parameter input (inputs derived outside of function)
foo.c = c; //part of parameter input (inputs derived outside of function)
return foo;
}
//initialize (or construct) the structure
Foo foo = ConstructFoo(x,y,z);
¿Hay alguna ventaja para uno sobre el otro?
¿Cuál debería hacer y cómo lo justificaría como una mejor práctica?
c
coding-style
constructors
initialization
Trevor Hickey
fuente
fuente
InitializeFoo()
es un constructor. La única diferencia con un constructor de C ++ es que elthis
puntero se pasa explícitamente en lugar de implícitamente. El código compilado deInitializeFoo()
y un C ++ correspondienteFoo::Foo()
es exactamente el mismo.Respuestas:
En el segundo enfoque, nunca tendrás un Foo medio inicializado. Poner toda la construcción en un solo lugar parece un lugar más sensible y obvio.
Pero ... la primera forma no es tan mala, y a menudo se usa en muchas áreas (incluso hay una discusión sobre la mejor forma de inyectar dependencia, ya sea la inyección de propiedades como la primera forma o la inyección del constructor como la segunda) . Tampoco está mal.
Entonces, si ninguno de los dos está mal y el resto de la compañía usa el enfoque n. ° 1, entonces debería ajustarse a la base de código existente y no tratar de estropearla introduciendo un nuevo patrón. Este es realmente el factor más importante en el juego aquí, juega bien con tus nuevos amigos y no trates de ser ese copo de nieve especial que hace las cosas de manera diferente.
fuente
void SetupFoo(Foo *out, int a, int b, int c)
Foo
estructura "semi-inicializada" ? El primer enfoque también realiza toda la inicialización en un solo lugar. (¿O está considerando que una estructura no inicializadaFoo
está "Ambos enfoques agrupan el código de inicialización en una sola llamada de función. Hasta aquí todo bien.
Sin embargo, hay dos problemas con el segundo enfoque:
El segundo no construye realmente el objeto resultante, inicializa otro objeto en la pila, que luego se copia al objeto final. Es por eso que vería el segundo enfoque como ligeramente inferior. El retroceso que ha recibido probablemente se deba a esta copia extraña.
Esto es aún peor cuando deriva una clase
Derived
deFoo
(las estructuras se usan principalmente para la orientación de objetos en C): con el segundo enfoque, la funciónConstructDerived()
llamaríaConstructFoo()
, copie elFoo
objeto temporal resultante en la ranura de la superclase de unDerived
objeto; terminar la inicialización delDerived
objeto; solo para que el objeto resultante se vuelva a copiar al volver. Agregue una tercera capa, y todo se vuelve completamente ridículo.Con el segundo enfoque, las
ConstructClass()
funciones no tienen acceso a la dirección del objeto en construcción. Esto hace que sea imposible vincular objetos durante la construcción, ya que es necesario cuando un objeto necesita registrarse con otro objeto para una devolución de llamada.Finalmente, no todas
structs
son clases completas. Algunosstructs
efectivamente solo agrupan un conjunto de variables, sin restricciones internas a los valores de estas variables.typedef struct Point { int x, y; } Point;
Sería un buen ejemplo de esto. Para estos el uso de una función inicializadora parece excesivo. En estos casos, la sintaxis literal compuesta puede ser conveniente (es C99):o
fuente
int x = 3;
no almacenar la cadenax
en el binario. La dirección y los puntos de herencia son buenos; El supuesto fracaso de la elisión es una tontería.unsigned char
no sea permitir optimizaciones para las cuales el La regla de alias estricta sería insuficiente, al tiempo que aclara las expectativas del programador.Dependiendo del contenido de la estructura y del compilador particular que se utilice, cualquier enfoque podría ser más rápido. Un patrón típico es que las estructuras que cumplen ciertos criterios pueden ser devueltas en los registros; para las funciones que devuelven otros tipos de estructura, el llamante debe asignar espacio para la estructura temporal en algún lugar (generalmente en la pila) y pasar su dirección como un parámetro "oculto"; En los casos en que el retorno de una función se almacena directamente en una variable local cuya dirección no se encuentra en ningún código externo, algunos compiladores pueden pasar la dirección de esa variable directamente.
Si un tipo de estructura satisface los requisitos de una implementación particular para que se devuelva en registros (por ejemplo, que no sea más grande que una palabra de máquina, o que complete exactamente dos palabras de máquina) que tenga una función de retorno, la estructura puede ser más rápida que pasar la dirección de una estructura, especialmente ya que exponer la dirección de una variable al código externo que podría mantener una copia de ella podría impedir algunas optimizaciones útiles. Si un tipo no satisface tales requisitos, el código generado para una función que devuelve una estructura será similar al de una función que acepta un puntero de destino; el código de llamada probablemente sería más rápido para el formulario que toma un puntero, pero ese formulario podría perder algunas oportunidades de optimización.
Es una pena que C no proporcione un medio para decir que una función tiene prohibido guardar una copia de un puntero pasado (semántica similar a una referencia de C ++) ya que pasar un puntero restringido obtendría las ventajas de rendimiento directo de pasar un puntero a un objeto preexistente, pero al mismo tiempo evite los costos semánticos de requerir que un compilador considere la dirección de una variable "expuesta".
fuente
Un argumento a favor del estilo "parámetro de salida" es que permite que la función devuelva un código de error.
Teniendo en cuenta algún conjunto de estructuras relacionadas, si la inicialización puede fallar para cualquiera de ellas, puede valer la pena hacer que todas usen el estilo de fuera del parámetro por razones de coherencia.
fuente
errno
.Supongo que su enfoque está en la inicialización a través del parámetro de salida frente a la inicialización a través del retorno, no la discrepancia en la forma en que se proporcionan los argumentos de construcción.
Tenga en cuenta que el primer enfoque podría permitir
Foo
ser opaco (aunque no con la forma en que lo usa actualmente), y eso generalmente es deseable para la mantenibilidad a largo plazo. Podría considerar, por ejemplo, una función que asigna unaFoo
estructura opaca sin inicializarla. O tal vez necesite reiniciar unaFoo
estructura que se inicializó previamente con diferentes valores.fuente