An die OOP-Fetischisten- & Dogmatiker: Freie Funktionen!?



  • Gregor schrieb:

    @finix: Du versuchst hier auf einer Art Metaebene den Argumenten der anderen Diskussionsteilnehmer aus dem Weg zu gehen. Du sagst dir einfach "Alles, was die anderen bringen ist aus dem Kontext gerissen und sowieso nur auf unnötige Details bezogen.". ...oder ähnliches.

    Mir ist nicht ganz klar was du hier mit "Metaebene" meinst, aber von meinem Verständnis des Wortes her wär damit wohl etwas wie "Zitat: 'finix = troll'" gemeint.

    Aber ich sehe was du meinst:

    Gregor schrieb:

    finix schrieb:

    Gregor schrieb:

    PS: Mit "globalen Funktionen" meinte ich bisher immer Funktionen, die sich in keinem Namespace befinden und auch nicht an eine Klasse oder ähnliches gebunden sind. Weiß nicht, ob die allgemeine Definition davon anders ist. Vielleicht liegt da ein gewisses Missverständnis zwischen uns vor.

    Auch gut, was ist an "globalen Funktionen" so furchtbar schlimm?

    ... Du hast bei der Beantwortung der Frage keine Namespaces zur Verfügung. 😉

    Hatte das gestern komplett falsch gelesen; war ein wenig abgelenkt und hab mehr oder weniger nur globale Funktionen in Anführungszeichen gedeutet... dachte der Begriff "freie Funktionen" war nicht mehr missverständlich. Ging mir also, wie schon zuvor erklärt eigentlich nur um die Antwort auf deine konkrete Frage, spricht abstrakt "Package = logische Programmeinheit" bildet sich in C++ auf Namespaces ab wie es sich in Java auf Packages abbildet.
    Von daher seh ich natürlich auch wie für Opti Details die für mich im Kontext irrelevant erschienen durchaus alles andere als irrelevant sind.



  • Gregor schrieb:

    @finix: Du versuchst hier auf einer Art Metaebene den Argumenten der anderen Diskussionsteilnehmer aus dem Weg zu gehen. Du sagst dir einfach "Alles, was die anderen bringen ist aus dem Kontext gerissen und sowieso nur auf unnötige Details bezogen.". ...oder ähnliches. Das kannst Du ja meinetwegen denken, aber wenn Du willst, dass Dich die anderen Diskussionsteilnehmer auf Dauer ernst nehmen, solltest Du etwas genauer erklären, warum welche Aussage in die falsche Richtung geht. Du gehst nämlich überhaupt nicht auf die sachlichen Argumentationen der anderen ein. Es ist, als ob man bei Dir gegen eine Wand redet: Man erhält keinerlei sinnvolle Reaktion zurück, auf der man aufbauen kann.

    Da kommt es dann natürlich auch dazu, dass jemand einen Vergleich "finix = troll?" macht. ...und Du kannst mal davon ausgehen, dass andere auch schon etwas in diese Richtung denken, auch wenn sie die Diskussion noch nicht aufgeben möchten.

    /signed



  • finix schrieb:

    Mir ist nicht ganz klar was du hier mit "Metaebene" meinst, aber von meinem Verständnis des Wortes her wär damit wohl etwas wie "Zitat: 'finix = troll'" gemeint.

    Nein, eigentlich nicht. Er weist dich nur daraufhin nicht zu viel zu abstrahieren wo es nichts zu abstrahieren gibt.

    MfG SideWinder



  • DEvent schrieb:

    Any schrieb:

    Statt meine Klassen mit privaten Helferfunktionen zuzumüllen mache ich oft sowas:

    statt

    class Foo
    {
      void helper1(int);
      void helper2(string);
      void helper3(double);
      void helper(); //verwendet helper1,2,3
    };
    

    mache ich das so:

    class Foo
    {
      void helper();
    };
    
    //in foo.cpp
    namespace
    {
      void helper1(int){}
      void helper2(string){}
      void helper3(double){}
    }
    

    Meist braucht nur "helper" zugriff auf die ganzen Membervariablen und die kleinen Hilfsfunktionen von helper sind nur dazu da den code übersichtlicher zu gestalten indem details ausgelagert werden.

    wo ist hier den der Vorteil? die Helper-Methoden sind doch eh private, da ist es vollkommene egal ob ich 100 oder 1000 davon habe.

    Der Header ist kleiner und damit übersichtlicher, wodurch die Dokumentation ebenfalls kürzer ausfällt und diese Funktionen nicht in der Benutzerdoku auftauchen.



  • DEvent schrieb:

    Any schrieb:

    Statt meine Klassen mit privaten Helferfunktionen zuzumüllen mache ich oft sowas:

    statt

    class Foo
    {
      void helper1(int);
      void helper2(string);
      void helper3(double);
      void helper(); //verwendet helper1,2,3
    };
    

    mache ich das so:

    class Foo
    {
      void helper();
    };
    
    //in foo.cpp
    namespace
    {
      void helper1(int){}
      void helper2(string){}
      void helper3(double){}
    }
    

    Meist braucht nur "helper" zugriff auf die ganzen Membervariablen und die kleinen Hilfsfunktionen von helper sind nur dazu da den code übersichtlicher zu gestalten indem details ausgelagert werden.

    wo ist hier den der Vorteil? die Helper-Methoden sind doch eh private, da ist es vollkommene egal ob ich 100 oder 1000 davon habe.

    Was was ist wenn helper1-3 nur Implementationsdetails von helper sind?
    Was ist bei universell einsetzbaren Klassen, wenn's wirklich auf Hunderte von solchen Methoden hinausläuft?



  • SideWinder schrieb:

    finix schrieb:

    Mir ist nicht ganz klar was du hier mit "Metaebene" meinst, aber von meinem Verständnis des Wortes her wär damit wohl etwas wie "Zitat: 'finix = troll'" gemeint.

    Nein, eigentlich nicht. Er weist dich nur daraufhin nicht zu viel zu abstrahieren wo es nichts zu abstrahieren gibt.

    Vielleicht solltest du mal nachschlagen was "Metaebene" bedeutet.



  • Gregor schrieb:

    Helium schrieb:

    In einer Sprache wie Smalltalk ist eben alles ein Objekt, auch Funktionen (Codeblöcke mit Parametern).

    Ich kenne Smalltalk leider nicht. Kannst Du da mal ein kleines Beispiel bringen, inwiefern Funktionen da als Objekte behandelt werden bzw. wie man mit denen als Objekt umgeht?

    Man kann einem Objekt eine Nachricht schicken. Wie ein Objekt auf eine Nachricht reagiert ist durch die gleichnamige Methode in der Klasse definiert. Solche Nachrichten können auch Argumente enthalten.

    Naja. Eine Zahl kann man z.B. mit einem Zahlenliteral angeben und einer Variable zuweisen:

    foo := 42.
    

    Code Kann man zu Blöcken zusammen fassen [...].

    Da alles ein Obejkt ist, gilt das natürlich auch für einen Codeblock:

    foo := [ ... ].
    

    Das wird sehr oft ausgenutzt in Smalltalk. Z.b. Gibt es keine Schleifen oder bedingte Verzweigungen, denn wir wollen ja Objektorientiert und nicht imperativ programmieren. Boolsche Objekte verstehen die Nachricht ifTrue:ifFalse: .

    foo < bar liefert z.B. ein solches Objekt. Es hat entweder den Typ True oder den Typ False (Beide Abgeleitet von Boolean).

    Die implementierung der Methode ifTrue:ifFalse: in der Klasse True sieht so aus, dass einfach das erste Argument ausgeführt wird und das zweite ignoriert wird, die implementierung in der Klasse False führt das zweite Argument aus und ignoriert das erste.

    Man schickt also einfach die Nachricht ifTrue:ifFalse: an einen solchen boolschen Ausdruck mit zwei Codeblöcken als Argument.

    foo < bar ifTrue:  [ Transcript show: 'foo ist kleiner als bar'. ]
              ifFlase: [ Transcript show: 'foo ist nicht kleiner als bar'. ].
    

    Das nur als Beispiel zur Verwendung von Codeblöcken als Objekt.

    Ein solcher Codeblock kann wiederum Argumente entgegennehmen.

    [:argument | "eigentlicher Code"]
    

    Das kann man z.B. wie folgt verwenden:

    1 to: 20 do: [:x | Transcript show: x asString. ].
    

    Es wurde die Nachricht to🇩🇴 mit den Argumenten 20 und [:x | Transcript show: x asString. ] an das Objekt 1 geschickt. Das ganze entspricht quasi einer Schleife. Die Methode ruft den Codeblock entsprechend oft auf. Dabei wird dann jeweils an den Codeblock der Wert übergeben, der bei der Iteration gerade aktuell ist.

    Ich gebe zu, das war sehr knapp und ich habe auch alles einfach so runtergeschrieben, wie es mir gerade in den Kopf gekommen ist, aber ich hoffe, du konntest dennoch etwas verstehen. Alles ist ein Objekt. Code bzw. Codeblöcke bilden hierbei keine Ausnahme. Da man in Variablen Verweise auf Objekte speichern kann, kann man das auch mit Codeblöcken.



  • DEvent schrieb:

    globale Funktionen sind für mich Funktionen die keinen Kontex haben, zu nichts in Bezug stehen. Ein Namespace stellt aber ein Bezug her.

    Ein Namespace ordnet nur. std:: sagt mir nur, dass darin alle Funktionen, Klassen und Objekte stehen, die der Standard definiert.

    DEvent schrieb:

    Die Funktion sort(bla[] x) hat für mich keinen Bezug. Ich habe absolut keine Ahnung was sie macht, sortiert sie auf- oder absteigend? Oder mit Quiksort oder mit Mergesort?
    Quicksort.sort(bla[] x). Aha, da wird einem sofort klar, das die statische-Methode nach dem Quicksort-Prinzip sortiert.

    Und was hast du davon? Es bringt dir nichts, zu wissen, wie sortiert wird (es sei denn, du weißt wie das Feld in etwa aussieht, aber das sollte sehr selten sein). Die Sortierrichtung gibt man mittels eines Funktors (der auch nur die ver-OOP-te (und "mächtigere") Variante der Funktion ist) oder per Iterator-Art an. Das ganze ist deutlich flexibler und sinnvoller. Man einigt sich auf Konventionen (in diesem Fall den Standard), die bestimmte Eckdaten bereitstellen (z.B. Laufzeit O(n*log(n))). Hauptsache ist, der Container (nicht nur Arrays!) ist nachher nach den eingegebenen Kriterien sortiert.

    DEvent schrieb:

    ein weiterer Vorteil:
    wenn ich eine Funktion sin(double x) habe im system.h und irgendwann importiere ich winsystem.h und die Header hat auch eine sin(double x)-Funktion, welche Funktion soll den nu der Compiler benutzen?

    Die im richtigen Namespace. Der globale Namespace wird nicht (ben|verschm)utzt. Selbst C-Leute benutzen (meist) ein Präfix, also Lightnamespaces.

    DEvent schrieb:

    und von Java, C# und co. übernommen und verbessert.

    Das halte ich für ein Gerücht. Klassen als reine Namespaces zu missbrauchen ist nicht sinnvoll.



  • finix schrieb:

    Was was ist wenn helper1-3 nur Implementationsdetails von helper sind?
    Was ist bei universell einsetzbaren Klassen, wenn's wirklich auf Hunderte von solchen Methoden hinausläuft?

    es doch vollkommen egal, weil die helper-methoden nicht public sind 💡
    Den Satzt mit der universell einsetzbaren Klassen verstehe ich nicht. Die Klasse Collection hat sicherlich auch zig private-Methoden und ist trotzdem recht universell einsetzbar.
    Was soll der Begriff "universell einsetzbar" überhaupt? Eine Klasse ist eine Sammlung von Algorithmen um eine ganz bestimmte Aufgabe zu erfüllen. Du verlangst ja auch nicht von einem Auto das es fliegt oder ?

    Der Header ist kleiner und damit übersichtlicher,

    das ist einzig nur ein Nachteil von C++. Header Dateien trennen nunmal nicht das Interface von der Implementation. Im Idealfall hast du nur ein Interface zur verfügung, in dem nur public Methoden auftauchen. Das ist auch eine Idee bei OOP. Die Trennung von unwichtigeren Sachen (implementation) von den wichtigen (interface).

    wodurch die Dokumentation ebenfalls kürzer ausfällt und diese Funktionen nicht in der Benutzerdoku auftauchen.

    seit wann tauchen private Methoden in der Docu auf? Wenn es bei so ist, dann frag ich mich welcher Depp private Methoden dokumentiert. Private Methoden sollten dem User der Klasse überhaupt nie sichtbar sein.



  • .filmor schrieb:

    Und was hast du davon? Es bringt dir nichts, zu wissen, wie sortiert wird (es sei denn, du weißt wie das Feld in etwa aussieht, aber das sollte sehr selten sein). Die Sortierrichtung gibt man mittels eines Funktors (der auch nur die ver-OOP-te (und "mächtigere") Variante der Funktion ist) oder per Iterator-Art an. Das ganze ist deutlich flexibler und sinnvoller. Man einigt sich auf Konventionen (in diesem Fall den Standard), die bestimmte Eckdaten bereitstellen (z.B. Laufzeit O(n*log(n))). Hauptsache ist, der Container (nicht nur Arrays!) ist nachher nach den eingegebenen Kriterien sortiert.

    kann ja schön und gut sein. Mein Beispiel war aber eine Kontex-Freie Funktion namens sort(bla[] a). Nix von Funktors und ähnliches. Es will ein Array haben des Types bla. Dieses Array sortiert, zumidest den Namen nach, die Funktion, irgendwie. Diese Funktion kann von sonstwo herkommen, auch von Mars.
    Bei Quicksort.sort(bla[] a) sehe ich aber auf den ersten Blick das sie im Kontex von Quicksort steht. Ich kann sofort die Docu aufrufen für die Klasse Quicksort und nachschauen wie sie funktioniert. Das ist nunmal der Vorteil.

    und von Java, C# und co. übernommen und verbessert.

    Das halte ich für ein Gerücht.

    Die Parallelen sind aber offensichtlich. Du vergleichst ja auch die packages von Java mit C++ namespaces.

    Klassen als reine Namespaces zu missbrauchen ist nicht sinnvoll.

    wieso bitte den? Es verhält sich zu min 100% wie ein Namespace.

    Die im richtigen Namespace. Der globale Namespace wird nicht (ben|verschm)utzt. Selbst C-Leute benutzen (meist) ein Präfix, also Lightnamespaces.

    C++ schreibt dir aber nicht vor Namespaces zu benutzen. Man kann sie benutzen. Was wenn die Programmierer zu faul waren Namespaces zu benutzen? Eine reine OO-Programmiersprache schreibt dir aber vor Klassen zu benutzen und verbietet dir globale Funktionen. 👍



  • DEvent schrieb:

    Was soll der Begriff "universell einsetzbar" überhaupt? Eine Klasse ist eine Sammlung von Algorithmen um eine ganz bestimmte Aufgabe zu erfüllen. Du verlangst ja auch nicht von einem Auto das es fliegt oder ?

    Eine Klasse ist _keine_ Sammlung von Algorithmen. Eine Klasse ist der Typ eines Objektes, das eine Aufgabe erfüllt oder Daten kapselt. Algorithmen sind völlig allgemeine Vorgehensweisen, die somit auch allgemein anwendbar sein sollten.[/quote]

    DEvent schrieb:

    das ist einzig nur ein Nachteil von C++. Header Dateien trennen nunmal nicht das Interface von der Implementation. Im Idealfall hast du nur ein Interface zur verfügung, in dem nur public Methoden auftauchen. Das ist auch eine Idee bei OOP. Die Trennung von unwichtigeren Sachen (implementation) von den wichtigen (interface).

    Das mit dem wichtig und unwichtig ist absolut abhängig von der Perspektive. Was du meinen könntest ist die Kapselung und das Verstecken von Details.
    Deine Einstellung zu den Headern teile ich allerdings, die sind nicht schön (vor allem solche nervigen Sachen wie Include-Guards, die einen dazu zwingen den Präprozessor zu benutzen wo er eigentlich nicht benötigt werden sollte). Mit den Modulen wird alles besser 😉

    DEvent schrieb:

    kann ja schön und gut sein. Mein Beispiel war aber eine Kontex-Freie Funktion namens sort(bla[] a). Nix von Funktors und ähnliches. Es will ein Array haben des Types bla. Dieses Array sortiert, zumidest den Namen nach, die Funktion, irgendwie. Diese Funktion kann von sonstwo herkommen, auch von Mars.
    Bei Quicksort.sort(bla[] a) sehe ich aber auf den ersten Blick das sie im Kontex von Quicksort steht. Ich kann sofort die Docu aufrufen für die Klasse Quicksort und nachschauen wie sie funktioniert. Das ist nunmal der Vorteil.

    Es ist mir vollkommen EGAL, wie sortiert wird (s. ursprünglicher Post), es könnte von mir aus auch BubbleSort sein, wenn das die Voraussetzungen erfüllen würde.
    Die Sprache kann außerdem nicht für Designfehler verantwortlich gemacht werden (und das wäre eine derartige Funktion). Ich gehe von der (sehr mächtigen) std::sort Funktion aus. Und gute Dokus zur STL gibts auch (z.B. sgi.com).

    DEvent schrieb:

    wieso bitte den? Es verhält sich zu min 100% wie ein Namespace.

    Was sagt den Java, wenn ich ein Objekt von dem "Namespace" erstellen will? Oder wenn ich an anderer Stelle noch etwas hinzufügen will, weil es dort besser passt?!

    DEvent schrieb:

    C++ schreibt dir aber nicht vor Namespaces zu benutzen. Man kann sie benutzen. Was wenn die Programmierer zu faul waren Namespaces zu benutzen? Eine reine OO-Programmiersprache schreibt dir aber vor Klassen zu benutzen und verbietet dir globale Funktionen. 👍

    Die Programmiersprache hat sich um ihren eigenen Kram zu kümmern, ich lasse mir nichts vorschreiben :p. Alle Entscheidungen, die man trifft, sollten sich sinnvoll ergeben. Im übrigen sind faule Programmierer immer schlecht, siehe oben zu Programmierfehlern.



  • Unter einem Algorithmus versteht man allgemein eine genau definierte Handlungsvorschrift zur Lösung eines Problems oder einer bestimmten Art von Problemen.

    Zwischen allgemein und universell einsetzbar ist ein ziemlich großer Unterschied.

    Eine Klasse ist der Typ eines Objektes, das eine Aufgabe erfüllt oder Daten kapselt.

    Damit ein Objekt Aufgaben erfüllen kann muss die Klasse zu der das Objekt gehört Algorithmen haben, die diese Aufgaben erfüllen. Somit ist eine Klasse eine Sammlung von Daten und Algorithmen die diese Daten verwenden.
    Ohne Algorithmen kann eine Klasse nur Daten kapseln.

    Was sagt den Java, wenn ich ein Objekt von dem "Namespace" erstellen will?

    Es gibt einen Compiler-Fehler.
    Zum Beispiel:

    public final class Math
    {
        public static double sin(double x)
        { 
            // .... 
        }
    
        private Math() { }
    }
    

    wenn ich von der Klasse Math versuche ein Object zu erzeugen gibt es einen Compiler-Fehler, weil der Kontruktor nicht public ist. Das final kann man sich auch sparen, dann wäre die Klasse auch erweiterbar.

    Deine Einstellung zu den Headern teile ich allerdings, die sind nicht schön

    einer der vielen Gründe wieso ich mich von C++ zu gunsten von C# und Java verabschiedet habe. Header nerven tierisch in C++, ich hoffe im nächsten Standart werden sie überarbeitet.



  • DEvent schrieb:

    Bei Quicksort.sort(bla[] a) sehe ich aber auf den ersten Blick das sie im Kontex von Quicksort steht.

    Bei quicksort(bla[] a) hingegen, ist das natürlich sehr viel schwieriger zu erkennen.

    Und vielleicht noch zu dem Hinweis mit dem Strategy-Pattern ganz am Anfang. std::sort ist doch genau das Strategy-Pattern. Es ist an der Stelle nur nicht Laufzeit-dynamisch, sondern wird zur Compile-Zeit festgelegt.



  • Jester schrieb:

    Und vielleicht noch zu dem Hinweis mit dem Strategy-Pattern ganz am Anfang. std::sort ist doch genau das Strategy-Pattern. Es ist an der Stelle nur nicht Laufzeit-dynamisch, sondern wird zur Compile-Zeit festgelegt.

    Ja, aber die objektorientierte Lösung ist std::sort nicht, da C++, Java, usw. Funktionen bzw. Funktionstemplates nicht als Objekte behandeln. Wie gesagt, Quicksort.sort(...) ist auch keine objektorientierte Lösung und in dem Sinne ist der Java-Weg sicherlich nicht "besser".



  • Die STL ist doch nach einem ganz anderen Prinzip entworfen worden, hier werden Daten und Algorithmen getrennt und bei OOP versucht man das ja zu vereinen.
    Von daher finde ich die ganze Diskussion ist einfach fehl am Platz, nen Apfel ist nunmal keine Birne.

    Ich finde dieses Design einfach genial, aber dass OOP-Dogmatiker das nicht nachvollziehen können, dürfte jedem klar sein.


Anmelden zum Antworten