Daten- / Funktionskappselung



  • Hallo,

    für ein kleines Projekt habe ich mir eine eigene Listenbibliothek geschrieben, welche sämtliche Funktionen zur Erzeugung einer neuen Liste, Hinzufügen von Elementen, Entfernen dieser etc. enthält. Kurz gesagt, lassen sich damit sowohl Stacks als auch LIFOs realisieren. Folgendes Codebeispiel soll dies veranschaulichen:

    typedef struct _meta_list _ML
    typedef _ML *ML
    
    struct _meta_list {
    ML first;
    ML last;
    size_t count;
    }
    
    ML init();
    void addfirst(ML, void *);
    void addlast(ML, void *);
    void *top(ML)
    etc.
    

    Unschön an dieser Art ist allerdings, dass sollte ich damit einen Stack realisieren, ich auch am anderen Ende des Stacks theoretisch Elemente anfügen könnte, analog dazu der LIFO. Um dies zu vermeiden hatte ich mir folgendes überlegt:

    typedef struct _meta_list _ML
    typedef _ML *ML
    typedef _ML *STACK
    typedef _ML *LIFO
    
    struct _meta_list {
    ML first;
    ML last;
    size_t count;
    }
    
    ML mlinit();
    void mladdfirst(ML, void *);
    void mladdlast(ML, void *);
    void *mltop(ML)
    
    STACK init(); /* ruft ML init() auf */
    void stackadd(STACK, void *); /* ruf void mladdfirst(ML, void *)  auf*/
    void *stacktop(STACK) /* ruft void *mltop(ML) auf */
    
    // analog dazu dann auch LIFO Funktionen
    

    Nun zu meiner Frage:
    Kann man das so machen oder gibt es einen eleganteren Weg, welcher ANSI-Konform ist? Bei meiner Lösung war mir die ANSI-Konformität wichtig.


  • Mod

    Komposition: Du hast deine Listenstruktur im Hintergrund, die du sowohl für FIFO- als auch für LIFO-Strukturen benutzen kannst. Dann programmierst du einmal eine FIFO-Klasse (also ein Datenstruct und dazugehörige Funktionen) und eine LIFO-Klasse, die intern beide eine Instanz deiner Liste benutzen, aber jeweils nur Zugriff über eine LIFO- bzw. FIFO-Schnittstelle erlauben.

    Das kann sogar so weit gehen, dass du auch die intern benutzte Liste abstrahierst, so dass der Benutzer der Queues/Stacks selber festlegen können, was für eine Datenstruktur intern benutzt wird, solange diese nur die nötigen Basisoperationen bereit stellen kann.



  • Also muss ich mein bisheriges Konstrukt um structs für den LIFO und den Stack erweitern, der typedef für den Strunkt und den Lifo aus dem meta_list Element fällt weg, wird dann aber intern benutzt?


  • Mod

    Nirvash schrieb:

    Also muss ich mein bisheriges Konstrukt um structs für den LIFO und den Stack erweitern, der typedef für den Strunkt und den Lifo aus dem meta_list Element fällt weg, wird dann aber intern benutzt?

    Ja. So kann der Compiler die richtige Benutzung der Typen überprüfen, da er dann Stack und Queue unterscheiden kann.



  • SeppJ schrieb:

    Ja. So kann der Compiler die richtige Benutzung der Typen überprüfen, da er dann Stack und Queue unterscheiden kann.

    Dann muss ich ja sicherlich beim Aufrufen der internen Meta-Listen-Funtktionen diese entsprechend casten. Also:

    STACK stinit() {
    return (STACK) meta_list_init();
    }
    

    Um dieser hin und her Casterei zu entgehen hab ich mich für den obigen Ansatz entschieden. Denke aber mal auch nach deiner Erläuterung der besseren Unterscheidung wegen, ist dies besser.


  • Mod

    Nirvash schrieb:

    Dann muss ich ja sicherlich beim Aufrufen der internen Meta-Listen-Funtktionen diese entsprechend casten. Also:

    STACK stinit() {
    return (STACK) meta_list_init();
    }
    

    Wo solltest du so etwas benötigen?



  • Naja die init Funktion der Meta-Liste liefert ein Pointer auf eine Meta-Liste zurück, rufe ich dieses aus der Stack Initialisierungs Funktion auf, welche ja einen Pointer auf einen Stack zurückliefern soll, so muss der Rückgabewert von Meta-Listen-Pointer nach Stack-Pointer umgewandelt werden?
    Oder fällt das Weg, wenn die definierten Structs für Meta-Liste und Stack genau gleich aussehen, nur einen anderen Namen haben?


  • Mod

    Ich hätte eher an so etwas gedacht:

    typedef struct
    {
      ML ml;
    } Stack;
    
    Stack init_stack()
    {
      Stack stack;
      stack.ml = init_ml();
      return stack;
    }
    


  • SeppJ schrieb:

    Ich hätte eher an so etwas gedacht:

    typedef struct
    {
      ML ml;
    } Stack;
    
    Stack init_stack()
    {
      Stack stack;
      stack.ml = init_ml();
      return stack;
    }
    

    Ok, das klingt plausbilder und handhabbarer als was ich versucht hab zurecht zu basteln. Ich nehme an, damit kann ich auch mehrere Stacks parallel erstellen, da für jeden Funktionsaufruf eine neue Instanz generiert wird?

    Dieser Umstand hat mich bisher immer ein wenig irritiert, wenn ich eigene Bibliotheken schreibe: Wie bekomme ich es hin, von dem Datenobjekt nicht nur eines, sondern mehrere Erzeugen zu können, auf die ich auch getrennt voneinander zugreifen kann. Mit dieser von dir beschriebenen Methode ist es super einfach und ich komm mir selbst bisschen dämlich vor, nicht selbst drauf gekommen zu sein 😃
    Vielen Dank!


  • Mod

    Nirvash schrieb:

    Ok, das klingt plausbilder und handhabbarer als was ich versucht hab zurecht zu basteln. Ich nehme an, damit kann ich auch mehrere Stacks parallel erstellen, da für jeden Funktionsaufruf eine neue Instanz generiert wird?

    Ja. War das bei deiner Originalliste denn nicht so?



  • SeppJ schrieb:

    Ja. War das bei deiner Originalliste denn nicht so?

    Es war so, nur umständlicher gestaltet. Mir war

    typedef struct
    {
      ML ml;
    } Stack;
    

    noch nicht so gut bekannt, da ich bisher immer mit:

    typedef struct _stack Stack
    
    struct _stack
    {
      ML ml;
    };
    

    gearbeitet habe und dann für ein neues Element dieses Structs immer mit malloc den jeweiligen Speicher reserviert. Bei dir viel das jetzt weg für ein neues Stack-Element, daher gehe ich davon aus, der Speicher wird dann automatisch durch den speziellen typedef struct {}; <Name> allokiert?


  • Mod

    😕
    Ich habe das Gefühl, du redest von Syntax, obwohl du auch von Speicher und malloc redest.

    typedef struct
    {
      ML ml;
    } Stack;
    

    Macht bezüglich des Programmablaufs gar nichts. Das holt keinen Speicher, führt nichts aus, ist keine Variable. Das macht dem Compiler einfach nur ein struct mit der Form

    struct
    {
      ML ml;
    };
    

    unter dem Namen "Stack" bekannt.



  • Oh ok, dann hab ich mich selbst etwas verwirrt 😃

    In deiner init_stack Funktion wird der Stack ja nicht als Pointer-Referenz, sondern als normales Objekt erstellt. Da hab ich nicht ganz aufgepasst.



  • SeppJ schrieb:

    Ich hätte eher an so etwas gedacht:

    ....

    Stack init_stack()
    {
    Stack stack;
    stack.ml = init_ml();
    return stack;
    }[/code]

    Ist die Variable stack nicht hier nur lokal?
    Wieso ist sie nach dem Verlassen der init_stack noch gültig?


  • Mod

    HugoK schrieb:

    Ist die Variable stack nicht hier nur lokal?

    Ja.

    Wieso ist sie nach dem Verlassen der init_stack noch gültig?

    Ist sie auch nicht. Aber ihre Kopie ist gültig.

    Das ist wie bei ganz einfachen Funktionen, lass dich nicht von den verrückten Namen der Datentypen verwirren:

    int sum(int a, int b)
    {
      int result = a + b;
    
      return result;
    }
    

    Da wunderst du dich sicher auch nicht, ob das Ergebnis noch gültig ist, oder?


Anmelden zum Antworten