Forum: PC-Programmierung C++ Suche "HighPerformance"-Lösung für dynamische Funktionsliste


von NeedSpeed (Gast)


Lesenswert?

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)
1
class Tier{
2
public:
3
    virtual void iss() {};
4
};
5
6
class Hund : public Tier{
7
public:
8
    void iss() { std::cout << "Wuff! Fresse gerade" << std::endl; };
9
};
10
11
class Mensch : public Tier{
12
public:
13
    void iss() { std::cout << "Esse gerade" << std::endl; };
14
};
15
16
#include <vector>
17
18
int main(){
19
20
    std::vector<Tier*> tiere;
21
    tiere.push_back(new Hund());
22
    tiere.push_back(new Mensch());
23
24
    for (std::vector<Tier*>::const_iterator it = tiere.begin(); it != tiere.end(); it++){
25
        (*it)->iss();
26
        delete *it;
27
    }
28
    return 0;
29
}

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ß

von Dr. Sommer (Gast)


Lesenswert?

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.

von NeedSpeed (Gast)


Lesenswert?

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).

von Ivo -. (Gast)


Lesenswert?

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

von Ivo -. (Gast)


Lesenswert?

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

von NeedSpeed (Gast)


Lesenswert?

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...

von NeedSpeed (Gast)


Lesenswert?

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.

von Sven B. (scummos)


Lesenswert?

Was willst du denn überhaupt machen?

von Ivo -. (Gast)


Lesenswert?

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

von NeedSpeed (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Johannes S. (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Ivo -. (Gast)


Lesenswert?

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

von Sven B. (scummos)


Lesenswert?

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?

von Thomas M. (langhaarrocker)


Lesenswert?

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

von Jim M. (turboj)


Lesenswert?

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.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

NeedSpeed schrieb:
> Das Prinzip funktioniert zwar so aber es ist bei einer größeren
> Funktionsliste sehr langsam ...

wie hast Du das gemessen?

von Der Andere (Gast)


Lesenswert?

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.

von Mark B. (markbrandis)


Lesenswert?

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.

von Dumdi D. (dumdidum)


Lesenswert?

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.

von Tom (Gast)


Lesenswert?

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

von Tom (Gast)


Angehängte Dateien:

Lesenswert?

Fürs Archiv, falls ideone das löscht.

von Dumdi D. (dumdidum)


Lesenswert?

Tom schrieb:
> Mal eine Spielerei ohne virtuelle Methoden (statt dessen duck typing),

Sehr schoene Loesung, wieder was gelernt.

von Tom (Gast)


Lesenswert?

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.

von Sheeva P. (sheevaplug)


Lesenswert?

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:
1
#include <iostream>
2
#include <string>
3
#include <vector>
4
5
class Lebewesen {
6
public:
7
    Lebewesen(std::string wer, std::string was): wer(wer), was(was) {}
8
    void operator() (void) {
9
        std::cout << "Ein " << wer << " " << was << "." << std::endl;
10
    }
11
protected:
12
    std::string wer;
13
    std::string was;
14
};
15
16
17
int main(void) {
18
19
    std::vector<Lebewesen> lv;
20
21
    for(int i = 0; i < 1000000; ++i) {
22
        lv.push_back( Lebewesen("Mensch", "isst") );
23
        lv.push_back( Lebewesen("Huhn", "pickt") );
24
        lv.push_back( Lebewesen("Hund", "frisst") );
25
    }
26
    
27
    for(auto& l: lv) {
28
        l();
29
    }
30
    
31
    return 0;
32
}

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. ;-)

: Bearbeitet durch User
von Sven B. (scummos)


Lesenswert?

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.

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

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

von Thomas M. (langhaarrocker)


Lesenswert?

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.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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...")

von Wilhelm M. (wimalopaan)


Lesenswert?

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 ...

von NeedSpeed (Gast)


Lesenswert?

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.

von BobbyX (Gast)


Lesenswert?

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.

von asdf (Gast)


Lesenswert?

@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.

von Dr. Sommer (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

Äh, auslesen meinte ich natürlich. Zuweisen könnte sogar langsamer sein 
wegen Cache Invalidierung

von Hulk (Gast)


Lesenswert?

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.

von Sheeva P. (sheevaplug)


Lesenswert?

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):
1
int main(void) {
2
3
    std::vector<Lebewesen> lv;
4
    lv.push_back( Lebewesen("Mensch", "isst") );
5
    lv.push_back( Lebewesen("Huhn", "pickt") );
6
    lv.push_back( Lebewesen("Hund", "frisst") );
7
8
    std::cout << "Bitte wählen:" << std::endl;
9
    for(int i = 0; i < lv.size(); ++i) {
10
        std::cout << " " << i << ": " << lv[i].getName() << std::endl;
11
    }
12
    std::cout << "Auswahl (x to exit): ";
13
14
    std::string buffer;
15
    std::getline(std::cin, buffer);
16
    if(buffer == "x") {
17
        std::cout << "Goodbye." << std::endl;
18
        std::exit(0);
19
    } else {
20
        try {
21
            lv.at(std::stoi(buffer))();
22
        } catch(std::exception e) {
23
            std::cerr << "Could not find selection #" << buffer
24
                      << ", sorry." << std::endl;
25
            std::exit(-1);
26
        }
27
    }
28
    
29
    return 0;
30
}

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. ;-)

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

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.

von Sheeva P. (sheevaplug)


Lesenswert?

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.

von Johannes S. (Gast)


Lesenswert?

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?

von Possetitjel (Gast)


Lesenswert?

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.

von Sheeva P. (sheevaplug)


Lesenswert?

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.

von Sheeva P. (sheevaplug)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Sven B. (scummos)


Lesenswert?

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!

von Possetitjel (Gast)


Lesenswert?

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.

von Ivo -. (Gast)


Lesenswert?

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

von Dr. Sommer (Gast)


Lesenswert?

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?

von Rolf M. (rmagnus)


Lesenswert?

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.

von Sheeva P. (sheevaplug)


Lesenswert?

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.

von Tom (Gast)


Lesenswert?

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.

von Sheeva P. (sheevaplug)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.