Valores predeterminados en una estructura C


92

Tengo una estructura de datos como esta:

    struct foo {
        int id;
        int route;
        int backup_route;
        int current_route;
    }

y una función llamada update () que se usa para solicitar cambios en ella.

  actualizar (42, dont_care, dont_care, nueva_ruta);

esto es realmente largo y si agrego algo a la estructura, tengo que agregar un 'dont_care' a CADA llamada para actualizar (...).

Estoy pensando en pasarle una estructura en su lugar, pero completar la estructura con 'dont_care' de antemano es aún más tedioso que simplemente deletrearlo en la llamada a la función. ¿Puedo crear la estructura en algún lugar con valores predeterminados de no me importa y simplemente establecer los campos que me importan después de declararlo como una variable local?

    struct foo bar = {.id = 42, .ruta_actual = nueva_ruta};
    actualizar (& bar);

¿Cuál es la forma más elegante de pasar solo la información que deseo expresar a la función de actualización?

y quiero que todo lo demás esté predeterminado en -1 (el código secreto para 'no me importa')

Respuestas:


155

Si bien las macros y / o funciones (como ya se sugirió) funcionarán (y podrían tener otros efectos positivos (es decir, enlaces de depuración)), son más complejas de lo necesario. La solución más simple y posiblemente más elegante es simplemente definir una constante que use para la inicialización de variables:

const struct foo FOO_DONT_CARE = { // or maybe FOO_DEFAULT or something
    dont_care, dont_care, dont_care, dont_care
};
...
struct foo bar = FOO_DONT_CARE;
bar.id = 42;
bar.current_route = new_route;
update(&bar);

Este código prácticamente no tiene una sobrecarga mental para comprender la indirección, y está muy claro qué campos barestablece explícitamente mientras ignora (con seguridad) los que no establece.


6
Otra ventaja de este enfoque es que no depende de las características de un C99 para funcionar.
D.Shawley

24
Cuando cambié a esto, 500 líneas "se cayeron" del proyecto. ¡Ojalá pudiera votar a favor dos veces sobre este!
Arthur Ulfeldt

4
PTHREAD_MUTEX_INITIALIZERusa esto también.
publicado el

He agregado una respuesta a continuación confiando en X-Macros por ser flexible en la cantidad de elementos presentes en la estructura.
Antonio

15

Puede cambiar su valor especial secreto a 0 y aprovechar la semántica de miembros de estructura predeterminada de C

struct foo bar = { .id = 42, .current_route = new_route };
update(&bar);

luego pasará 0 como miembros de la barra no especificada en el inicializador.

O puede crear una macro que hará la inicialización predeterminada por usted:

#define FOO_INIT(...) { .id = -1, .current_route = -1, .quux = -1, ## __VA_ARGS__ }

struct foo bar = FOO_INIT( .id = 42, .current_route = new_route );
update(&bar);

¿Funciona FOO_INIT? El compilador debería quejarse, creo, si inicializa el mismo miembro dos veces.
Jonathan Leffler

1
Lo probé con gcc y no se quejó. Además, no he encontrado nada en contra en el estándar, de hecho, hay un ejemplo en el que se menciona específicamente la sobrescritura.
jpalecek

2
C99: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf : 6.7.8.19: La inicialización ocurrirá en el orden de la lista de inicializadores, cada inicializador proporcionado para un subobjeto particular anulando cualquier inicializador previamente listado para el mismo subobjeto;
altendky

2
Si esto es cierto, ¿no podría definir DEFAULT_FOO, que tiene todos los inicializadores, y luego hacer struct foo bar = {DEFAULT_FOO, .id = 10}; ?
Juan

7

<stdarg.h>le permite definir funciones variadas (que aceptan un número indefinido de argumentos, como printf()). Definiría una función que toma un número arbitrario de pares de argumentos, uno que especifica la propiedad que se actualizará y otro que especifica el valor. Utilice una enumcadena o una para especificar el nombre de la propiedad.


Hola @Matt, ¿cómo asignar enumvariables al structcampo? ¿Codificarlo? Entonces esta función solo será aplicable a específicos struct.
misssprite

5

