C++ Datenbank: dynamische Templates?



  • Hallo!
    ich will mit C++ eine kleine Datenbank erstellen, die auf binären Bäumen basiert. Jeder Baum ist eine Tabelle, ich habe mir das so vorgestellt:

    BENUTZERKLASSE/INTERFACE                            
                                        |
                                       \ /
                                    HEAD-KNOTEN
                                   /     |     \
                                  /\    / \    /\
                                 NODE   NODE   NODE
                                  ||     ||     ||
                                 NODE   NODE   NODE
                                  ||     ||     ||
                                        usw...
                                  ||     ||     ||
                                 ENDNODE ENODE  ENDNODE
    

    Jeder Linie ist ein Next bzw. Prev-Zeiger. Mann kann im Headnode eine beliebige Anzahl an Verzweigungen wählen, hier sind es z.B. 3, dabei stellt jede Verzweigung eine Tabellenspalte dar, die dann doppelt verkettet bis zum Endnode weitergeführt wird.
    Im HeadKnoten müssen also eine dynamische Anzahl an Zeigern bzw. Spalten gespeichert werden. Fall: Eine Spalte soll "ID" heißen und int-Werte speichern. Die zweite heißt "Name" und soll Strings speichern, die dritte heißt Gehalt und soll long-Werte speichern. So soll dann die Tabelle aussehen:

    =======Gehälter======
    +----+-------+--------+
    | [b][u]ID[/u][/b] | [b][u]Name[/u][/b]  | [b][u]Gehalt[/u][/b] |
    +----+-------+--------+
    | 0  | Hans  | 2500   | = NODE-Ebene 1
    +----+-------+--------+
    | 1  | Peter | 3900   | = NODE-Ebene 2
    +----+-------+--------+
    |... | ...   |  ...   | = NODE-Ebenen
    +----+-------+--------+
    | N  | Max   |   9999 | = NODE-Ebene N
    +----+-------+--------+
    

    Um die Zeiger auf die Spalten speichern zu können, würde ich eine std::map<std::string, Node<T>> verwenden. Der std::string ist jeweils der Spaltenname, Node<T> der eigentliche Zeiger auf den ersten Node in Ebene 1 von dieser Spalte mit dem Namen von std::string, T ist der gewählte Datentyp für diese Spalte, z.B. int für "ID". Node<T> muss dafür ein Template sein, logisch, je nach Datentyp/Objekt. Nur, wie speichere ich diese map als Attribut von HeadNode? Bei der Deklaration von der map ist ja noch nicht bekannt, welche Daten, besser, welches Template-Argument das Node bekommt.
    In Code so:

    tempalte <typename T> // Template für abstrakte Basisklasse
    class HeadNode : public INode<T> { // INode ist nur eine abstrakte Klasse
    private:
       std::map<std::string, Node<T>*> spalten; 
       /* T als Templateargument von Node? Die Klasse wird doch nur einmal
          instanziiert, dann hätten ja alle Spalten den Datentyp T! Wie kann
          ich das Template-Argument von Node dynamisch halten?
       */
    public:
       template <typename N> // Template für Datentyp der Spalte
       void SetColumn(std::string spaltenname) {
          spalten[spaltenname] = new Node<N>; // Neue Spalte mit neuem Node, Typ N als Template-Argument, die der Funktion, nicht der Klasse übergeben wird
       }
    
       // ...
    };
    

    Sorry, sehr komplex, das Problem zusammengefasst:
    Wie kann ich in einer einmalig instantiieren Klasse so eine Art dynamisches Template erhalten, das zur Laufzeit erst bestimmt wird und die demnach je nach mehrmaligem Methodenaufruf der Klasse verschieden sein kann? Ich bräuchte sowas wie:

    template <typename T>
    class HeadNode : public INode<T> {
    private:
       template <typename S>
       std::map<std::string, Node<S>*> spalten; // Typ von Node erst bei Benutzung, nicht bei Klassenaufruf bestimmen
    // ...
    };
    

    Design-Fehler?



  • Ad aCTa schrieb:

    Sorry, sehr komplex, das Problem zusammengefasst:
    Wie kann ich in einer einmalig instantiieren Klasse dynamische Templates erhalten, die je nach mehrmaligem Methodenaufruf der Klasse verschieden sein können? Ich bräuchte sowas wie:

    template <typename T>
    class HeadNode : public INode<T> {
    private:
       template <typename S>
       std::map<std::string, Node<S>*> spalten; // Typ von Node erst bei Benutzung, nicht bei Klassenaufruf bestimmen
    // ...
    };
    

    Design-Fehler?

    Ja. "Dynamische Templates" ist ein Oxymoron.

    Was hat denn Deine Datenstruktor gegenüber folgendem für Vorteile?

    struct GehaelterDatenSatz {
        long id; // <-- wofuer auch immer
        std::string name;
        double gehalt;
      };
    
      int main() {
        std::vector<GehaelterDatenSatz> g;
        // ...
      }
    

    Sprich: Was soll da wo "dynamisch" laufen?

    Du musst ja nicht eine SQL enginge nachbauen. Wenn Du etwas besseres brauchst, kannst Du Dir ja dafür eine gescheite Bibliothek suchen, statt das Rad neu zuerfinden.

    Gruß,
    SP



  • Um den Sinn geht es mir hierbei nicht, ich bin Hobby-Programmierer. 😉

    Ich will keine 250.000-Codezeilen-SQL-Engine nachbauen, nur so etwas in der Art und nicht so ewig groß und komplex. So mal eben ein Struct zu erstellen namens Gehalt geht natürlich nicht, da ich ja das Speicherdesign dem Nutzer überlassen will.
    Id ist übrigens dazu da, um auf die Elemente zugriefen zu können. 😉 Spaltenname + ID = einzelner, eindeutiger Wert!

    > "Dynamische Templates" ist ein Oxymoron.

    Dessen bin ich mir ja bewusst, dass Templates zur Kompilierzeit festgelegt werden. Aber ich brauche irgendetwas, damit ich verschiedene Typen dynamisch in irgendeiner Struktur speichern kann. Da sind sowohl Arrays alsauch Templates mehr als ungeeignet. Was kann man da tun? Ich dachte z.B. an virtuelle Typen, d.h. ich habe eine abstrakte Klasse IType, von ihr erbt z.B. MyInteger, die von jedem C++-Datentyp ein Attribut, gespeichert in einer union, hat. In einem einmaligen Zugriff lege ich den Wert hinein in das Attribut, welches den Datentyp des zu speichernden Wertes hat. Dann kann ich mit dynamic_cast versuchen, das in IType zu konvertieren und in ihm eine Information speichern, welcher Typ das mal war und ich dem Baum abspeichern, beim Abruf von diesem IType konvertiere ich es mithilfe der Information in den alten Typ zurück.
    Der Weg klingt für mich trotzdem nicht sehr schön, ich weiß nicht mal, ob das funktioniert. Gibt es andere Wege?



  • Es gibt andere Sprachen dafuer, C++ ist wahrscheinlich nicht dafuer geeignet.



  • Ach das stimmt doch garnicht.
    MySQL wurde in C programmiert.



  • Ad aCTa, ich weiß zwar nicht, wass Dir dieser Asnatz mit dieser "Dynamik" bringt, aber Du kannst es natürlich auch mit pseudo-dynamischer Typisierung machen, wenn Du unbedingt willst. Das Stichwort dazu heißt "type erasure" und findet u.a. bei boost::any und boost::function Anwendung. Du kannst da ja mal nachgucken, wie die das gemacht haben.

    Gruß,
    SP



  • Ich bin immer offen für andere Ansätze. Wie gesagt, es muss so dynamisch sein, da der Endanwender möglichst eigene Tabellen mit beliebigen Datentypen und Spaltenanzahl kreieren kann.
    Ich habe mir den virtuellen Datentyp-Weg so vorgestellt:

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    class IType { // Speicherklasse
    
        protected:
            union types { // Potentielle Speichertypen
                int tInteger;
                long tLong;
                float tFloat;
                char tChar;
            };
    
        public:
            IType() { }
            virtual const int& GetVal() const; // Polymorphe Methode für die Wertrückgabe
    
    };
    
    class MyInteger : public IType { // Typ für Integer
    
        private:
            types typeTable;
    
        public:
            MyInteger(int zahl) {
                typeTable.tInteger = zahl; // Wert der union zuweisen
            }
    
            MyInteger() { }
    
            const int& GetVal() const {
                return typeTable.tInteger;
            }
    
    };
    
    class MyChar : public IType {   // Typ für Char
    
        private:
            types typeTable;
    
        public:
            MyChar(char zeichen) {
                typeTable.tChar = zeichen; // Wert der union zuweisen
            }
    
            MyChar() { }
    
            const char& GetVal() const {
                return typeTable.tChar;
            }
    
    };
    
    int main() {
    
        vector<IType*> vec; // Speichervektor
    
        MyInteger *inti = new MyInteger(4); // virtueller Int erstellen
        MyChar *chari = new MyChar('a'); // virtueller Char erstellen
    
        vec.push_back(dynamic_cast<IType*>(inti)); // in Speicherklasse casten
        vec.push_back(dynamic_cast<IType*>(chari)); // und in den Vektor füllen
    
        MyInteger *testInt = dynamic_cast<MyInteger*>(vec.front()); // auslesen und zurückcasten
        MyChar *testChar = dynamic_cast<MyChar*>(vec.back());
    
        cout << testInt->GetVal() << endl; // Ausgabe '4'
        cout << testChar->GetVal() << endl; // Ausgabe 'a'
    
        // aufräumen
        for(vector<IType*>::const_iterator iter = vec.begin(); iter != vec.end(); iter++) {
            delete *iter;
        }
        cin.get();
        return 0;
    }
    

    Aber trotzdem finde ich das unsauber, besonders das protected.



  • LOOOL DEN CODE KAPIERT JA NIE EINER!!!!!!



  • Ich stand schon vor einen ziemlich ähnlichen Problem, mit dem dynamischen Detntypen. Wenn du so etwas ähnliches haben willst, wie den letzter Vorschlag kannst du dir mal Boost.Variant anschauen. Damit hab ich es letztlich gelöst, auch wenn es eine agnz andere Vorgehensweise beim programmieren erfordert hat. Damit bist du aber eigentlich recht flexibel aufgestellt was die Datentypen angeht, da es quasi ein union für alles ist und nicht nur PODs.



  • @Ad aCTa:
    Fang mal beim Interface an. Also überleg dir wie das aussehen soll.
    Und zwar nicht wie es mit Features die es garnicht gibt aussehen könnte/sollte, sondern ganz konkret, wie es mit dem heutigen C++ Standard aussehen könnte.

    Also: wie soll der Client z.B. auf die Daten zugreifen?
    Der Return-Typ einer Funktion ist statisch, also kannst du den nicht anhand des Spaltennamens bestimmen.
    Also Funktionen ala Get<int>(row_id, "column_name") ?
    Oder doch eher Get(row_id).Get("column_name").As<int>() ?
    Oder ganz anders?

    Und was soll das ganze Ding unterstützen?

    Und bist du sicher dass du es als Liste von Spalten speichern willst, und nicht als Liste von Rows?



  • > Also Funktionen ala Get<int>(row_id, "column_name")?

    Ja, eher so. Nur, dass ich auf diese Weise keine Templates verwenden kann. 😞

    > Und was soll das ganze Ding unterstützen?

    Ich habe 4 Komponenten geplant: ein File-Manager, der die Daten auf die Festplatte schreibt und sich darum kümmert, dass die Daten in Komponente 2 richtig aufgenommen werden, denn das ist der binäre Baum mit den Iteratoren, Sortier-Algorithmen usw. Komponente 3 ist ein String-Parser, der so ähnlich wie SQL fungiert, nur viel schlichter. Punkt 4 ist eine ganz kleine Socket-Anbindung, damit das Ding im Ethernet funktioniert.
    Datentypen, die speicherbar sind, sind mir erst mal egal, wenn's 3 erst mal sind, ist okay, aber man sollte dann leicht weitere hinzufügen können.

    > Und bist du sicher dass du es als Liste von Spalten speichern willst, und nicht als Liste von Rows?

    Ja, klingt interessant, nur, wie soll ich die Zeigerstruktur da setzten? In meiner "Grafik" einfach vertikal?


Anmelden zum Antworten