Hallo,
ich möchte eine Liste von unterschiedlichen Funktionen erstellen dessen
Auswahl aber erst zur Laufzeit bestimmt wird und jeweils dazugehörige
beständige Daten haben. Dann sollen alle gewählten Funktionen
Event-gesteuert möglichst performant aufgerufen werden.
Derzeit stecken die Funktionen mit den dazugehörigen Daten zusammen in
einer Klasse.
Nun habe ich das per virtueller Methode mit abgeleiteter Klasse gemacht
nach diesem Beispiel
(https://de.wikibooks.org/wiki/C%2B%2B-Programmierung/_Objektorientierte_Programmierung/_Virtuelle_Methoden)
Das Prinzip funktioniert zwar so aber es ist bei einer größeren
Funktionsliste sehr langsam daher suche ich eine Lösung die maximal
performant ist. Virtuelle Funktionen haben leider viele cycles mehr als
"normale" Aufrufe. Gibt es vielleicht ein Ansatz der besser oder mehr
"Low Level" ist und damit schneller?
Gruß
Funktions Zeiger sind vielleicht noch 2-3 Takte schneller. Die Ausgabe
auf die Konsole sowie die Allocation ist aber sowieso um viele
Größenordnungen langsamer.
NeedSpeed schrieb:> Virtuelle Funktionen haben leider viele cycles mehr als "normale"> Aufrufe.
Nur am Anfang. Wenn die CPU die vtable erstmal im Cache hat ist der
Unterschied sehr gering.
Die Ausgabe auf Konsole ist nur von dem Beispiel. Was schnell gehen soll
ist ja am Ende die Ausführung for-Schleife.
Wie würde das mit Funktionszeigern funktionieren ich bin bisher daran
gescheitert unterschiedliche Klassen in ein Zeiger-Vector zu bekommen da
es ja unterschiedliche Klassen sind mit dazugehörigen Daten (im obigen
Beispiel nicht zu sehen).
Wofür muss das Ganze denn so High-Perfomance sein, ich denke mal, du
schreibst keinen Hochleistungscode für Computertomographen, oder?
Und ansonsten kann ich meinem Vorredner eigentlich nur zustimmen, so
eine Konsolenausgabe braucht um Größenordnungen mehr Takte, es macht
eigentlich nur Sinn 2-3 Takte zu sparen, wenn die Funktion viele
Milliarden mal aufgerufen wird, und das bezweifle ich etwas.
Gruß Ivo
Ach ja, wenn es wirklich High-Perfomance sein soll, würde ich auch nicht
inC++, sondern in Assembler programmieren, da sparst du so einige Takte.
Gruß Ivo
Man könnte ggf auch die Funktionen von den Daten seperieren und die
nackten Funktionen in ein low-level Funktionszeiger-vector geben. Dann
müsste man aber die dazugehörigen Daten (als Struct) noch beim
Funktionsaufruf als Referenz übergeben, die Structs wäre allerdings ja
je nach Funktion unterschiedlich und man hätte auch das Ding mit den
unterschiedlichen Datentypen...
Ivo Z. schrieb:> Wofür muss das Ganze denn so High-Perfomance sein, ich denke mal,> du> schreibst keinen Hochleistungscode für Computertomographen, oder?> Und ansonsten kann ich meinem Vorredner eigentlich nur zustimmen, so> eine Konsolenausgabe braucht um Größenordnungen mehr Takte, es macht> eigentlich nur Sinn 2-3 Takte zu sparen, wenn die Funktion viele> Milliarden mal aufgerufen wird, und das bezweifle ich etwas.>> Gruß Ivo
Es kann bis zu 100000 Funktionsaufrufe per Event geben, das es nicht
schnell ist ist spürbar. Die Milliarden sind unter Umständen nach einer
gewissen Anzahl von Events erreicht. Ein ppar Takte mehr würde sich
daher lohnen.
Wie gesagt die Konsolenausgabe war nur Teil des Beispiels die existiert
dann im entgültigen Code natürlich nicht.
NeedSpeed schrieb:> Wie gesagt die Konsolenausgabe war nur Teil des Beispiels die existiert> dann im entgültigen Code natürlich nicht.
Sorry, das hattest du gepostet, während ich meinen Beitrag schrieb, hab
das deshalb erst später gesehen...
Aber um zum Thema zurückzukommen, wie gesagt, mit Assembler wirst du
nochmal um einiges schneller, würde ich mir an deiner Stelle also
überlegen.
Ivo
Ivo Z. schrieb:> Aber um zum Thema zurückzukommen, wie gesagt, mit Assembler wirst du> nochmal um einiges schneller, würde ich mir an deiner Stelle also> überlegen.
Meine Fähigkeiten beschänken sich leider nur auf C++ :-)
Es muss ja noch andere Wege geben als über die abgeleiteten Klassen
sonst könnten man zB in C sowas gar nicht machen.
Fang doch erstmal damit an einen Profiler zu benutzen. Erst wenn der
zweifelsfrei belegt dass der virtuelle Funktionsaufruf tatsächlich das
Bottleneck ist (was ich bezweifle) solltest du versuchen daran herum zu
doktorn.
NeedSpeed schrieb:> sonst könnten man zB in C sowas gar nicht machen.
Das macht man da mit Funktionszeigern. Zusammen mit der
Laufzeit-Unterscheidung die du ja brauchst ist das auch nicht schneller
als virtuelle Funktionen. Virtuelle und normale Funktionsaufrufe sind in
C und C++ allgemein sehr effizient, im Gegensatz zu z.B. Java. 1-2
ungeschickte if's oder ungünstige Daten Strukturen können da viel
schlimmer sein (Branch Prediction und Caches). Überleg mal die Funktion
nur 1x aufzurufen und alle Daten auf einmal zu übergeben.
NeedSpeed schrieb:> Es kann bis zu 100000 Funktionsaufrufe per Event geben,
Das sagt gar nix aus so lange du nicht sagst wie oft ein Event eintritt
und wie schnell die Reaktion sein soll.
braucht wirklich das iterieren so lange oder das Elemente anfügen? Wenn
der vector ständig vergrössert wird kostet das einiges an Performance.
Mit reserve(count) könnte man das unterdrücken.
Guck z.B. mal das an:
https://stackoverflow.com/q/11227809/4730685
Es gibt noch ganz andere Performance Bremsen als Funktions Aufrufe...
aber ohne mehr Informationen dazu was da genau passiert kann man da
nicht viel zu sagen.
Dr. Sommer schrieb:> Es gibt noch ganz andere Performance Bremsen als Funktions Aufrufe...> aber ohne mehr Informationen dazu was da genau passiert kann man da> nicht viel zu sagen.
Genau, Programme kann man eigentlich immer schneller machen, oft sind es
sogar sehr grundlegene Schwächen, wie z.B. dauerndes Elemte zu Arrays
hinzufügen oder ähnliches.
Du müsstest uns mal die Aufgabe des Codes und den groben Ablauf des
Programms (wenn möglich vielleicht sogar als Flussdiagramm) geben, dann
können wir dir besser helfen.
Gruß Ivo
Vielleicht eine von einer Million Anwendungen lässt sich am
effizientesten durch die Verwendung von Assembler statt C++ schneller
machen. In allen anderen Fällen ist das Problem woanders. Was willst du
machen? Hast du einen Profiler benutzt und ist das mit der vtable
tatsächlich das Problem?
Keine Ahnung ob's schneller wäre, aber :
Du könntest Dir bei der Initialisierung (zur Laufzeit) ein Array mit
Funktionszeigern gemäß der Auswahl Deiner Funktionen erstellen.
In Deiner Datenklasse hinterlegst Du dann (möglichst als
Klassenvariable) den Index für die zugehörige Funktion.
Kommt ein Event, dann liest Du aus Deiner Datenklasse diesen
Funktionsindex.
Damit holst Du Dir den Funktionspointer aus dem Array und rufst die
Funktion auf.
Also im Prinzip eine dynamisch erstellte Abbildungstabelle von Klasse
auf Funktion.
Ob's Performance bringt? achselzuck
Sven B. schrieb:> Vielleicht eine von einer Million Anwendungen lässt sich am> effizientesten durch die Verwendung von Assembler statt C++ schneller> machen.
Wir reden IMHO nicht von einer ganzen Anwendung, sondern von einer recht
konkreten Funktion. Da kann sich etwas Assembler durchaus lohnen - aber
ein durchschnittlicher Programmierer ist auf X86 nicht mehr unbedingt
besser als ein guter C Compiler.
Vorsicht: Oftmals ist es deutlich besser am Algorithmus zu arbeiten und
den Code in C/C++ zu belassen. Bei sehr hohen Event Frequenzen lohnt es
sich z.B. Events zu sammeln und dann am Stück zu verarbeiten.
Dr. Sommer schrieb:> NeedSpeed schrieb:>> Es kann bis zu 100000 Funktionsaufrufe per Event geben,>> Das sagt gar nix aus so lange du nicht sagst wie oft ein Event eintritt> und wie schnell die Reaktion sein soll.
Wahrscheinlich nudelt er alle 100000 Funktionen dann durch und genau
eine davon kann mit dem Event wirklich was anfangen.
Clever wäre, wenn man vorher weis welcher Event welche Funktionen
aufrufen muss.
Ich gehe hier fast ne Wette ein, daß das Performanceproblem eine ganz
andere Lösung hat als die die der TO sich hier denkt.
Sven B. schrieb:> Was willst du denn überhaupt machen?
Dies.
@NeedSpeed:
Beschreibe uns mal Deine Anforderungen auf vernünftige Art und Weise.
Das Problem liegt recht wahrscheinlich nicht da, wo Du es vermutest.
Ich gehe davon aus, dass das langsamste der cout und delete sind (nicht
unbedingt in dieser Reihenfolge).
Zudem: delete durch die Baseclass ohne virtual desctructir ist UB.
Wenn man viele Objekte hat und mit diesen effizient etwas machen will,
sollte man sie nicht alle in einen dummen Container (tiere) stecken und
die Entscheidungen (per vtable) auf jeden einzelnen Aufruf verlagern.
Wenn der Container weiß, was in ihm steckt, kann die Iteration über alle
Elemente sinnvoll sortiert und effizient im Container erfolgen.
Mal eine Spielerei ohne virtuelle Methoden (statt dessen duck typing),
bei der die Objekte cache-günstig in einem vector je Typ liegen und die
Ess-Methoden je Typ nacheinander aufgerufen werden:
https://ideone.com/F1lmQY
Nachtrag: Wenn es nicht eine übersichtliche Anzahl von Tierarten gibt,
von denen es jeweils viele Exemplare existieren, sondern sehr viele
Tierarten mit jeweils wenigen Vertretern, ist das Template-Gebastel in
dieser Form nicht sehr sinnvoll.
Dr. Sommer schrieb:> Fang doch erstmal damit an einen Profiler zu benutzen.
Genau das ist hier der einzige Korrekte Ratschlag (tm). Im Bereich der
Performanceoptimierung gibt es eigentlich nur zwei Leitsätze, die man
verstehen muß:
"Premature optimization is the root of all evil." (Donald E. Knuth)
Standardrezepte, die auf Annahmen des Entwicklers basieren,
funktionieren nicht. Im Zweifelsfalle macht der Compiler aus dem les-
und wartbaren Code etwas viel Effizienteres als aus dem Code, den der
Entwickler damals für besonders effizient gehalten hat.
"Measure, don't guess." (Kirk Pepperdine)
Einfach so ins Blaue optimieren funktioniert auch nicht. Ohne gemessen
zu haben kann niemand sagen, welche Codeteile die meiste Zeit
verbrauchen, wo genau sich eine Optimierung also überhaupt lohnt. Es
nutzt nämlich nichts, eine Funktion zu optimieren, die in Summe der
Aufrufe nur ein Millionstel der Laufzeit des Programms beansprucht.
Es nutzt auch nichts, irgendwelchen Beispielcode zu optimieren. Im
Zweifel macht der Compiler aus dem Produktionscode nämlich etwas ganz
anderes, und dann war die ganze schöne Optimierung für die Katz'.
Um aber neben allem, was ich für flashc halte, auch noch eine neue Idee
beizutragen, die ich für einen besseren Ansatz halte, möchte ich darauf
hinweisen, daß C++ die Möglichkeit bietet, Operatoren zu überladen. Als
Äquivalent eines Funktionszeigers läßt sich also einfach der ()-Operator
überladen:
Höchstwahrscheinlich performt die Sache noch besser, wenn man anstatt
der Lebewesen-Objekte in den Vector zu kopieren, mit Pointern arbeitet
-- aber das hat dann nichts mehr mit der Loop über die Aufrufe zu tun,
sondern mit dem Befüttern des Vector. This is left as an excercise to
the reader. ;-)
Das Hauptproblem hier ist in meinen Augen immer noch, dass diese "wie
löse ich X elegant" oder "wie mache ich Y schneller"-Fragestellungen
nicht anhand des Lehrbuch-Beispiels mit dem Hund der "ich belle" printft
beantwortbar sind. Das geht immer nur fünftausend Beiträge hin und her
zwischen Lösungen die irgendwelchen wirr aus der Luft gegriffenen
Anforderungen genügen und führt zu gar nichts. Das gilt übrigens meines
Erachtens für quasi jede Diskussion, die anhand dieses oder eines
ähnlichen Beispiels geführt wird.
Um diese Frage gewinnbringend voranzubringen, muss man den konkreten
Anwendungsfall kennen. Und einen Profiler benutzen.
Die ganze Diskussion ist müssig. Der Aufgabenstellung nach wird ein
Funktionsaufruf gebraucht, der erst zur Laufzeit aus dem Typ der Instanz
bestimmt werden kann.
Ob man das jetzt als virtuelle Funktionen implementiert( die genau dafür
da sind), als Funktionspointer, über Templates, oder in direkt
Assembler, am Ende läuft das auf einen indirekten Sprung über einen aus
einer Tabelle zu suchenden Pointer hinaus. Da kann man sich
totoptimieren, besser wirds nicht.
Ich würde da bei den virtuellen Funktionen bleiben. Da kann man davon
ausgehen, daß die vom Compiler optimal umgesetzt werden.
Besser würde es nur, wenn schon zur Compilezeit Informationen über die
aufzurufende Funktion existieren. Das aber gibt die Aufgabenstellung im
Ausgangsbeitrag nicht her.
Oliver
Oliver S. schrieb:> am Ende läuft das auf einen indirekten Sprung über einen aus> einer Tabelle zu suchenden Pointer hinaus. Da kann man sich> totoptimieren, besser wirds nicht
Genau diese Suche kann man aber optimieren bzw im Vorfeld lösen, wenn
die Aufgabenstellung das her gibt. Und hier sieht es so aus als gäbe sie
es her.
Sheeva P. schrieb:> Als> Äquivalent eines Funktionszeigers läßt sich also einfach der ()-Operator> überladen:
In C++ sind operatoren nix anderes, als Funktionen mit "lustigen" Namen.
Der Funktions-Operator spielt da überhaupt keine besondere Rolle (ausser
syntaktisch) und der Aufruf wird exakt genauso zur Compiler-Zeit
aufgelöst, wie der Aufruf jeder anderen, nicht virtuellen Funktion. Und
ist damit genauso wenig als Funktionszeiger geeignet.
Tom schrieb:> Fürs Archiv, falls ideone das löscht.
Damit sind dann aber alle Entscheidungen bereits zur Compile-Zeit
getroffen. ("...aber erst zur Laufzeit...")
Der Container-per-Type Ansatz ist schon eine gute Optimierung, aber auch
ich bezweifle erst einmal, dass hier überhaupt der Bottleneck liegt. Wir
wissen mal wieder nicht über Messungen, und ich vermute jetzt einfach
mal, dass der TO den new/delete-Operator in seine Laufzeit-Aussage mit
einbezogen hat ...
Vielen Dank für die Antworten und Beispiele!
Ihr habt natürlich recht, ich werde am Wochenende mich mal darauf
konzentrieren konkret Zeiten zu messen und so Varianten zu prüfen.
Hatte eben schon öfters gelesen, dass virtuelle Funktionen "langsam"
sind daher die Idee nach möglichen Alternative zu gucken. Aber es stimmt
schon, durch die benötigte Flexibilität wird es vermutlich sowieso bei
jeder Variante die leider notwendigen Indirektionen geben.
Total sinnlose Diskussion solange der Fragende nicht schreibt was er
konkret machen will und was er mir "langsam" meint. std::cout ist in
jedem Fall langsam.
@NeedSpeed:
Zeig uns doch mal einen CodeBlock der Anwendungsnah ist und den dazu
passenden Profiler-Output. Das was Du gezeigt hast, ist mit Sicherheit
langsam, weil Du in der Schleife den delete Aufruf hast. Und cout ist
sowieso lahm - zumal mit flush nach jeder Ausgabe.
NeedSpeed schrieb:> Hatte eben schon öfters gelesen, dass virtuelle Funktionen "langsam"> sind daher die Idee nach möglichen Alternative zu guck
Das ist ein Mythos (typischerweise propagiert von C-Programmieren). Alle
Alternativen mit gleicher(!) Funktionalität, d.h.
Laufzeit-Unterscheidung (Polymorphie), sind genauso "langsam". Schau dir
einfach mal an wie das kompiliert wird. Typischerweise kommen da einfach
nur 1-2 "Load"/"Move" Instruktionen vor dem "Call". Sobald die vtable im
Cache ist geht das schnell - ist auf gleicher Größenordnung wie das
Zuweisen einer Integer-Variable.
Ivo Z. schrieb:> Ach ja, wenn es wirklich High-Perfomance sein soll, würde ich auch> nicht> inC++, sondern in Assembler programmieren, da sparst du so einige Takte.>> Gruß Ivo
Unsinn. Macht alles nur unübersichtlich und heutige CPU sind einfach zu
kompliziert um hier in Assemblersprache noch optimieren zu können. Du
weißt ja nicht mal wie die CPU das ausführt, denn die CPU optimiert hier
selbst nochmal. Wie willst du dann konkret optimieren können? Die
Heuristiken der CPU zerlegen dir das eh alles und du hast nichts
gewonnen. Man kann schon auf Assemblercode gezielt setzen wenn man z.B.
eine Funktion für C++ mit embedded ASM schreibt in der man z.B. die
Reihenfolge der Bit per ASM Befehl in einem Zyklus oder was optimiert.
Aber grundsätzlich die ganze Anwendung in ASM deswegen zu schreiben ist
zu viel des guten, moderne Compiler sind dafür einfach zu gut. Kein
Mensch bezahlt dir die Arbeitszeit.
C++14 bzw. C++17, dazu libstdc++ mit parallel Mode und openmp und -O2
bzw. -O3 mit g++ >=7.3, da sieht dein ASM Code alt dagegen aus, allein
schon die Zeit die man für das Programmieren benötigt fällt erheblich
geringer aus, zudem kannst du in ASM nie auch nur annähernd so
weitsichtig wie der g++ optimieren.
Und solang da noch irgendwo in Funktionen ein std::cout steht welches
häufig aufgerufen wird brauchen wir gar nicht über Optimierungen
sprechen. Die Konsole ist dafür einfach zu langsam.
http://en.cppreference.com/w/cpp/chrono
Miss welche Funktion wieviel Zeit braucht und mit z.B. Cachegrind usw.
wieviel L1 Misses usw. du hast. Darauf optimiert man bei HPC
Anwendungen, denn mal will konstant den L1 nutzen, alles andere ist zu
langsam. Und lass dich auch nicht zu zu vielen Flags beim Compiler
verleiten, manche sind einfach nicht sinnvoll. Genauso Parallelisierung,
ich habe oftmals Bottlenecks hinsichtlich RAM<->CPU, da ist ein Thread
in der Regel schneller als 12.
Eclipse hat tolle Einbindungen für Valgrind/Oprofile usw., da bekommt
man schön alles visualisiert. kcachegrind und der massif Visualizer sind
auch nett.
Torsten R. schrieb:> Sheeva P. schrieb:>> Als>> Äquivalent eines Funktionszeigers läßt sich also einfach der ()-Operator>> überladen:>> In C++ sind operatoren nix anderes, als Funktionen mit "lustigen" Namen.
<klugscheiß>Es sind Methoden mit lustigen Namen.</klugscheiß>
> Der Funktions-Operator spielt da überhaupt keine besondere Rolle (ausser> syntaktisch) und der Aufruf wird exakt genauso zur Compiler-Zeit> aufgelöst, wie der Aufruf jeder anderen, nicht virtuellen Funktion. Und> ist damit genauso wenig als Funktionszeiger geeignet.
Ja, in meinem Beispiel. Aber Du kannst ja stattdessen auch diese main()
benutzen (das Inkludieren von cstdlib für std::exit() und "std::string&
getName(void) { return wer; }" in der Klasse Lebewesen nicht vergessen):
Dabei wird zur Laufzeit ermittelt, welches Objekt aufgerufen wird, und
jedes Objekt hält seine eigenen Daten, wie vom TO gefordert.
Nein, damit wird der eigentliche Aufruf nicht schneller (wie auch). Aber
solange der TO sich weigert, seinen konkreten Anwendungsfall zu
erklären, werde ich mich weigern, etwas blind ins Blaue zu optimieren,
von dem ich nicht einmal weiß, ob es sich überhaupt lohnen würde. ;-)
Sheeva P. schrieb:> <klugscheiß>Es sind Methoden mit lustigen Namen.</klugscheiß>
Nö, Funktionen bzw. Member-Funktionen ist richtig. "Methoden" gibt's nur
in Java. Im C++-Standard taucht das Wort "Method" nicht auf.
In C++ sind Operatoren aber auch nicht 100% das Gleiche wie Funktionen.
Man kann zwar eine Funktion foo(int, int) definieren, aber keinen
operator + (int, int). Operatoren werden syntaktisch anders behandelt.
In ruby z.B. ist das anders, da sind überladene Operatoren wirklich nur
komisch benannte Funktionen. Da ist "a+b" oder "a.+b" oder "a.+(b)"
alles das Gleiche.
Sheeva P. schrieb:> for(int i = 0; i < lv.size(); ++i) {
Für Indices sollte man std::size_t und nicht int verwenden.
Sheeva P. schrieb:> std::exit(0);
Warum nicht einfach "return", oder exit() ganz weglassen?
Sheeva P. schrieb:> } catch(std::exception e) {
Exceptions sollte man via "const std::exception&" fangen, um Slicing und
Kopieren zu vermeiden.
NeedSpeed schrieb:> Ihr habt natürlich recht, ich werde am Wochenende mich mal darauf> konzentrieren konkret Zeiten zu messen und so Varianten zu prüfen.> Hatte eben schon öfters gelesen, dass virtuelle Funktionen "langsam"> sind daher die Idee nach möglichen Alternative zu gucken.
Klara Fall von "premature optimization": Du willst etwas optimieren,
ohne überhaupt zu wissen, ob das tatsächlich der Bottleneck ist. Und das
nicht einmal, weil Du etwas aus eigener Erfahrung und für Deinen
Anwendungsfall weißt oder gemessen hast, sondern, weil Du irgendwo mal
irgendeinen Unfug "öfters gelesen" hast. Obendrein sagst Du genau nichts
über den konkreten Anwendungsfall, sondern speist Deine Befragten mit
einem Beispielcode ab, welcher unsinniger kaum sein könnte -- think
std::cout.
Sei mir nicht böse, aber Du solltest an Deinen Strategien arbeiten.
Sheeva P. schrieb:> think std::cout.
3 mal hat der TO schon selber geschrieben das dies nur als Beispiel
genannt war - warum reitet ihr immer noch darauf rum?
Dr. Sommer schrieb:> Sheeva P. schrieb:>> <klugscheiß>Es sind Methoden mit lustigen Namen.</klugscheiß>>> Nö, Funktionen bzw. Member-Funktionen ist richtig.
Operatoren sind "member functions"? Tatsächlich?
> "Methoden" gibt's nur in Java.
Das solltest Du mal
1. den TurboPascal-Leute und
2. der Wikipädie
sagen.
> Im C++-Standard taucht das Wort "Method" nicht auf.
Da taucht ganz sicher auch das Wort "Vernunft" oder
das Wort "Intelligenz" nicht auf, und das Wort "Turing-
maschine" auch nicht.
Dr. Sommer schrieb:> Sheeva P. schrieb:>> <klugscheiß>Es sind Methoden mit lustigen Namen.</klugscheiß>>> Nö, Funktionen bzw. Member-Funktionen ist richtig. "Methoden" gibt's nur> in Java. Im C++-Standard taucht das Wort "Method" nicht auf.
Eine Funktion, die an ein Objekt gebunden ist, nennt man Methode.
> Sheeva P. schrieb:>> std::exit(0);> Warum nicht einfach "return", oder exit() ganz weglassen?
Obvious.
Johannes S. schrieb:> Sheeva P. schrieb:>> think std::cout.>> 3 mal hat der TO schon selber geschrieben das dies nur als Beispiel> genannt war - warum reitet ihr immer noch darauf rum?
Weil $OP es immer noch nicht geschafft hat, den Anwendungsfall zu
erklären.
Dr. Sommer schrieb:> In C++ sind Operatoren aber auch nicht 100% das Gleiche wie Funktionen.> Man kann zwar eine Funktion foo(int, int) definieren, aber keinen> operator + (int, int).
Das liegt aber nur daran, dass eine Bedeutung von a+b für int bereits
vordefiniert ist und C++ es (aus gutem Grund) verbietet, diese zu
ändern.
> In ruby z.B. ist das anders, da sind überladene Operatoren wirklich nur> komisch benannte Funktionen. Da ist "a+b" oder "a.+b" oder "a.+(b)"> alles das Gleiche.
In C++ geht das auch, aber man muss a.operator+(b) schreiben, bzw. wenn
er nicht als Member definiert ist, operator+(a, b). Schließlich heißt
die Funktion dort ja operator+, und nicht nur +.
> Sheeva P. schrieb:>> for(int i = 0; i < lv.size(); ++i) {> Für Indices sollte man std::size_t und nicht int verwenden.
Ja, auf jeden Fall. Und das nicht nur, um die Warnung über einen
Vergleich zwischen signed und unsigned wegzubekommen.
Sheeva P. schrieb:> Dr. Sommer schrieb:>> Sheeva P. schrieb:>>> <klugscheiß>Es sind Methoden mit lustigen Namen.</klugscheiß>>>>> Nö, Funktionen bzw. Member-Funktionen ist richtig. "Methoden" gibt's nur>> in Java. Im C++-Standard taucht das Wort "Method" nicht auf.>> Eine Funktion, die an ein Objekt gebunden ist, nennt man Methode.
Der Erfinder von C++ sieht das anders. In seinem Buch steht zum Thema
"Methoden" nur ein einziger Satz:
"A virtual member function is sometimes called a method."
Für ihn sind also nur virtuelle Memberfunktionen Methoden, und es ist im
C++-Kontext auch nicht der Standard-Begriff, sondern wird nur manchmal
als Alternative verwendet.
Possetitjel schrieb:> Dr. Sommer schrieb:>>> Sheeva P. schrieb:>>> <klugscheiß>Es sind Methoden mit lustigen Namen.</klugscheiß>>>>> Nö, Funktionen bzw. Member-Funktionen ist richtig.>> Operatoren sind "member functions"? Tatsächlich?
Sie können es sein. Sie können aber auch freie Funktionen sein.
Johannes S. schrieb:> Sheeva P. schrieb:>> think std::cout.>> 3 mal hat der TO schon selber geschrieben das dies nur als Beispiel> genannt war - warum reitet ihr immer noch darauf rum?
Weil man Beispielcode nicht optimieren kann, und dieser Thread schon 100
Beiträge Zeit damit verschwendet es trotzdem zu tun!
Rolf M. schrieb:>> Operatoren sind "member functions"? Tatsächlich?>> Sie können es sein. Sie können aber auch freie> Funktionen sein.
Okay... sachlich verständlich, aber sprachlich
grausam.
In meinem Universum sind Operatoren keine Funktionen,
genauswenig, wie Skalare Vektoren sind.
Hallöchen,
können wir nicht mit unseren Diskussionen solange warten, bis der
Threadersteller endlich mal mit Informationen rausgerückt hat, vorher
macht das alles hier keinen Sinn, oder?
Gruß Ivo
Possetitjel schrieb:> In meinem Universum sind Operatoren keine Funktionen, genauswenig, wie> Skalare Vektoren sind.
Dann ist dein Universum halt nicht konform zum Standard, dem sonst jeder
folgt. Ist für dich die 0 keine Zahl oder ein Moped kein Kraftfahrzeug?
Es gibt eine glasklare Definition für Vektorräume, und die Elemente
heißen Vektoren. Die Mengen Q und R erfüllen diese Definition, und somit
sind Skalare Vektoren. Es gibt eine Menge Sätze und Beweise die für alle
Vektoren gelten, und somit auch für Skalare. Würdest du diese Beweise
noch mal extra für Skalare durchführen weil die gefühlt keine Vektoren
sind?
Possetitjel schrieb:> Rolf M. schrieb:>>>> Operatoren sind "member functions"? Tatsächlich?>>>> Sie können es sein. Sie können aber auch freie>> Funktionen sein.>> Okay... sachlich verständlich, aber sprachlich> grausam.
Sagen wir mal so: Die Implementation eines überladenen Operators ist
eine Funktion. Warum sollte man das auch anders definieren? Das würde es
nur unnötig verkomplizieren.
Rolf M. schrieb:>> Sheeva P. schrieb:>>> for(int i = 0; i < lv.size(); ++i) {>> Für Indices sollte man std::size_t und nicht int verwenden.>> Ja, auf jeden Fall. Und das nicht nur, um die Warnung über einen> Vergleich zwischen signed und unsigned wegzubekommen.
Oh ja, stimmt natürlich. Mea maxima culpa.
Torsten R. schrieb:> Tom schrieb:>> Fürs Archiv, falls ideone das löscht.>> Damit sind dann aber alle Entscheidungen bereits zur Compile-Zeit> getroffen. ("...aber erst zur Laufzeit...")
In beiden Fällen wird die Entscheidung de facto getroffen, wenn man das
Tier in die Liste schreibt. Einen prinzipiellen Unterschied sehe ich
nicht.
Aber:
Mit einem angepassten Beispiel (ein paar Variablen in den Tier-Objekten,
etwas Rechnen statt cout, 5 Millionen Tiere, new/delete nicht
mitgemessen) ist die Variante mit der virtuellen Methode nur ca. 30%
langsamer. Der Container-pro-Typ-Ansatz lohnt sich also (bei diesem aus
der Luft geriffenen und fast ebenso unrealistischen Beispiel) nicht.
Rolf M. schrieb:> Sagen wir mal so: Die Implementation eines überladenen Operators ist> eine Funktion.
Das kann so sein (operator<<()), muß es aber nicht (operator+()).
Im ersten Fall ("<<") handelt es sich um eine freistehende Funktion, die
nicht an eine Instanz gebunden ist und deswegen auch keinen
"this"-Zeiger erhält. Deswegen muß man die Funktion in diesem Falle ja
auch als "friend" deklarieren, damit der Compiler weiß, daß die Funktion
auf private Variablen der (hier als zweitem Parameter übergebenen)
Instanz zugreifen darf.
Der zweite Operator (+) erhält einen impliziten Zeiger ("this") auf eine
Instanz der Klasse, zu welcher er gehört, und ist deswegen eine Methode.
> Warum sollte man das auch anders definieren?
Weil es de facto etwas anderes ist. Übrigens benutzt auch der "Vater"
der Objektorientierung, Alan Kay, den Begriff "method" für Funktionen,
die an ihre Objekte gebunden sind.
Sheeva P. schrieb:> Rolf M. schrieb:>> Sagen wir mal so: Die Implementation eines überladenen Operators ist>> eine Funktion.>> Das kann so sein (operator<<()), muß es aber nicht (operator+()).
Ist immer so.
> Im ersten Fall ("<<") handelt es sich um eine freistehende Funktion, die> nicht an eine Instanz gebunden ist und deswegen auch keinen> "this"-Zeiger erhält. Deswegen muß man die Funktion in diesem Falle ja> auch als "friend" deklarieren, damit der Compiler weiß, daß die Funktion> auf private Variablen der (hier als zweitem Parameter übergebenen)> Instanz zugreifen darf.
Wenn sie das denn überhaupt muss. Oft ist das überhaupt nicht nötig.
Wenn ich beispielsweise einen Vektor per operator<< nach cout ausgeben
will, besitzt die Klasse in der Regel public-Zugriffsfunktionen auf alle
Elemente, so dass es keinen Grund gibt, im Operator direkt auf private
Elemente zuzugreifen.
> Der zweite Operator (+) erhält einen impliziten Zeiger ("this") auf eine> Instanz der Klasse, zu welcher er gehört, und ist deswegen eine Methode.
Was nix anderes ist als eine Art von Funktion. Der Methode den Status
"Funktion" abzusprechen, ist genauso wenig sinnvoll, wie das bei den
Operatoren tun zu wollen. Sie sind einfach spezielle Formen von
Funktionen. Das ist ja so, als ob man einem Zeiger den Status "Variable"
entzieht, nur weil er ein paar Dinge hat, die andere Variablen nicht
haben.
Im übrigen würde ich empfehlen, den operator+ auch als freie Funktion zu
schreiben, damit der linke und der rechte Operand gleich behandelt
werden (was z.B. bei impliziten Konvertierungen eine Rolle spielen
kann).
>> Warum sollte man das auch anders definieren?>> Weil es de facto etwas anderes ist. Übrigens benutzt auch der "Vater"> der Objektorientierung, Alan Kay, den Begriff "method" für Funktionen,> die an ihre Objekte gebunden sind.
Im C++-Kontext heißt es dennoch Memberfunktion. So ist es in C++ eben
einfach definiert. Man kann aber eben alternativ auch "Methode" sagen,
weil das der Begriff aus der OO-Lehre ist und dieser in reinen
OO-Sprachen gängig ist.
Rolf M. schrieb:>> Im ersten Fall ("<<") handelt es sich um eine freistehende Funktion, die>> nicht an eine Instanz gebunden ist und deswegen auch keinen>> "this"-Zeiger erhält. Deswegen muß man die Funktion in diesem Falle ja>> auch als "friend" deklarieren, damit der Compiler weiß, daß die Funktion>> auf private Variablen der (hier als zweitem Parameter übergebenen)>> Instanz zugreifen darf.
Wenn wir schon bei den Begriffen sind: es heißt "free function" (freie
Funktion), wenn man explizit den Unterschied zu einer "member function"
(Elementfunktion) ausdrücken will, andernfalls reicht es, einfach
"function" zu sagen (und so ist es auch üblich im Kontext von C++).
Ansonsten gibt es Operatoren, die man als Elementfunktionen realisieren
muss, und bei vielen anderen hat man die Wahl (zwischen sinnvoll und
weniger sinnvoll). Es gibt bei Scotty irgendwo eine Tabelle der
"empfohlenen Operatorrealisierungen. Bei Templates gibt es allerdings
Situationen, wo man in einem "non deducible context" landet und man
wieder zu Elementfunktionen zurückgehen muss, obwohl man es (besser) als
freie Funktion schreiben würde (prefer free functions over members),
aber nicht kann.
> Was nix anderes ist als eine Art von Funktion. Der Methode den Status> "Funktion" abzusprechen, ist genauso wenig sinnvoll, wie das bei den> Operatoren tun zu wollen. Sie sind einfach spezielle Formen von> Funktionen. Das ist ja so, als ob man einem Zeiger den Status "Variable"> entzieht, nur weil er ein paar Dinge hat, die andere Variablen nicht> haben.> Im übrigen würde ich empfehlen, den operator+ auch als freie Funktion zu> schreiben, damit der linke und der rechte Operand gleich behandelt> werden (was z.B. bei impliziten Konvertierungen eine Rolle spielen> kann).
Im Sinne von least-surprise würde man im Falle des binären "+" auch wohl
"+=" realisieren für einen DT: dann ist es üblich, den "+" auf den "+="
(und bei den anderen genauso, falls gewünscht) zurück zu führen. Nennt
sich "kanonische Implementierung".
>>> Warum sollte man das auch anders definieren?>>>> Weil es de facto etwas anderes ist. Übrigens benutzt auch der "Vater">> der Objektorientierung, Alan Kay, den Begriff "method" für Funktionen,>> die an ihre Objekte gebunden sind.
Im Sinne von C++ ist das aber Unsinn. Im übrigen werden wir wohl auch
noch die
"uniform function call syntax" erleben, bei der der Unterschied zwischen
Elementfunktion und freier Funktion auf der Seite des Aufrufers nicht
mehr vorhanden ist.