Schickes Interface für Strukturierte Daten gesucht



  • Hi zusammen

    ich baue aktuell ein Modul zum Schreiben von strukturierten Daten und bin unentschlossen, was das Interface angeht.
    Das interface soll möglichst intuitiv sein und die strukturierung von Daten ermöglichen, im Stile von XML z.b.

    Variante 1 StreamOperatoren

    DatenObjekt obj;
    obj << StartGruppe(1) << Element1 << Element2 << Element3 << EndeGruppe() << Element5 << Element6;
    

    Variante 2 Klassisch

    DatenObjekt obj;
    obj.startGruppe( 1 );
    obj.write( Element1 );
    obj.write( Element2 );
    obj.write( Element3 );
    obj.endeGruppe();
    obj.write( Element5 );
    obj.write( Element6 );
    

    Variante 3 Parameter Pack

    DatenObjekt obj;
    obj.writeGruppe( 1, Element1, Element2, Element3 );
    obj.writeElement( Element5, Element6 );
    

    In meinem Fall sind es dann allerdings keine einzelnen Elemente sondern Key-Value-Paare. Das soll aber zunächst nicht von Bedeutung sein.

    Bevorzugt ihr eine der Varianten, oder habt ihr da noch andere Ideen, wie man solch ein Interface umsetzen kann?



  • Kann Boost.PropertyTree was Du brauchst?



  • Was spricht gegen

    Datengruppe g;
    DatenObjekt o;
    
    g << element1 << element2 << element3 << element4
    o << g;
    


  • @DocShoe sagte in Schickes Interface für Strukturierte Daten gesucht:

    Was spricht gegen

    Datengruppe g;
    DatenObjekt o;
    
    g << element1 << element2 << element3 << element4
    o << g;
    

    Nix 🙂 Sowas ähnliches kam mir auch schon in den Sinn.

    Wobei das analog auch mit ParameterPacks geht, wenn man sich für die DatenGruppe eine create-Funktion baut.

    DatenGruppe g = gruppe( 1, Element1, Element2, Element3 );
    DatenObjekt o;
    o.write( g, Element5, Element6 );
    

    so in der Art jedenfalls.

    Mir ist halt wichtig, dass es einen halbwegs modernen Eindruck hinterlässt. Vermutlich werde ich die klassische Variante, wie auch die meisten XML-Schreiber arbeiten, aus kompatibilitäts-Gründen ohnehin anbieten müssen, aber ich will eben auch eine etwas elegantere Variante zusätzlich anbieten, die vielleicht auch weniger Schreibaufwand ist.



  • @Swordfish sagte in Schickes Interface für Strukturierte Daten gesucht:

    Boost.PropertyTree

    Danke für den Hinweis. Muss ich mir erstmal anschauen. Sagt mir bisher gar nix 😉



  • Sowas ist hier wäre definitiv machbar (finde ich ne ganz nette Syntax):

    auto d = dokument{
        gruppe{
            element{ "element1" },
            element{ "element2" },
            element{ "element3" }
        },
        gruppe{
            gruppe{
                element{ "element4" },
                element{ "element5" },
                element{ "element6" }
            }
        }
    };
    
    d.append(
        gruppe{
            element{ "element7" }
        },
    );
    
    std::cout << d.child(1) << "\n";
    
    <gruppe>
        <gruppe>
            <element>element4</element>
            <element>element5</element>
            <element>element6</element>
        </gruppe>
    </gruppe>
    
    std::cout << d.child(1).child(0).child(2).value() << "\n";
    
    element6
    

    Ich habe zwar jetzt nicht die Muße, das in Code auszuformulieren, aber ich sehe nicht, warum sowas nicht ginge.



  • @Finnegan
    Meinst du die geschweiften Klammern in Bezug auf intialisierungslisten?

    Und das müsste auf die Art eigentlich sogar halbwegs performant sein, wenn die Konstruktoren mit { .... } komplett zur Compilezeit aufgelöst werden. Oder?

    (unabhängig mal von der Art der Speicherung im Hintergrund )



  • @It0101 sagte in Schickes Interface für Strukturierte Daten gesucht:

    Und das müsste auf die Art eigentlich sogar halbwegs performant sein, wenn die Konstruktoren mit { .... } komplett zur Compilezeit aufgelöst werden. Oder?

    initializer_lists und performance passen nicht unbedingt zusammen: The Cost of std::initializer_list ... aber Du meinst mit variadic templates?

    Ich kann mir aber nicht vorstellen daß das bei solch einer Datenstruktur von Bedeutung ist.



  • @It0101 @Swordfish Das geht auch ohne std::initializer_list, ich hab hier mal schnell was zusammengestoppelt, das die Idee hoffentlich etwas illustriert (nicht perfekt, nur ein erster grober Entwurf):

    Ideone-Demo: https://ideone.com/AnjdAc

    #include <iostream>
    #include <utility>
    #include <memory>
    #include <vector>
    #include <stdexcept>
    
    class element
    {
        public:
            element() = default;
            virtual ~element() = default;
    
            element(std::string value)
            : value_{ std::move(value) }
            {
            }
    
            auto value() const -> const std::string&
            {
                return value_;
            }
    
            virtual const element& child(std::size_t i) const
            {
                throw std::runtime_error("Invalid child index.");
            }
       
            virtual void print(std::ostream& out, int level) const
            {
                out << std::string(level * 2, ' ')
                    << "<element>" << value() << "</element>";
            }
        
        private:
            std::string value_;
    };
    
    auto operator<<(std::ostream& out, const element& e) -> std::ostream&
    {
        e.print(out, 0);
        return out;
    }
    
    class group : public element
    {
        public:
            template <typename... Children>
            group(Children&&... children)
            {
                append(std::forward<Children>(children)...);
            }
            
            template <typename... Children>
            void append(Children&&... children)
            {
                (
                    children_.emplace_back(
                        std::make_unique<Children>(std::forward<Children>(children))
                    ), 
                    ...
                );
            }        
    
            virtual const element& child(std::size_t i) const override
            {
                if (i >= children_.size())
                    throw std::runtime_error("Invalid child index.");
                return *children_[i];
            }
    
            virtual void print(std::ostream& out, int level) const override
            {
                out << std::string(level * 2, ' ') << "<group>\n";
                print_children(out, level + 1);
                out << std::string(level * 2, ' ') << "</group>";
            }
    
            void print_children(std::ostream& out, int level) const
            {
                for (const auto& child : children_)
                {
                    child->print(out, level);
                    out << "\n";
                }            
            }
    
        private:
            std::vector<std::unique_ptr<element>> children_;
    };
    
    class document : public group
    {
        public:
            template <typename... Children>
            document(Children&&... children)
            : group{ std::forward<Children>(children)... }
            {
            }
    
        protected:
            virtual void print(std::ostream& out, int level) const override
            {
                out << std::string(level * 2, ' ') << "<document>\n";
                print_children(out, level + 1);
                out << std::string(level * 2, ' ') << "</document>";
            }        
    };
    
    auto main() -> int
    {
        auto d = document{
            group{
                element{ "element1" },
                element{ "element2" },
                element{ "element3" }
            },
            group{
                group{
                    element{ "element4" },
                    element{ "element5" },
                    element{ "element6" }
                },
                element{ "element7" }
            }
        };
        
        d.append(
            group{
                element{ "element8" }
            }
        );
        
        std::cout << "\nd --------------------------------------\n\n" << d << "\n";
        std::cout << "\nd.child(1) -----------------------------\n\n"
            << d.child(1) << "\n";
        std::cout << "\nd.child(1).child(0).child(2).value() ---\n\n" 
            << d.child(1).child(0).child(2).value() << "\n\n";
    }
    

    Das ist noch ausbaufähig und hat auch noch ein paar Probleme. Z.B. wird bei einem Element, das ein einziges Element seines eigenen Typs enthalten soll der Copy-Konstruktor aufgerufen: group { group { ... } } kopiert die innere Gruppe in die äußere. Im Kopier-Kontext ist das ja durchaus korrekt, in diesem Fall jedoch nicht. Interessantes Problem, für das ich auf die Schnelle noch keine elegante Lösung habe.

    Auch habe ich die child()-Methode bereits im element-Basisobjekt deklariert - auch wenn nur group-Objekte Kinder haben können, und auch group und document erben die value()-Methode. Alles um dieses d.child(1).child(0).child(2).value() zu ermöglichen. Vielleicht will man das etwas anders designen - oder vielleicht ist es ja auch nicht so schlecht - wie gesagt, nur ein erster Entwurf 😉

    P.S.: Noch ne Idee für später: Wenn die Anzahl der Element-Typen überschaubar bleibt, dann könnte man die vielleicht auch in einem std::variant speichern, statt mit std::unique_ptr zu arbeiten. Das würde die Datenstruktur im Speicher noch etwas kompakter und eventuell auch etwas effizienter machen (std::vector und std::string haben dann zwar immer noch dynamischen Speicher, aber man reduziert es ein wenig). Ich entwerfe Datenstrukturen gerne so, dass sie nicht übermäßig "all over the place" im Speicher liegen.



  • @Finnegan sagte in Schickes Interface für Strukturierte Daten gesucht:

    das die Idee hoffentlich etwas illustriert

    @Swordfish sagte in Schickes Interface für Strukturierte Daten gesucht:

    aber Du meinst mit variadic templates?



  • @Swordfish sagte in Schickes Interface für Strukturierte Daten gesucht:

    @Swordfish sagte in Schickes Interface für Strukturierte Daten gesucht:

    aber Du meinst mit variadic templates?

    Ich weiss nicht ob ich dich richtig verstehe. Willst du eher auf etwas ohne virtuelle Vererbung hinaus, wo die Daten z.B. in einer geschachtelten std::tuple-Struktur oder etwas ähnlichem gespeichert sind? ich die Richtung geht bestimmt auch was, das am Ende ähnlich zu benutzen ist, das wird aber definitiv pfriemeliger - nix für "mal eben zusammenstoppeln" 😉



  • @Finnegan sagte in Schickes Interface für Strukturierte Daten gesucht:

    Willst du eher auf etwas ohne virtuelle Vererbung hinaus

    Nene, sowas in der Art hab' ich mit variadic template schon gemeint. 👌



  • @Finnegan danke, dass du soviel Zeit investiert hast. Das wäre wirklich nicht nötig gewesen 😉


Anmelden zum Antworten