Sinn und Zweck von "nested classes"?



  • Ich habe heute zum ersten mal "nested classes" ausprobiert. Ohne nachzulesen dachte ich, dass die "innere Klasse" zur "Äußeren" gehört und zusammen mit dieser Instanziert wird. Ich dachte auch, dass damit dann der Zugriff auf die Attribute der äußeren Klasse möglich ist.

    Ein kurzer Test hat mich dann wieder auf den Boden gebracht. 😉

    a) muss ich die innere Klasse selbst instanzieren und b) kann diese kann mangels `this' nicht mehr auf die äußere Klasse zugreifen.

    Nur was bringen mir dann "nested classes"?

    Für mich sieht es momentan so aus, als ob ich nur eine andere Art von "Namespace" habe.

    Übersehe ich da was?



  • Ja, die nested classes sind im normalen Gebrauch relativ unabhängig von der "Mutterklasse". Der größte Vorteil besteht wohl darin, daß du bestimmte Implementationsdetails in deiner Klasse kapseln kannst (z.B. lebt eine Iterator-Klasse in ihrem zugehörigen Container).



  • Danke für die Antwort.

    So richtig viel Sinn kann ich in den nested classes immer noch nicht sehen. Gerade wenn man dem Nutzen die Komplexität der Sache (protected nested...) entgegen stellt. Egal 😉

    Weißt Du zufällig wie das bei Java und C# aussieht?



  • Der Vorteil der Konstruktion ist die Zusammengehörigkeit zwischen Mutterklasse und eingebetteter Klasse. (und durch die Zugriffsrechte kannst du außerdem auch kontrollieren, wer etwas von dieser Klasse weiß - der Baumaufbau ist ein Implementationsdetail einer set<>, funktioniert aber nicht über Zusatzobjekte).

    Außerdem hast du auf diese Weise die Möglichkeit, über templates auf die richtigen Typen zugreifen zu können - "CONT::iterator" liefert immer die passende Iteratorklasse für deinen Container, egal ob du mit vector<>, list<> oder map<> hantierst (und dabei ist es egal, ob sich dahinter ein typedef für T* (vector) oder eine eingebettete Klasse verbirgt).

    Weißt Du zufällig wie das bei Java und C# aussieht?

    Von Java kenne ich (leider) nur die Grundlagen, von C# nur den Namen, also kann ich da nicht weiterhelfen.



  • Ich bin mir da nicht ganz sicher, aber da Java-Packages afaik auch Klassen sind, ist dieses ganze Konzept auf "eingenistete" Klassen aufgebaut.
    Bei C# sollte es auch gehen, aber da gibt es auch Namespaces, es ist also nicht notwendig.



  • In Java und C# benutzt man nested Klassen nicht, um Implementierungsdetails zu verbergen. Dazu gibt es dort Packages bzw. Assemblies. Die eigentlich nutzbare Set-Klasse wäre dann public, die Knoten-im-Baum-Klassen wären internal.

    Nested Klassen sind außerdem Member der umgebenen Klassen und können deshalb auch auf deren private Elemente zugreifen. In Java gibt es noch inner classes, das sind nested classes die immer gleichzeitig mit einer äußeren Klasse instanziert werden. Wenn man das nicht braucht oder haben will, muss man die nested class als statisch deklarieren.

    Wenn man in C++ nicht auf die privaten Member der äußeren Klasse zugreifen kann, muss ich mich fragen, wo der Sinn von nested classes dort überhaupt liegt. Vielleicht als Package-Ersatz?



  • Optimizer schrieb:

    Wenn man in C++ nicht auf die privaten Member der äußeren Klasse zugreifen kann, muss ich mich fragen, wo der Sinn von nested classes dort überhaupt liegt. Vielleicht als Package-Ersatz?

    Habe nicht erkannt, wo Packages Vorteile bringen. Aber lassen wir es besser, C++ mit einer Spielsprache zu vergleichen.
    Nested Classes sind wie bereits erwähnt sinnvoll bei Iteratoren.

    for(typeof(container)::Iterator i=container.begin();...
    

    Manchmal setzt man sie, weil eine Klasse einfach nur einer anderen gehört

    template<typename T> class List{struct Node{T data;Node* next};
    

    hier ist T automagisch auch für Nodes definiert.
    und man sieht es gelegentlich zum pimpln, dann aber meist schrottig, weil die Impl-Klasse vielleicht gar nicht in einen header muß.



  • Achso, ich hab das was falsch verstanden. Die nested class kann schon auf alle Member der äußeren Zugreifen, damit haben sie natürlich einen gewissen Sinn. Hatte die b) Aussage vom Thread-Ersteller falsch interpretiert.

    Denn Sinn von packages kapiert man btw. ganz leicht, wenn man etwas größere Spiel-Programme schreibt und welche zur Verfügung hat. So kann man Programmmodule designen, die aus vielen Klassen bestehen, von denen aber nur eine Handvoll von außerhalb des Packages nutzbar sind. Man kann auch Klassenmember package-intern deklarieren, dann können alle Klassen des selben Packages die Member benutzen.

    Wenn du für diesen Zweck nested classes benutzt, hat die nested class dann außerdem Zugriff auf _alle_ Member der äußeren Klasse, was auch unerwünscht sein kann. Deshalb hat sich der Stroustrup die tolle Lösung "friend" einfallen lassen, damit man trotzdem bestimmen kann, wer was sieht. Packages finde ich um Welten schöner als friend.



  • volkard schrieb:

    Aber lassen wir es besser, C++ mit einer Spielsprache zu vergleichen.

    Ich hatte da btw. keine bestimmte Sprache im Kopf, nur das Konzept von Packages. Sowas gibt es in vielen Sprachen, seltsamerweise hat sonst jede Sprache es verplant, die geniale Idee von friend aufzugreifen. 😃



  • Optimizer schrieb:

    seltsamerweise hat sonst jede Sprache es verplant, die geniale Idee von friend aufzugreifen. 😃

    keiner lobt friend. friend war nicht notwendig, um packages zu simulieren. jede klasse muß für sich selbst sorgen und sich gegen datenfrickler schützen. gleich ein ganzes dorf einzuladen, ist sicher nicht etwas, was zur fehlervermeidung sachdienlich ist. normalerweise brauchen nested classes nur zugriff auf öffentliche methoden. nix gut, die erstmal aus prinzip zum friend zu machen.



  • Es ist natürlich auch nicht die Idee dahinter, standardmäßig alles package-intern zu machen. Normalerweise gibt es eine bewusst klein gehalten Anzahl von Members, die internal gemacht werden (Anzahl oft auch gleich 0), in den Fällen wo man sonst vielleicht friend verwendet hätte.

    Was aber mehr oder weniger das häufigere ist, sind package-interne Klassen - oder muss wirklich jedes Programmmodul jede Klasse kennen? Sowas fehlt doch in C++ einfach. Du wirst vermutlich nicht aus Prinzip alle Klassen erstmal nesten. Du hast deine Namespaces und die Klassen aus namespace A, die von Klassen aus namespace B benutzt werden, ist wahrscheinlich sehr überschaubar. Da wäre es dann schön, wenn man public Klassen und interne Klassen haben könnte.

    normalerweise brauchen nested classes nur zugriff auf öffentliche methoden.

    Wenn Klassen nur Zugriff auf öffentliche Methoden brauchen, dann kommen sie in C#, VB.Net, C++/CLI, Java in das selbe Assembly/Package und können sich gegenseitig benutzen. Kein Grund, die endlos kompliziert zu nesten.

    Aber der nested Iterator braucht oft Zugriff auf private Member, deshalb kann man den schön private reinnesten und nur ne Interface-Referenz nach außen geben. Der Sinn vom nesten ist eigentlich schon, dass man die privaten Member nutzen kann, ich würde aber verstehen, wenn man das in C++ oft benutzt, um Klassen zu verstecken. Da fehlt es einfach an Alternativen.



  • Optimizer schrieb:

    Was aber mehr oder weniger das häufigere ist, sind package-interne Klassen - oder muss wirklich jedes Programmmodul jede Klasse kennen?

    muß nicht, darf aber.

    normalerweise brauchen nested classes nur zugriff auf öffentliche methoden.

    Wenn Klassen nur Zugriff auf öffentliche Methoden brauchen, dann kommen sie in C#, VB.Net, C++/CLI, Java in das selbe Assembly/Package und können sich gegenseitig benutzen. Kein Grund, die endlos kompliziert zu nesten.[/quote]
    ok, wir nähern uns dem ziel.
    es wird bei iteratoren genested, damit man schreiben kann

    template<typename C>
    void sort(C* container){
       typename C::Iterator b=container.begin();//hier, Iterator nested in C,
                                                //wasauchimmer C sein mag
       typename C::Iterator e=container.end();
       if(b!=e) 
          sort(b,e);
    }
    

    Aber der nested Iterator braucht oft Zugriff auf private Member,

    die letzten zehn, die ich geschrieben hab, brauchten keinen.

    deshalb kann man den schön private reinnesten und nur ne Interface-Referenz nach außen geben. Der Sinn vom nesten ist eigentlich schon, dass man die privaten Member nutzen kann, ich würde aber verstehen, wenn man das in C++ oft benutzt, um Klassen zu verstecken. Da fehlt es einfach an Alternativen.

    ich glaube, was du meinst, sind namespaces der art

    namespace container_privat{
       struct ListNode...
    }
    

    und

    namespace privat{
       class A{...};//verschiedene implementierunen
       class B{...};
       class C{...};
       class D{...};
    }
    typedef privat::C List;//defaultimplementierung vorschlagen
    

    jo, das macht man auch mal. die bloße veröffentlichung der klasse container_privat::ListNode ist übrigens gar kein risiko, solange keine Liste eine öffentliche funktion anbiet, die einen ListeNode-Zeiger nimmt oder zurückgibt. aber aufgeräumter ist schon List::Node statt ListNode und dann kann man als bonus, wenn man spaß hat, die ganze klasse Node privat in List halten.



  • volkard schrieb:

    Optimizer schrieb:

    Was aber mehr oder weniger das häufigere ist, sind package-interne Klassen - oder muss wirklich jedes Programmmodul jede Klasse kennen?

    muß nicht, darf aber.

    Das relativiert sich mit zunehmender Projektgröße. Große Probleme löst man ja bekanntlich nicht sondern zerlegt sie. Wenn man einen Webbrowser entwickelt, gibt man einer Gruppe die Aufgabe, ein API für das GUI zu entwerfen, das erweiterbar und Plugin-fähig ist. Eine andere Gruppe entwickelt Netzwerkkomponenten, damit die Gruppe für das Rendering der Seiten sich nicht mehr Ärgern muss, dass ein WSA_ASYNC_TIMEOUT kommt.

    Irgendwann schreibt man wiederverwendbare Libs und tut sie in eigene DLLs und schon hat man nen Zugriffsschutz, denn schon immer konnte man nicht alle Implementierungsdetails einer DLL nutzen. Im Windows API gibt es vermutlich Funktionen, von denen keiner was weiß, die nur implementieren. Heute muss natürlich alles objektorientiert sein und deshalb gibt es in WinFX Klassen, von denen keiner was weiß - DLL-intern. Wird also schon seit Jahren auch bei C++-Projekten gemacht, packages zu erstellen.

    Die gibt's nur nicht im Standard, aber eine höhere Ordnung als Klassen braucht man halt. Ab 2000 Klassen reicht es nicht mehr, nur private Datenelemente zu sichern, sondern man hätte gerne ein großes Programm in eine Handvoll Module aufgeteilt, von denen jedes nur noch 200 Klassen hat. Verstehe ich ehrlich gesagt nicht, wie man da den Sinn von nicht sehen kann. Den Sinn in Klassen siehst du ja auch. Packages modularisieren halt auf höhere Ebene und wie gesagt, in C++ gibt es sowas in der Praxis auch schon immer, dank proprietärer Lib-Formate.

    es wird bei iteratoren genested, damit man schreiben kann

    template<typename C>
    void sort(C* container){
       typename C::Iterator b=container.begin();//hier, Iterator nested in C,
                                                //wasauchimmer C sein mag
       typename C::Iterator e=container.end();
       if(b!=e) 
          sort(b,e);
    }
    

    Dieses Beispiel zeigt eigentlich sehr schön, dass C völlig irrelevant ist. Also ist auch der genaue Typ von C::Iterator irrelevant und ich möchte ihn nicht kennen müssen.

    Darf ich ein IMHO schöneres Design vorschlagen - man kann die genesteten Iteratoren gleich mal private machen:

    public interface Iterator<T> { }
    
    public class ArrayList<T> {
        public Iterator begin() {
            return new ArrayListIterator(this);
        }
    
        private class ArrayListIterator : Iterator<T> { ... }
    }
    

    So muss ich C weder zur compile- noch zur Laufzeit kennen. C kann sogar ein völlig neuer Typ sein, der aus einer Laufzeit-Bibliothek kommt. Das muss ich einfach schöner finden, da kann ich nicht anders.

    Eine Alternative wäre übrigens:

    public class ArrayList<T> { ... }
    
    public interface Iterator<T>
    
    internal class ArrayListIterator<T> : Iterator<T> { ... }
    

    Und jetzt sind wir wieder bei der Frage: Wann nesten und wann nicht. Die Antwort ist wieder, wenn der Iterator Zugriff auf private Member braucht, dann neste ich ihn da rein. Oder wenn ich es schöner finde, T gleich zu kennen.

    Aber der nested Iterator braucht oft Zugriff auf private Member,

    die letzten zehn, die ich geschrieben hab, brauchten keinen.

    Das mag ja sein, aber das geht halt nicht immer. Wie würde denn dein Iterator zu einer SortedMap<K, V> aussehen? Du willst wohl kaum den Rot-Schwarz-Baum darin veröffentlichen. Aber mit dem von mir oben vorgeschlagenen Design ist das auch kein Problem, wenn der Iterator die Internas kennt, weil der Benutzer kennt den Iterator nicht.



  • Optimizer schrieb:

    volkard schrieb:

    Optimizer schrieb:

    Was aber mehr oder weniger das häufigere ist, sind package-interne Klassen - oder muss wirklich jedes Programmmodul jede Klasse kennen?

    muß nicht, darf aber.

    Das relativiert sich mit zunehmender Projektgröße. Große Probleme löst man ja bekanntlich nicht sondern zerlegt sie. Wenn man einen Webbrowser entwickelt, gibt man einer Gruppe die Aufgabe, ein API für das GUI zu entwerfen, das erweiterbar und Plugin-fähig ist. Eine andere Gruppe entwickelt Netzwerkkomponenten, damit die Gruppe für das Rendering der Seiten sich nicht mehr Ärgern muss, dass ein WSA_ASYNC_TIMEOUT kommt.

    lass mal das argument mit der projektgröße. guter stil mißt sich immer bei großen projekten, beim tausenzeiler isser praktisch egal. ich bin der meinung, daß mit zunehmender größe man immer stärker kapseln muß, auch gegen die nachbarn im eigenen package und zum schluß eigentlich gegen jeden. irgendwie senkt sich die fehlersuchzeit, wenn der programmierer paranoid ist, und das ist was schrecklich gutes. ein WSA_ASYNC_TIMEOUT wird vermutlich ne exception auslösen, oder? hat wenig mit packages zu tun.

    Irgendwann schreibt man wiederverwendbare Libs und tut sie in eigene DLLs und schon hat man nen Zugriffsschutz, denn schon immer konnte man nicht alle Implementierungsdetails einer DLL nutzen. Im Windows API gibt es vermutlich Funktionen, von denen keiner was weiß, die nur implementieren.

    jo, lies dazu als einsteig http://www.sysinternals.com/Information/NativeApi.html

    Die gibt's nur nicht im Standard, aber eine höhere Ordnung als Klassen braucht man halt. Ab 2000 Klassen reicht es nicht mehr, nur private Datenelemente zu sichern, sondern man hätte gerne ein großes Programm in eine Handvoll Module aufgeteilt, von denen jedes nur noch 200 Klassen hat. Verstehe ich ehrlich gesagt nicht, wie man da den Sinn von nicht sehen kann. Den Sinn in Klassen siehst du ja auch. Packages modularisieren halt auf höhere Ebene und wie gesagt, in C++ gibt es sowas in der Praxis auch schon immer, dank proprietärer Lib-Formate.

    die libs oder dlls sind nur detail. nur ne ansammlung von übersetzungseinheiten. ob man sowas baut oder nicht, ist egal. aber zusammenhängenden code irgendwie zusammenzutun, ist was gutes. zum beispiel auf platte in ein verzeichnis rein und im code in einen namespace rein.

    typename C::Iterator b=container.begin();//hier, Iterator nested in C,
                                                //wasauchimmer C sein mag
    

    Dieses Beispiel zeigt eigentlich sehr schön, dass C völlig irrelevant ist. Also ist auch der genaue Typ von C::Iterator irrelevant und ich möchte ihn nicht kennen müssen.
    Darf ich ein IMHO schöneres Design vorschlagen - man kann die genesteten Iteratoren gleich mal private machen:
    [cs]public interface Iterator<T> { }
    ...
    So muss ich C weder zur compile- noch zur Laufzeit kennen. C kann sogar ein völlig neuer Typ sein, der aus einer Laufzeit-Bibliothek kommt. Das muss ich einfach schöner finden, da kann ich nicht anders.

    in c++ entsteht aus sowas (extremfall vcerkettet liste):
    man faßt Iterator mit basisklassenzeigern an und verliert kopier- und verglichs-semantik. na, kann man zu nullkosten kapseln und wiedergewinnen. es bleibt aber, daß man solche iteratoren per zeiger anfaßt und auf dem freispeicher anlegt. wie in java alle objekte, die von Object erben. die alternative mit normalem c++ führt dazu, daß ein Iterator genau aus einem zeiger besteht, praktisch immer in einem register lebt und so praktischen kram macht, der dafür sorgt, daß man genausoschnell wie mit assembler ist.

    könnte ein intelligenter compiler das auch mit deinem entwurf schaffen? ja, aber kein mir bekannter c++-compiler. die haben nämlich das modell mit den übersetzungseinheiten, die danach ein *dummer* linker zusammenfügt. sicher ein großes problem, was zu manchem code führt, der nicht hübsch ist. dadurch muß allerhand in den headers bekanntgemacht werden, was eigentlich kein mensch lesen will, nur der compiler brauchts, wenn er nicht auf alle objekte per zeiger oder referenz zugreifen soll. in java hätte man keine zusatzkosten bei deinem design, java fasst by design die objekte per zeiger an - und dem compiler (auch dem c++-compiler) ist erlaubt, das wegzuoptimieren, wobei der java-compiler wesentlich leichteres siel hat.

    Und jetzt sind wir wieder bei der Frage: Wann nesten und wann nicht. Die Antwort ist wieder, wenn der Iterator Zugriff auf private Member braucht, dann neste ich ihn da rein. Oder wenn ich es schöner finde, T gleich zu kennen.

    oder wen die genestete klasse echt für keinen anderen von interesse sein soll. so wird ListNode gerne ganz ohne nachdenken automatisch zu List::Node.

    die letzten zehn, die ich geschrieben hab, brauchten keinen.

    Das mag ja sein, aber das geht halt nicht immer. Wie würde denn dein Iterator zu einer SortedMap<K, V> aussehen? Du willst wohl kaum den Rot-Schwarz-Baum darin veröffentlichen. Aber mit dem von mir oben vorgeschlagenen Design ist das auch kein Problem, wenn der Iterator die Internas kennt, weil der Benutzer kennt den Iterator nicht.

    weiß nicht. evtl würde ich ne klasse RedBlackTree nebst RedBlackTree::Iterator bauen und SortedMap nebst SortedMap::Iterator wäre nur ein dummer adapter. aber das verschiebt das problem nur. nun zwingt das mit den übersetungseinheiten uns dazu, daß RedBlackTree und RedBlackTree::Iterator (oder RedBlackTreeIterator) vollständig bekannt sind, weil sie templates sind. der Iterator braucht nen operator++, und dazu hat er lust, die interna des Trees zu kennen. dann macht der Tree seinen Iterator zum friend. oder man macht den Iterator sehr dumm und er muß für den op++ sowas wie Iterator RedBlackTree::moveRight(Iterator old) aufrufen, dann ist nix friend. dann kann der benutzer böswillig sein und nen Iterator mit garbage füllen und moveRight aufrufen. gegen moveRight-aufrufen ist nix zu sagen. aber gegen das garbage-füllen. da könnte man die konstruktoren des Iterators private machen und andersrum friend. aber oha, der nutzer kann immernoch mist machen, nämlich sich nen Iterator besorgen, den Baum komplett ummodeln (leeren) und dann mal den op++ aufrufen. dann barcuhtr man auch nicht die konstruktoren zu privatisieren und kommt ohne friend aus, wenn man die logik eher in den Baum legen mag (mehr mein stil, da hab ich den bei designänderungen im baum auch die zu prüfenden methoden beisammen) oder man macht friend, wenn man sie im iterator haben mag (weniger mein stil). dahinschreiben, wo ich's am liebsten hab, und hin- und herdelegieren geht in c++ zu nullkosten, da gewöhnt man sich sowas an.

    ich bin sicher, dir fällt ein einleuchtendes beispiel ein, wo ein ganzes rudel klassen gegenseitig kenntnis der interna haben sollte, weswegen man einen haufen package-interne members baut. bin gespannt drauf. meine c++-scheuklappen erlauben mir gerade nicht, so eins zu finden.

    edit: da fällt mir was ganz altes ein: eigentlich will ich friend noch feiner. will machen können, daß eine bestimmte andere klasse genau eine meiner privaten methoden aufrufen kann aber nicht gleich alle.



  • volkard schrieb:

    ich bin sicher, dir fällt ein einleuchtendes beispiel ein, wo ein ganzes rudel klassen gegenseitig kenntnis der interna haben sollte, weswegen man einen haufen package-interne members baut. bin gespannt drauf. meine c++-scheuklappen erlauben mir gerade nicht, so eins zu finden.

    Was heißt "ein Haufen"? Wie gesagt, solche Fälle sollten ähnlich selten sein wie friend. Für mich sind Fälle vorstellbar, wo du eine Klassenhierarchie hast:

    package Malzeug {
    
    public abstract class Bild {
        < einige Methoden >
        internal IntPtr myNativeImageHandle;
    }
    
    internal class GebuffertesBild : Bild { }
    
    internal class AnderesSpeziellesBild : Bild { }
    
    public abstract class Malfläche {
        protected Bild getMyImage();
        private Bild aktuellesBild;
    }
    
    }
    

    Man hat hier eine Lib, die es erlaubt, schnell eigene Malflächen zu erstellen und auch das enthaltene Bild darin zu benutzen und zu bearbeiten. Aber unter keinen Umständen braucht irgendein komisches Betriebssystem-Handle nach außen gelangen. Mit "außen" ist hier natürlich das Package gemeint, die Grenze zwischen dem Implementierer der Malzeug-Lib und dem Benutzer der Lib.

    Gleichzeitig ist aber klar, dass die abgeleiteten Bild-Klassen das Handle noch benötigen. Du magst jetzt argumentieren, dass durch das internal _alle_ Klassen im package an das Handle kommen. Das ist richtig, aber das package wird von _einem_ Team erstellt und ein Package hat eine überschaubare Menge von Klassen, es ist also relativ absehbar, wer alles an das Handle kommt und damit was machen kann. Mit Sicherheit kein Benutzer der Lib.

    Demgegenüber, bei dir sind sozusagen alle Klassen public. Da wäre das erst wirklich ein Problem, wenn das Handle protected wäre. Also müsstest du alle abgeleiteten Klassen friend machen und das ist mal wirklich schlechter Stil, wenn die Basisklasse ihr abgeleiteten Klassen kennt.

    Überhaupt benutzt man Packages häufiger dazu, den Zugriff zu beschränken, anstatt den Zugriff zu öffnen. Du denkst, ich will dir die ganze Zeit erzählen, dass das tolle an Packages ist, dass man package-interne Klassenmember machen kann. Das tolle ist aber, dass man Package-interne Klassen machen kann.

    Eine Klasse ist nicht besonders genug, innerhalb eines Programms. Ich schreibe für jeden Dreck eine Klasse, sogar für einen Vektor. Ist doch klar, dass nur klassenweite Zugriffsregelungen für ein komplexes Programm nicht mehr ausreicht, man muss Klassen auch mal zu einer höheren Ordnung zusammenfassen können.


Log in to reply