2013-10-10 15 views
7

Czy to dobry/poprawny sposób to zrobić tak solidnego programu CDesign for C programu

//File1 => Module1.h 

static int Fun(int); 

struct{ 
int (*pFn)(int) 
}Interface; 

// File2 => Module1.c

static int Fun(int){ 
//do something 
} 

Interface* Init(void) 
{ 
    Interface *pInterface = malloc(sizeof(Interface)); 
    pInterface->pFn = Fun; 
    return pInterface; 
} 

//File 3 => Module2.c 
#include"Module1.h" 
main() 
{ 
    Interface *pInterface1 = Init(); 
    pInterface1->pFn(5); 
} 

Moją intencją jest, aby każdy Moduł narazić interfejs ...

pytania:

  1. Czy dobrze jest napisać kod C jak wyżej, aby odsłonić interfejs ...?
  2. Jakie są lepsze sposoby na ujawnienie interfejsu?
  3. Czy są jakieś odniesienia do zasad projektowania dla programowania C (nie C++)?
+5

Lepiej opublikować jedną [witrynę przeglądu kodu] (http://codereview.stackexchange.com/questions) –

+1

Zapomniałeś 'typedef' dla typu' Interface'. –

+0

Tak, zakładając, że typedef jest ... czy to dobry projekt? – user2706540

Odpowiedz

1

Jest to bardziej idiomatyczne dla modułu ładowanego dynamicznie. (Lub coś w tym stylu.)

Zazwyczaj interfejs między dwoma plikami źródłowymi jest definiowany przy użyciu funkcji i obiektów dostępnych bezpośrednio. Będziesz musiał zająć się robieniem czegoś bardziej wyrafinowanego.

0
  1. Jeśli używamy go do rozwiązania prostego zadania, może to tylko zwiększa kodu bazę i złożoności. Ale niektóre miejsca mogą być przydatne.
    Na przykład: jeśli chcesz zaimplementować STRATEGY-pattern w języku C, to jest jeden sposób, aby to zrobić: .
  2. Możesz ukryć zawartość struktury Interface od użytkowników, używając FIRST-CLASS ADT pattern. Lub możesz łączyć powyższe wzory.
0

Moja krótka odpowiedź brzmi: nie.

Zignorowanie komentarzy wskazujących, że funkcja statyczna nie powinna być zadeklarowana w pliku .h (jest to część, którą chcesz ukryć - mimo wszystko jest statyczna), oto kilka rzeczy, o których należy pamiętać.

Wygląda na to, że próbujesz użyć interfejsu, aby oddzielić implementację od użytkowników modułu; szlachetny cel. Może to pozwolić na większą elastyczność, ponieważ implementacja może się zmienić w pliku .cc bez przerywania wywoływania kodu. Zastanów się jednak nad zdefiniowanym interfejsem.

Każdy kod korzystający z tego modułu powinien mieć gwarancję, że będzie mógł pobrać instancję Interface za pośrednictwem Init (chociaż musi to być zawarte w pliku .h). Ta instancja interfejsu wskaże funkcje do użycia. Jedną z takich funkcji jest podpis

int Fun(int); 

Co ukryłeś? Oczywiście, możesz zmienić funkcję, do której dzwonią, ale nadal musisz podać funkcję z tym podpisem.

Alternatywnie, można po prostu zdefiniować .h jako:

int Fun(int); 

a następnie w.c plik, coś jak:

static int StaticFun(int i) 
{ 
    // something 
} 

int Fun(int i) 
{ 
    StaticFun(i); 
} 

Można wyśmiać wywołać inną funkcję w oparciu o jakiś stan wewnętrzny, którym zarządza, lub plik konfiguracyjny, co chcesz. Dlaczego to jest lepsze? Poza tym, że jest prostsze, jest to połączenie statyczne; kompilator może na przykład wywołać połączenie z StaticFun, a nawet z funkcją StaticFun. Połączenia za pośrednictwem wskaźników funkcyjnych zwykle powodują zauważalny spadek wydajności.

Teraz może istnieć przypadek dla obiektu "Interfejs", takiego jaki zdefiniowałeś. Ale byłbym skłonny zasugerować, że nie ma w twoim przypadku, nie bez modyfikacji.

Załóżmy, że funkcja Init zmienione na:

Interface* Init(some state info); 

Teraz obiekt Interfejs można przejść z powrotem może się zmienić w zależności od stanu lub konfiguracji przeszedł w, co pozwala Twój moduł dynamicznie map funkcje. Zasadniczo, moja opinia jest taka, że ​​jeśli faktycznie wywołujesz funkcję statyczną pod spodem, wtedy pracujesz znacznie ciężej niż potrzeba i zapobiegasz optymalizacji kompilatora.

Na marginesie, jeśli zamierzasz przejść do interfejsu (pracuję teraz nad projektem z czymś podobnym), czy mogę zaproponować modyfikację? Coś jak ...

.h plików: plik

const Interface* Init(some state); 

.c:

static int FunTyp1(int); 
static int FunTyp2(int); 


static const Interface typ1 = { &FunTyp1 }; 
static const Interface typ2 = { &FunTyp2 }; 

const Interface* Init(some state) 
{ 
    if (some state == type1) 
     return &typ1; 
    else if (some state == type2) 
     return &typ2; 
} 

Zalety:

  1. Prawdopodobnie nie chcą spożywania kod, aby zmodyfikować berło? Przynajmniej nie mogę wymyślić dobrego powodu.
  2. Definiowanie tego statycznie pozwala uniknąć niepotrzebnego przydzielania sterty i wycieku pamięci, gdy konsumenci wielokrotnie żądają obiektów interfejsu i zapominają je zwolnić (lub nie wiedzą, że powinni - kto jest właścicielem tego obiektu?).
  3. Możesz udostępnić pojedynczą (statyczną) instancję obiektu interfejsu dla całego kodu używanego w procesie, nie martwiąc się, że ktoś to zmieni.

Należy pamiętać, że dzięki kilku instancjom inicjowanym statycznie można zdefiniować inny obiekt "Interfejs" dla każdego mapowania funkcji, które mają być obsługiwane.