Quizás considere usar una definición de macro de preprocesador en su lugar:

#define UPDATE_ID(instance, id)  ({ (instance)->id= (id); })
#define UPDATE_ROUTE(instance, route)  ({ (instance)->route = (route); })
#define UPDATE_BACKUP_ROUTE(instance, route)  ({ (instance)->backup_route = (route); })
#define UPDATE_CURRENT_ROUTE(instance, route)  ({ (instance)->current_route = (route); })

Si su instancia de (struct foo) es global, entonces no necesita el parámetro para eso, por supuesto. Pero supongo que probablemente tengas más de una instancia. Usar el bloque ({...}) es un GNU-ismo que se aplica a GCC; es una forma agradable (segura) de mantener las líneas juntas como un bloque. Si más adelante necesita agregar más a las macros, como la verificación de validación de rango, no tendrá que preocuparse por romper cosas como declaraciones if / else, etc.

Esto es lo que haría, de acuerdo con los requisitos que indicó. Situaciones como esta son una de las razones por las que comencé a usar mucho Python; el manejo de los parámetros predeterminados se vuelve mucho más simple que nunca con C. (supongo que es un complemento de Python, lo siento ;-)


4

Un patrón que usa gobject es una función variable y valores enumerados para cada propiedad. La interfaz se parece a:

update (ID, 1,
        BACKUP_ROUTE, 4,
        -1); /* -1 terminates the parameter list */

Escribir una función varargs es fácil; consulte http://www.eskimo.com/~scs/cclass/int/sx11b.html . Simplemente haga coincidir los pares clave -> valor y establezca los atributos de estructura apropiados.


4

Como parece que solo necesita esta estructura para la update()función, no use una estructura para esto en absoluto, solo ofuscará su intención detrás de esa construcción. Quizás debería reconsiderar por qué está cambiando y actualizando esos campos y definir funciones o macros separadas para estos "pequeños" cambios.

p.ej


#define set_current_route(id, route) update(id, dont_care, dont_care, route)
#define set_route(id, route) update(id, dont_care, route, dont_care)
#define set_backup_route(id, route) update(id, route, dont_care, dont_care)

O incluso mejor, escriba una función para cada caso de cambio. Como ya notó, no cambia todas las propiedades al mismo tiempo, por lo tanto, haga posible cambiar solo una propiedad a la vez. Esto no solo mejora la legibilidad, sino que también lo ayuda a manejar los diferentes casos, por ejemplo, no tiene que verificar todos los "dont_care" porque sabe que solo se cambia la ruta actual.


algunos casos cambiarán 3 de ellos en la misma llamada.
Arthur Ulfeldt

Puede tener esta secuencia: set_current_rout (id, route); set_route (id, ruta); apply_change (id); o similar. Lo que creo es que puede tener una llamada de función para cada setter. Y haga el cálculo real (o lo que sea) en un momento posterior.
quinmars

4

¿Qué tal algo como:

struct foo bar;
update(init_id(42, init_dont_care(&bar)));

con:

struct foo* init_dont_care(struct foo* bar) {
  bar->id = dont_care;
  bar->route = dont_care;
  bar->backup_route = dont_care;
  bar->current_route = dont_care;
  return bar;
}

y:

struct foo* init_id(int id, struct foo* bar) {
  bar->id = id;
  return bar;
}

y en consecuencia:

struct foo* init_route(int route, struct foo* bar);
struct foo* init_backup_route(int backup_route, struct foo* bar);
struct foo* init_current_route(int current_route, struct foo* bar);

En C ++, un patrón similar tiene un nombre que no recuerdo en este momento.

EDITAR : Se llama el idioma de parámetro con nombre .


Creo que se llama constructor.
Desconocido

No, me refería a lo que puedes hacer: update (bar.init_id (42) .init_route (5) .init_backup_route (66));
Reunanen

Parece llamarse "inicialización encadenada de estilo Smalltalk", al menos aquí: cct.lsu.edu/~rguidry/ecl31docs/api/org/eclipse/ui/internal/…
Reunanen

2

Estoy oxidado con las estructuras, por lo que probablemente me falten algunas palabras clave aquí. Pero, ¿por qué no comenzar con una estructura global con los valores predeterminados inicializados, copiarla en su variable local y luego modificarla?

