2013-07-12 7 views
15

Ich weiß, dass es albern klingt und ich weiß, dass C keine objektorientierte Sprache ist.Dynamisches Methoden-Dispatching in C

Aber gibt es eine Möglichkeit, dass dynamisches Methoden-Dispatching in C erreicht werden kann? Ich dachte über Funktionszeiger nach, aber verstehe nicht die ganze Idee.

Wie könnte ich das umsetzen?

+3

haben kann Sie dies tun, indem Sie die Art, wie es in anderen Sprachen "unter der Haube" getan wird. In C++ zum Beispiel wird der Versand durchgeführt, indem ein Index in eine Tabelle von Funktionszeigern nachgeschlagen wird. Wenn Sie etwas unterklassifizieren, fügen Sie Ihre Träger am Ende der Tabelle hinzu. Sie können eine Struktur von Funktionsträgern verwenden, um die Tabelle zu repräsentieren und einfach die strikte Klasse der Elternklasse am Anfang von Ihnen für die Vererbung einzuschließen. – Will

+1

Lassen Sie uns dies zu einer praktischen Diskussion machen, indem Sie sagen, was Sie in Ihrer Anwendung erreichen möchten. Dann können wir Codebeispiele und praktikable Ideen liefern. Ihre aktuelle Frage ist eher vage und kann in 15 Minuten mit Google beantwortet werden. –

+0

gut jemand würde es vorziehen, wenn sie etwas Code bereits in c geschrieben haben, aber etwas Funktionalität hinzufügen. anstatt von Grund auf mit OOlanguage zu schreiben. – Dineshkumar

Antwort

1

C++ wurde (anfangs) auf C aufgebaut. Der erste C++ - Compiler erzeugte C als Zwischenschritt. Ergo, ja, es ist möglich.

Here's wie C++ Dinge wie das tut.

Es gibt viele solide Informationen online verfügbar, mehr als wir hier in ein paar Minuten zusammen eingeben können. "Google und du wirst finden."

sagte Sie in einem Kommentar über:

Nun, jemand würde, dass es vorziehen, wenn sie etwas Code geschrieben schon in c haben aber einige Funktionen hinzuzufügen. anstatt von Grund auf mit OOlanguage zu schreiben.

Um diese Funktionalität in C zu haben, müssen Sie die OO-Sprachfunktionen grundlegend neu implementieren. Die Leute dazu zu bringen, diese neue OO-Methode zu verwenden, ist der größte Faktor gegen die Benutzerfreundlichkeit. Mit anderen Worten: Indem Sie eine weitere Methode zur Wiederverwendung erstellen, werden Sie die Dinge weniger wiederverwendbar machen.

+4

Zu sagen, es ist möglich, ohne zu erklären, wie eine eher schwache Antwort ist. –

+0

wie man es umsetzt? – Dineshkumar

+1

@ChrisStratton Ich stimme zu, ich werde erweitern. –

3

Ja. Es kann leicht erreicht werden. Sie würden ein Array von Funktionszeigern verwenden und dann diese Funktionszeiger verwenden, um den Aufruf auszuführen. Wenn Sie eine Funktion "überschreiben" möchten, legen Sie einfach den entsprechenden Slot so fest, dass er auf die neue Funktion zeigt. Genau so implementiert C++ virtuelle Funktionen.

+1

Das ist ein guter Anfang - aber Sie müssen auch die Art Ihrer Daten im Auge behalten, damit Sie wissen, welche Version Sie verwenden sollen. –

+1

ein Stück Code würde sehr viel für Anfänger wie mich helfen! – Dineshkumar

+0

Eine ziemlich umfassende Beschreibung der OO-Programmierung in C finden Sie unter http://www.cs.rit.edu/~ats/books/ooc.pdf – cmaster

30

Wie andere bemerkt haben, ist es sicherlich möglich, dies in C zu implementieren. Es ist nicht nur möglich, es ist ein ziemlich verbreiteter Mechanismus. Das am häufigsten verwendete Beispiel ist wahrscheinlich die Dateideskriptorschnittstelle in UNIX. Ein read() Aufruf eines Dateideskriptors wird an eine Lesefunktion gesendet, die für das Gerät oder den Dienst spezifisch ist, der diesen Dateideskriptor bereitgestellt hat (war es eine Datei? War es ein Socket? War es eine andere Art von Gerät?).

Der einzige Trick besteht darin, den Zeiger auf den konkreten Typ vom abstrakten Typ wiederherzustellen. Bei Dateideskriptoren verwendet UNIX eine Nachschlagetabelle, die für diesen Deskriptor spezifische Informationen enthält. Wenn Sie einen Zeiger auf ein Objekt verwenden, ist der vom Benutzer der Schnittstelle gehaltene Zeiger der Typ "base", nicht der Typ "derived". C hat keine Vererbung an sich, aber es garantiert, dass der Zeiger auf das erste Element eines struct gleich dem Zeiger des enthaltenen struct ist. Sie können also den "abgeleiteten" Typ wiederherstellen, indem Sie die Instanz der "Basis" zum ersten Element des "abgeleiteten" Typs machen.

Hier ist ein einfaches Beispiel mit einem Stapel:

