Wartości domyślne w strukturze C

93

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”)

Arthur Ulfeldt
źródło

Odpowiedzi:

156

Chociaż makra i / lub funkcje (jak już sugerowano) będą działać (i mogą mieć inne pozytywne efekty (np. Punkty zaczepienia do debugowania)), są one bardziej złożone niż potrzeba. Najprostszym i możliwie najbardziej eleganckim rozwiązaniem jest po prostu zdefiniowanie stałej, której używasz do inicjalizacji zmiennej:

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);

Ten kod nie ma praktycznie żadnego umysłowego narzutu zrozumienia pośredniego kierunku i jest bardzo jasne, które pola w bartobie ustawiają jawnie, jednocześnie (bezpiecznie) ignorując te, których nie ustawiłeś.

hlovdal
źródło
6
Kolejną zaletą tego podejścia jest to, że nie polega ono na działaniu funkcji C99.
D.Shawley
24
Kiedy przesiadłem się na te 500 linijek "wypadło" z projektu. Chciałbym móc dwukrotnie głosować w górę w tej sprawie!
Arthur Ulfeldt
4
PTHREAD_MUTEX_INITIALIZERrównież tego używa.
wysłano
Dodałem odpowiedź poniżej, opierając się na X-Macros, aby być elastycznym w stosunku do liczby elementów obecnych w strukturze.
Antonio
15

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);
jpalecek
źródło
Czy FOO_INIT działa? Myślę, że kompilator powinien narzekać, jeśli dwukrotnie zainicjujesz tego samego członka.
Jonathan Leffler
1
Wypróbowałem to z gcc i nie narzekałem. Ponadto nie znalazłem nic przeciwko temu w standardzie, w rzeczywistości jest jeden przykład, w którym nadpisywanie jest wyraźnie wymienione.
jpalecek
2
C99: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf : 6.7.8.19: Inicjalizacja powinna odbywać się w kolejności listy inicjalizatorów, każdy inicjalizator przewidziany dla określonego podobiektu przesłania każdy poprzednio wymieniony inicjator dla tego samego podobiekt;
altendky
2
Jeśli to prawda, czy nie mógłbyś zdefiniować DEFAULT_FOO, który ma wszystkie inicjatory, a następnie zrobić struct foo bar = {DEFAULT_FOO, .id = 10}; ?
John
7

<stdarg.h> pozwala na zdefiniowanie funkcji wariadycznych (które akceptują nieskończoną liczbę argumentów, np printf() .). 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żyj enumciągu znaków lub, aby określić nazwę właściwości.

Matt J
źródło
Cześć @Matt, jak mapować enumzmienne na structpole? Zakodować to na stałe? Wtedy ta funkcja będzie miała zastosowanie tylko do konkretnych struct.
misssprite
5

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 ;-)

digijock
źródło
4

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.

John Millikin
źródło
4

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.

quinmary
źródło
w niektórych przypadkach 3 z nich zmienią się w tym samym wezwaniu.
Arthur Ulfeldt
Możesz mieć taką sekwencję: set_current_rout (id, route); set_route (id, trasa); Apply_change (id); lub podobne. Myślę, że możesz mieć jedno wywołanie funkcji dla każdego ustawiacza. I wykonaj prawdziwe obliczenia (lub cokolwiek) w późniejszym czasie.
quinmars
4

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 .

Reunanen
źródło
Myślę, że nazywa się konstruktorem.
Nieznane
Nie, chodziło mi o to, co możesz zrobić: update (bar.init_id (42) .init_route (5) .init_backup_route (66));
Reunanen
Wydaje się, że nazywa się to „łańcuchową inicjalizacją w stylu Smalltalk”, przynajmniej tutaj: cct.lsu.edu/~rguidry/ecl31docs/api/org/eclipse/ui/internal/…
Reunanen
2

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
Steven Fisher
źródło
2

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 zaktualizowanafoo_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_carewartoś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 intmasz nic przeciwko strukturze all , możesz pominąć typ danych z LIST_OF_foo_MEMBERSi po prostu zmienić funkcję X definicji struktury na#define X(name) int name;

Antonio
źródło
Ta technika wykorzystuje, że preprocesor C używa późnego wiązania, co może być bardzo przydatne, gdy chcesz, aby jedno miejsce definiowało coś (na przykład stany), a następnie mieć 100% pewności, że wszędzie, gdy używane elementy są zawsze w tej samej kolejności, nowe elementy są automatycznie dodawane, a usunięte elementy usuwane. Nie przepadam za używaniem krótkich nazw, takich jak Xi zazwyczaj dołączam _ENTRYdo nazwy listy, np #define LIST_SOMETHING LIST_SOMETHING_ENTRY(a1, a2, aN) \ LIST_SOMETHING_ENTRY(b1, b2, bN) ....
hlovdal
1

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.

gnud
źródło
Sieć to dobry powód =) Ale jeśli -1 jest wartością 'dont care', to po prostu przywróć funkcję empty_foo () i zastosuj to podejście?
gnud
przepraszam, przez pomyłkę kliknąłem „przeciw” i zauważyłem dopiero później !!! Nie mogę znaleźć żadnego sposobu, aby to teraz cofnąć, chyba że istnieje edycja ...
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功