Welche Schnittstelle anbieten: Container oder begin() und end()



  • Moin,

    ich habe ein Objekt Klasse A, die Referenzen/Pointer auf eine Menge (nicht mathematisch) von B-Objekten hält. Darauf brauche ich lesenden Zugriff. Wie
    würdet ihr die Schnittstelle gestalten? Was wäre da idiomatisches C++?

    Ich tendiere Stark zu Version 2.

    class A {
        // so
        typedef std::vector<const B*>::const_iterator b_iter;
        b_iter b_begin();
        b_iter b_end();
    
        // oder so?
    
        const std::vector<const B*>& bes() const;
    
    private:
        std::vector<const B*> internals;
    };
    

    Gruß, Panke



  • Das "defensivste" Interface ist vermutlich template <class OutIt> void get(OutIt outputIterator) . Also nen Getter der alle Elemente in einen Output-Iterator reinkopiert.

    Wenn das reicht -> so machen.
    Wenn das z.B. zu langsam ist, oder die Objekte nicht kopierbar sind, dann auf jeden Fall eher die begin/end Variante als direkt den Vektor rausreichen.



  • Danke, auf die Idee mit dem OutputIterator wär ich nicht gekommen.



  • Kann ich sowas auch in einem Interface machen? Also beispielsweise:

    class Item{};
    
    struct Interface {
        typedef std::vector<C>::iterator iter;
        virtual iter begin() = 0;
    };
    
    struct Implementation : public Interface {
        typedef std::deque<C>::iterator iter;
        iter begin() { /* ... */ }
    };
    

    So wie oben dargestellt, sagt mir gcc invalid covariant for return type ... . Was ich möchte ist, im Interface nur zu sagen, dass da ein Iterator zurückgegeben wird ohne die Implementierungen auf eine interne Struktur festzulegen. Was mir da einfällt, wäre ein Template. Dann müsste ich aber ein Klassentemplate schreiben, weil Templatemethoden ja nicht virtuell sind.

    Alternative: Nutzer der Klassen sind Templates und ich lass das Interface weg. Gefällt mir aber irgendwie auch nicht Recht. Aber noch am besten. Im Grunde ist das für mein Programm nur ein kleineres Problem, ich könnt auch einfach festlegen, welcher Container intern genutzt wird und punkt. Was macht da der Profi?



  • Der Typ C ist in Implementation unbekannt.

    nur zu sagen, dass da ein Iterator zurückgegeben wird

    Erben die Standardcontainer von einem Interface mit begin oder end ? Nein. Mache nicht aus der Klasse ein Template sondern aus deinen Algorithmen.



  • Alles zu templatisieren kann aber auch der falsche Weg sein – besonders wenn es darum geht, Abhängigkeiten von Programmbestandteilen klein zu halten. In diesem Fall kann sich das Konzept der Type Erasure bewähren (siehe z.B. hier).

    Zur Zusammenfassung einiger Möglichkeiten, wie sich eine Schnittstelle zu einem Member-Container gestalten lässt:

    • (Const-)Referenz auf Container als Rückgabetyp. Schlechte Kapselung, Abstraktion zum Containertyp geht verloren.
    • Begin-End als Iterator-Schnittstelle.
    • Output-Iterator. Erlaubt aber nur Lesezugriff und erfordert Kopien.
    • Funktor, der an eine Memberfunktion apply() übergeben wird und intern mit std::for_each() auf jedes Element des Containers angewandt wird. Kann Veränderungen durchführen oder auch Daten im Funktionsobjekt speichern. Braucht bei der Anwendung jedoch jedes Mal viel Boilerplate-Code, zumindest ohne Lambda-Funktionen.
    • Polymorpher Basisklassenzeiger anstelle eines Funktors, mit virtueller Methode statt operator() . Im Prinzip aber gleich wie oben.
    • Range als Rückgabetyp. Sehr flexibler Ansatz, auch mit Type Erasure kombinierbar.

Anmelden zum Antworten