Un inicializador como:

void init_struct( structType * s )
{
   memcopy(s,&defaultValues,sizeof(structType));
}

Luego, cuando quieras usarlo:

structType foo;
init_struct( &foo ); // get defaults
foo.fieldICareAbout = 1; // modify fields
update( &foo ); // pass to function

2

Podría abordar el problema con una X-Macro

Cambiaría la definición de su estructura en:

#define LIST_OF_foo_MEMBERS \
    X(int,id) \
    X(int,route) \
    X(int,backup_route) \
    X(int,current_route)


#define X(type,name) type name;
struct foo {
    LIST_OF_foo_MEMBERS 
};
#undef X

Y luego podrá definir fácilmente una función flexible que establezca todos los campos en dont_care.

#define X(type,name) in->name = dont_care;    
void setFooToDontCare(struct foo* in) {
    LIST_OF_foo_MEMBERS 
}
#undef X

Siguiendo la discusión aquí , también se podría definir un valor predeterminado de esta manera:

#define X(name) dont_care,
const struct foo foo_DONT_CARE = { LIST_OF_STRUCT_MEMBERS_foo };
#undef X

Que se traduce en:

const struct foo foo_DONT_CARE = {dont_care, dont_care, dont_care, dont_care,};

Y utilícelo como en hlovdal answer , con la ventaja de que aquí el mantenimiento es más fácil, es decir, cambiar el número de miembros de la estructura se actualizará automáticamentefoo_DONT_CARE . Tenga en cuenta que la última coma "falsa" es aceptable .

Aprendí por primera vez el concepto de X-Macros cuando tuve que abordar este problema .

Es extremadamente flexible para agregar nuevos campos a la estructura. Si tiene diferentes tipos de datos, puede definir diferentes dont_carevalores según el tipo de datos: desde aquí , puede inspirarse en la función utilizada para imprimir los valores en el segundo ejemplo.

Si está de acuerdo con una intestructura all , entonces puede omitir el tipo de datos de LIST_OF_foo_MEMBERSy simplemente cambiar la función X de la definición de la estructura a#define X(name) int name;


Esta técnica aprovecha que el preprocesador de C utiliza un enlace tardío y esto puede ser muy útil cuando desea que un solo lugar defina algo (por ejemplo, estados) y luego estar 100% seguro de que en todas partes cuando los elementos usados ​​estén siempre en el mismo orden, los elementos nuevos se agregan automáticamente y se eliminan los elementos eliminados. No me gusta mucho usar nombres cortos como Xy, por lo general, los agrego _ENTRYal nombre de la lista, por ejemplo #define LIST_SOMETHING LIST_SOMETHING_ENTRY(a1, a2, aN) \ LIST_SOMETHING_ENTRY(b1, b2, bN) ....
hlovdal

1

La forma más elegante sería actualizar los campos de estructura directamente, sin tener que usar la update()función, pero tal vez haya buenas razones para usarla que no aparecen en la pregunta.

struct foo* bar = get_foo_ptr();
foo_ref.id = 42;
foo_ref.current_route = new_route;

O puede, como sugirió Pukku, crear funciones de acceso independientes para cada campo de la estructura.

De lo contrario, la mejor solución en la que puedo pensar es tratar un valor de '0' en un campo de estructura como un indicador de 'no actualizar', por lo que solo crea una función para devolver una estructura cero y luego usa esto para actualizar.

struct foo empty_foo(void)
{
    struct foo bar;
    bzero(&bar, sizeof (struct bar));
    return bar;    
}

struct foo bar = empty_foo();
bar.id=42;
bar.current_route = new_route;
update(&bar);

Sin embargo, esto podría no ser muy factible si 0 es un valor válido para los campos en la estructura.


Una red es una buena razón =) Pero si -1 es el valor 'no me importa', entonces simplemente reestructura la función empty_foo () y usa ese enfoque.
gnud

lo siento, hice clic en el voto negativo por error y solo lo noté más tarde. No puedo encontrar ninguna manera de deshacer eso ahora a menos que haya una edición ...
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.