struct Stack { 
    const struct StackInterface * const vtable; 
}; 

struct StackInterface { 
    int (*top)(struct Stack *); 
    void (*pop)(struct Stack *); 
    void (*push)(struct Stack *, int); 
    int (*empty)(struct Stack *); 
    int (*full)(struct Stack *); 
    void (*destroy)(struct Stack *); 
}; 

inline int stack_top (struct Stack *s) { return s->vtable->top(s); } 
inline void stack_pop (struct Stack *s) { s->vtable->pop(s); } 
inline void stack_push (struct Stack *s, int x) { s->vtable->push(s, x); } 
inline int stack_empty (struct Stack *s) { return s->vtable->empty(s); } 
inline int stack_full (struct Stack *s) { return s->vtable->full(s); } 
inline void stack_destroy (struct Stack *s) { s->vtable->destroy(s); } 

Nun, wenn ich einen Stapel mit einem festen Größe Array implementieren will ich etwas tun könnte:

struct StackArray { 
    struct Stack base; 
    int idx; 
    int array[STACK_ARRAY_MAX]; 
}; 
static int stack_array_top (struct Stack *s) { /* ... */ } 
static void stack_array_pop (struct Stack *s) { /* ... */ } 
static void stack_array_push (struct Stack *s, int x) { /* ... */ } 
static int stack_array_empty (struct Stack *s) { /* ... */ } 
static int stack_array_full (struct Stack *s) { /* ... */ } 
static void stack_array_destroy (struct Stack *s) { /* ... */ } 
struct Stack * stack_array_create() { 
    static const struct StackInterface vtable = { 
     stack_array_top, stack_array_pop, stack_array_push, 
     stack_array_empty, stack_array_full, stack_array_destroy 
    }; 
    static struct Stack base = { &vtable }; 
    struct StackArray *sa = malloc(sizeof(*sa)); 
    memcpy(&sa->base, &base, sizeof(base)); 
    sa->idx = 0; 
    return &sa->base; 
} 

Und wenn Ich wollte stattdessen einen Stapel mit einer Liste implementieren:

struct StackList { 
    struct Stack base; 
    struct StackNode *head; 
}; 
struct StackNode { 
    struct StackNode *next; 
    int data; 
}; 
static int stack_list_top (struct Stack *s) { /* ... */ } 
static void stack_list_pop (struct Stack *s) { /* ... */ } 
static void stack_list_push (struct Stack *s, int x) { /* ... */ } 
static int stack_list_empty (struct Stack *s) { /* ... */ } 
static int stack_list_full (struct Stack *s) { /* ... */ } 
static void stack_list_destroy (struct Stack *s) { /* ... */ } 
struct Stack * stack_list_create() { 
    static const struct StackInterface vtable = { 
     stack_list_top, stack_list_pop, stack_list_push, 
     stack_list_empty, stack_list_full, stack_list_destroy 
    }; 
    static struct Stack base = { &vtable }; 
    struct StackList *sl = malloc(sizeof(*sl)); 
    memcpy(&sl->base, &base, sizeof(base)); 
    sl->head = 0; 
    return &sl->base; 
} 

Die Implementierungen des Stapels ope Rationen würden einfach das struct Stack * zu dem, was es weiß, es sollte sein. Zum Beispiel:

static int stack_array_empty (struct Stack *s) { 
    struct StackArray *sa = (void *)s; 
    return sa->idx == 0; 
} 

static int stack_list_empty (struct Stack *s) { 
    struct StackList *sl = (void *)s; 
    return sl->head == 0; 
} 

Wenn ein Benutzer von einem Stapel einen Stapelbetrieb auf der Stack-Instanz aufruft, versenden die Operation auf den entsprechenden Vorgang in den vtable. Diese vtable wird von der Erstellungsfunktion mit den Funktionen initialisiert, die ihrer jeweiligen Implementierung entsprechen. Also:

Stack *s1 = stack_array_create(); 
Stack *s2 = stack_list_create(); 

stack_push(s1, 1); 
stack_push(s2, 1); 

stack_push() wird sowohl genannt auf s1 und s2. Aber für s1 wird es an stack_array_push() versandt, während für s2, wird es an stack_list_push() senden.

+2

+1 Große Antwort! –

+1

Außerdem ist 'vtable' Mitglied im Allgemeinen' const' und zeigt auf einen konstanten Speicher (wenn MMU/MPU da ist und OS das richtige tut, natürlich). –

0

Ich bin ein wenig überrascht, dass niemand glib und/oder das ganze gtk-Zeug als Beispiel hinzugefügt hat. Also bitte überprüfen: http://www.gtk.org/features.php

Ich bin mir bewusst, es ist ziemlich einige Boilerplate-Code müssen Sie für die Verwendung von GTK haben und es ist nicht so "einfach", um das erste Mal richtig zu bekommen. Aber wenn man es benutzt hat, ist es bemerkenswert. Das Einzige, an das Sie sich erinnern müssen, ist eine Art "Objekt" als ersten Parameter für Ihre Funktionen. Aber wenn Sie durch die API schauen, sehen Sie < es wird überall benutzt. IMHO, das ist wirklich ein gutes Beispiel für Vorteile und Probleme, die man mit OOP-