Mam taką strukturę danych:
struct foo { int id; int route; int backup_route; int current_route; }
oraz funkcję o nazwie update (), która służy do żądania zmian w nim.
update (42, dont_care, dont_care, new_route);
to jest naprawdę długie i jeśli dodam coś do struktury, muszę dodać 'dont_care' do KAŻDEGO wywołania aktualizacji (...).
Zastanawiam się nad przekazaniem mu struktury zamiast tego, ale wcześniejsze wypełnienie struktury za pomocą `` dont_care '' jest jeszcze bardziej żmudne niż zwykłe przeliterowanie jej w wywołaniu funkcji. Czy mogę gdzieś stworzyć strukturę z domyślnymi wartościami „nieważne” i ustawić tylko pola, na których mi zależy, po zadeklarowaniu jej jako zmiennej lokalnej?
struct foo bar = {.id = 42, .current_route = new_route}; aktualizacja (& bar);
Jaki jest najbardziej elegancki sposób przekazania tylko informacji, które chcę wyrazić, do funkcji aktualizacji?
i chcę, aby wszystko inne było domyślnie ustawione na -1 (tajny kod dla „nie obchodzi mnie”)
źródło
PTHREAD_MUTEX_INITIALIZER
również tego używa.Możesz zmienić swoją tajną wartość specjalną na 0 i wykorzystać domyślną semantykę elementu struktury w C.
struct foo bar = { .id = 42, .current_route = new_route }; update(&bar);
przekaże wtedy 0 jako elementy składowe bar nieokreślone w inicjatorze.
Lub możesz utworzyć makro, które wykona domyślną inicjalizację za Ciebie:
#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);
źródło
<stdarg.h>
pozwala na zdefiniowanie funkcji wariadycznych (które akceptują nieskończoną liczbę argumentów, npprintf()
.). Zdefiniowałbym funkcję, która pobierałaby dowolną liczbę par argumentów, jedną, która określa właściwość do zaktualizowania, a drugą określa wartość. Użyjenum
ciągu znaków lub, aby określić nazwę właściwości.źródło
enum
zmienne nastruct
pole? Zakodować to na stałe? Wtedy ta funkcja będzie miała zastosowanie tylko do konkretnychstruct
.Być może zamiast tego rozważ użycie definicji makra preprocesora:
#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); })
Jeśli twoja instancja (struct foo) jest globalna, to oczywiście nie potrzebujesz do tego parametru. Ale zakładam, że prawdopodobnie masz więcej niż jedną instancję. Używanie bloku ({...}) jest GNU-izmem, który ma zastosowanie do GCC; jest to miły (bezpieczny) sposób na trzymanie linii razem jako blok. Jeśli później będziesz musiał dodać więcej do makr, na przykład sprawdzanie poprawności zakresu, nie będziesz musiał martwić się o zepsucie takich rzeczy, jak instrukcje if / else i tak dalej.
To właśnie bym zrobił w oparciu o wskazane przez Ciebie wymagania. Takie sytuacje są jednym z powodów, dla których zacząłem często używać Pythona; obsługa parametrów domyślnych i taka staje się dużo prostsza niż kiedykolwiek z C. (myślę, że to wtyczka Pythona, przepraszam ;-)
źródło
Jednym wzorcem używanym przez gobject jest funkcja wariadyczna i wyliczone wartości dla każdej właściwości. Interfejs wygląda mniej więcej tak:
update (ID, 1, BACKUP_ROUTE, 4, -1); /* -1 terminates the parameter list */
Pisanie funkcji varargs jest łatwe - patrz http://www.eskimo.com/~scs/cclass/int/sx11b.html . Po prostu dopasuj pary klucz -> wartość i ustaw odpowiednie atrybuty struktury.
źródło
Ponieważ wygląda na to, że potrzebujesz tej struktury tylko dla
update()
funkcji, w ogóle nie używaj struktury do tego, tylko zaciemni to twoją intencję stojącą za tą konstrukcją. Może powinieneś przemyśleć, dlaczego zmieniasz i aktualizujesz te pola i zdefiniować osobne funkcje lub makra dla tych „małych” zmian.na przykład
#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)
Lub jeszcze lepiej napisz funkcję dla każdego przypadku zmiany. Jak już zauważyłeś, nie zmieniasz wszystkich właściwości w tym samym czasie, więc możesz zmieniać tylko jedną właściwość na raz. To nie tylko poprawia czytelność, ale także pomaga w obsłudze różnych przypadków, np. Nie musisz sprawdzać wszystkich „dont_care”, ponieważ wiesz, że zmieniana jest tylko bieżąca trasa.
źródło
A może coś takiego:
struct foo bar; update(init_id(42, init_dont_care(&bar)));
z:
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; }
i:
struct foo* init_id(int id, struct foo* bar) { bar->id = id; return bar; }
i odpowiednio:
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);
W C ++ podobny wzorzec ma nazwę, której nie pamiętam.
EDYCJA : Nazywa się idiomem nazwanego parametru .
źródło
Jestem zardzewiały ze strukturami, więc prawdopodobnie brakuje mi tutaj kilku słów kluczowych. Ale dlaczego nie zacząć od struktury globalnej z zainicjalizowanymi wartościami domyślnymi, skopiować ją do zmiennej lokalnej, a następnie zmodyfikować?
Inicjator taki jak:
void init_struct( structType * s ) { memcopy(s,&defaultValues,sizeof(structType)); }
Następnie, gdy chcesz go użyć:
structType foo; init_struct( &foo ); // get defaults foo.fieldICareAbout = 1; // modify fields update( &foo ); // pass to function
źródło
Możesz rozwiązać problem za pomocą X-Macro
Zmieniłbyś swoją definicję struktury na:
#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
Wtedy możesz łatwo zdefiniować elastyczną funkcję, która ustawia wszystkie pola na
dont_care
.#define X(type,name) in->name = dont_care; void setFooToDontCare(struct foo* in) { LIST_OF_foo_MEMBERS } #undef X
Po dyskusji w tym miejscu można również zdefiniować wartość domyślną w następujący sposób:
#define X(name) dont_care, const struct foo foo_DONT_CARE = { LIST_OF_STRUCT_MEMBERS_foo }; #undef X
Co przekłada się na:
const struct foo foo_DONT_CARE = {dont_care, dont_care, dont_care, dont_care,};
I użyj go tak, jak w odpowiedzi hlovdal , z tą zaletą, że tutaj konserwacja jest łatwiejsza, tj. Zmiana liczby elementów struktury zostanie automatycznie zaktualizowana
foo_DONT_CARE
. Zauważ, że ostatni „fałszywy” przecinek jest dopuszczalny .Po raz pierwszy poznałem koncepcję X-Macros, kiedy musiałem rozwiązać ten problem .
Jest niezwykle elastyczny w przypadku dodawania nowych pól do struktury. Jeśli masz różne typy danych, możesz zdefiniować różne
dont_care
wartości w zależności od typu danych: stąd możesz czerpać inspirację z funkcji używanej do drukowania wartości z drugiego przykładu.Jeśli nie
int
masz nic przeciwko strukturze all , możesz pominąć typ danych zLIST_OF_foo_MEMBERS
i po prostu zmienić funkcję X definicji struktury na#define X(name) int name;
źródło
X
i zazwyczaj dołączam_ENTRY
do nazwy listy, np#define LIST_SOMETHING LIST_SOMETHING_ENTRY(a1, a2, aN) \ LIST_SOMETHING_ENTRY(b1, b2, bN) ...
.Najbardziej eleganckim sposobem byłaby bezpośrednia aktualizacja pól struktury, bez konieczności korzystania z
update()
funkcji - ale być może istnieją dobre powody, dla których warto jej użyć, a które nie pojawiają się w pytaniu.struct foo* bar = get_foo_ptr(); foo_ref.id = 42; foo_ref.current_route = new_route;
Możesz też, jak sugerował Pukku, utworzyć osobne funkcje dostępu dla każdego pola struktury.
W przeciwnym razie najlepszym rozwiązaniem, jakie przychodzi mi do głowy, jest traktowanie wartości „0” w polu struktury jako flagi „nie aktualizuj” - więc po prostu tworzysz funkcję zwracającą zerową strukturę, a następnie używasz jej do aktualizacji.
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);
Jednak może to nie być wykonalne, jeśli 0 jest prawidłową wartością dla pól w strukturze.
źródło