C++ C++ Design einer großen Funktionsliste mit Daten?

T_55

Lt. Junior Grade
Dabei seit
Feb. 2013
Beiträge
279
#1
Hallo,

ich möchte ein vector erstellen um dynamisch zur Laufzeit Funktionen nacheinander durch iterieren des Vectors auszuführen. Es sind eigentlich nicht besonders viele unterschiedliche Funktionen aber ein paar der Funktionen sollen mit unterschiedlichen Variablen/Parametern für kleine Zwischenspeicherungen in vielfacher Weise existieren.
Erster Gedanke ist natürlich ein struct oder class wo die Funktion und dessen Individualdaten gekapselt werden. Per new könnte man die Varianten generieren und dem vector die Zeiger übergeben.
Und jetzt die eigentliche Sache: Die Funktionen sind teilweise umfangreich und die Anzahl an Varianten im vector könnte bis an die 100.000 oder mehr gehen.
So werden große Funktionen nur wegen der individuellen Daten (die viel kleiner sind) vielfach im struct angelegt und dies ist eigentlich ein massiver Speicherverbrauch.
Daher wäre es meiner Ansicht nach besser die Funktion so zu erstellen, dass sie nur 1x existieren und für alle Varianten (die sich ja nur in den Variablen/Parametern unterscheiden) gelten.
Eine möglichst performante Variante wäre mir natürlich am liebsten denn da wird schon einiges durchiteriert.
Meine Frage wäre jetzt was wäre ein sinnvolles Design?

Meine aktuelle Idee ist:
- erstelle ein Struct mit ausschließlich den Variablen und nur dem Funktionsaufruf. Die Funktion liegt aber aussen und ist für alle structs gültig. Der Vector müsste dann irgendwie ein Zeiger auf den Funktionsaufruf im Struct bekommen.

oder gibts sonst noch Möglichkeiten wo die Funktion nur 1x Speicher belegt aber per Individualdaten aufgerufen wird?

Grüße
 

T_55

Lt. Junior Grade
Ersteller dieses Themas
Dabei seit
Feb. 2013
Beiträge
279
#3
Ne Funktion frisst immer nur einmal Speicher, ich glaub du bringst Klasse und Funktion durcheinander.
Echt eine Funktion in einer Klasse frisst nur 1x Speicher egal wie oft die Klasse angelegt wird?

Code:
struct mystruct
{
   int a;
   int b;

   void funktion(int &x, int &usw)
   {
      // große Funktion...
   }
};
std::vector<mystruct> vec(1000000);
 
Dabei seit
Nov. 2007
Beiträge
3.226
#4
Ja, warum sollte sie mehr fressen? Die Klasse speichert nur die Variablen und das wars. Der Compiler ruft dann die passende Funktion mit einem vorangestellten hidden-this pointer auf.
​Falls du eine virtuelle Funktion drin hast, wird in der Klasse eine Function-Table gespeichert, das macht (ohne multiple inheritance) die Klasse genau 1 pointer größer (der auf die Tabelle (vtable genannt)).
 

T_55

Lt. Junior Grade
Ersteller dieses Themas
Dabei seit
Feb. 2013
Beiträge
279
#5
Das ist ja mal eine gute Sache!, damit war das Problem gar kein Problem :)

Nochmal zum Verständnis, wäre das so richtig beschrieben:?
Ein vector von Objekten speichert selbst nur die Zeiger auf die Objektinstanzen die im heap rumliegen in denen nur die Variablen liegen sowie Zeiger auf die immer selbe Funktion welche alle Objekte teilen :)

So in etwa richtig?
 
Dabei seit
Nov. 2007
Beiträge
3.226
#6
Ein vector von Objekten speichert die Objekte direkt (wir sind ja nicht bei Java, sondern bei C++), ein vector<Objekt*> oder ein vector<unique_ptr<Object*>> speichert dann die Zeiger auf die Objekte.

​Nicht-virtuelle Klassenfunktionen werden nicht bei den Objekten mitabgespeichert. Sondern der Compiler erkennt, welche Funktion gemeint ist und ruft diese auf.

​Virtuelle Klassenfunktionen werden in einer Tabelle gesammelt und ein Zeiger im Objekt zeigt auf die Tabelle.

​Funktionen werden von C++ nicht dynamisch erstellt, da das a) kompliziert wäre und b) vom Betriebssystem unterbunden wird (der Stack und der Heap sind nicht ausführbar).
 

m.c.ignaz

Lt. Junior Grade
Dabei seit
Apr. 2003
Beiträge
297
#7
Magst du zu der ganzen Sache mal ein bisschen mehr Kontext geben? Das klingt irgendwie als waere schon die Absicht so viele Funktionen (unterscheiden sie sich nun oder nur die Parameter?) in einen vector zu stopfen (std::vector<std::function<my_type>()> ?) irgendwie schraeg.
 

T_55

Lt. Junior Grade
Ersteller dieses Themas
Dabei seit
Feb. 2013
Beiträge
279
#8
virtuelle Funktionen und std::function, das sind genau noch Dinge wo ich mich nochmal einlesen muss, werd gleich damit anfangen... Bin zwar schon fleißig mit OOP am Kapseln aber viel mehr auch nicht.

Ich versuchs nochmal besser auf den Nenner zu bringen:

Ziel ist zur Laufzeit eine Liste von unterschiedlichen Funktionen zu erstellen, wo manche mehrfach mit unterschiedlichen Startwerten ihrer dazugehörigen Variablen existieren. (das hatte ich oben missverständlicherweise als Parameter artikuliert).

Man könnte sicherlich auch alles mit etlichen if-Abfragen machen aber ich denke eine vorbereitete Liste ist schicker, leichter skalierbar und vermutlich auch performanter. Daher der Ansatz ein vector zu erstellen mit den entsprechenden Klassen, der jedesmal mit durchiterieren die gewünschten Funktionen schnell hintereinander aufruft und bis zum nächsten Trigger wartet.

Der vector erwartet allerdings einen Datentyp. Mit unterschiedlichen Klassen kann ich wie ich festgestellt habe leider kein vector bedienen daher wäre interessant wie man Funktionen die sich in unterschiedlichen vorbereiteten Klassen befinden in eine gemeinsame Liste/vector bekommt? Vielleicht gibt es aber auch noch eine viel bessere Idee?
 
Dabei seit
Nov. 2007
Beiträge
3.226
#9
Unterschiedliche Funktionen? Dann musst du diese mindestens einmal ausschreiben. Wenn dud das hast, kannst du die Funktionen auf geeignete Weise z.B. in eine map packen, dann brauchst du nur den passenden Schlüssel, um deine Funktion zu finden.

​Verschiedene Datentypen im vector geht via unique_ptr und vererbte Klassen (auf Slicing aufpassen).

​Aber ich werde immer noch nicht ganz aus deiner Problembeschreibung schlau.

​"unterschiedliche Funktionen": Geb mal ein Beispiel
​"mehrfach [...] unterschiedliche Startwerte": Ahhm, ne Funktion wird erstmal ohne ihre Variablen beschrieben.

​Bevor du mit Caching anfängst, schreib doch den Code erstmal ganz direkt.

​Crazy Ansatz:
Code:
vector<function<void()>>triggers;

​void addTrigger(val a, val b)
{
​function<void()>trig=[=](){make_trigger_things(a,b);};
​triggers.push_back(trig);
​//Oder gleich emplace_pack verwenden
​}
​int main(){
​...

for(auto&&i:daten)
​addTrigger(i.a,i.b);
​}
​
Entschuldige die fehlende Einrückung. Im Endeffekt fange ich eine lambda-Funktion und deren Parameter in einer std::function, die man dann problemlos in ein std::vector packen kann. Falls du nun die Trigger alle aufrufen willst:
Code:
for(auto&&i:triggers)
​i();
 

T_55

Lt. Junior Grade
Ersteller dieses Themas
Dabei seit
Feb. 2013
Beiträge
279
#10
Code:
class multi_class
{
   int a; // wert wird vor dem ersten Funktionsaufruf zugewiesen

   void funktion()
   {
      // Funktion individuell für diese Klasse
   }
};

class single_class
{
   double a = 10;

   void funktion()
   {
      // Funktion individuell für diese Klasse
   }
};
Ok also so wäre die Basis.

Die Klasse multi_class soll mehrfach existieren. Das heißt zB einmal erstellen und der Variable a zu Beginn den Wert 1 zuweisen dann nochmal erstellen und a den Wert 2 zuweisen dann 3 dann 4 usw bis zB 100.
Und eine simple Klasse single_class die nur einmal in die Liste kommt.
Wären dann in diesem Beispiel in Summe 101 Einträge in der Liste wovon 100x als multi_class und 1x als single_class.
Werden dann in echt eben nur mehr Klassen mit mehr Variablen. Die Funktionsinhalte sind auch jeweils individuell.
 

m.c.ignaz

Lt. Junior Grade
Dabei seit
Apr. 2003
Beiträge
297
#11
Ich versteh noch nicht ganz wie die Funktionsinhalte individuell sein koennen. Dann muesstest du ja eine Art Funktionsgenerator schreiben. Oder eben hunderte selbst ausgedachte Funktionen.

Ausserdem weiss ich nicht, warum du die Funktionen in eine Klasse einbettest. Sollten die tatsaechlich alle auf der gleichen Anzahl Variablen arbeiten, kannst du die doch easy als Parameter in einer freistehenden Funktion reinreichen.

Ist das ein frei erfundenes, abstraktes Beispiel um programmieren zu lernen, oder steht da ein greifbarer Plan dahinter? Wenn letzteres, bitte nochmal mehr Kontext zu dem, was du damit vorhast.
 

T_55

Lt. Junior Grade
Ersteller dieses Themas
Dabei seit
Feb. 2013
Beiträge
279
#12
- Ja es sind "ausgedachte" selbst geschriebene Funktionen in jeweils einer eigenen Klasse.
Die Unterschiede bei der Mehrfachausführung von multi_class sind ja nicht in der Funktion sondern in den Varianten der Startdaten. Das ist aber eigentlich auch unwichtig denn das Problem für mich ist ja unterschiedliche Klassen mit unterschiedlichen Funktionen in eine ausführbare Liste zu bekommen.

- Die Funktionen will ich in eigene Klassen einbetten weil sie zugehörige Variablen haben die ausserhalb des scope der Funktion gültig bleiben sollen.

- Ist im Prinzip sowas wie ein Event-getriebenes Programm wo ich Mess-Daten reinbekomme und jedes mal wenn Daten reinkommen will ich die Liste der Funktionen ausführen. Die Liste der aktivierten Funktionen selbst wird erst zur Laufzeit bestimmt sonst wäre es einfach.

Ziel ist simpel gesagt Funktionen auszuführen dessen Auswahl zur Laufzeit bestimmt wurden und dazugehörige beständige Daten haben. Da ich Zeiger-Arrays kenne die auf nackten Funktionen basieren kam die Idee der Klassen-Liste im vector.

Also nach allem was ich bisher überflogen hab scheinen folgende Stichworte, die ja auch schon zum Teil genannt wurden, in die richtige Richtung zu gehen:
- abgeleiteten Klassen, virtuelle Methode.
- std::function im vector
- void zeiger im vector
und evt...
- pointer to Member
- funktor
Ergänzung ()

Ok die eine Variante, die per virtueller Funktion und abgeleiteten Klassen, hat jetzt schon mal geklappt.
Code:
class basis_class{
public:
    virtual void funktion() {};
};

class multi_class : public basis_class{
public:
    void funktion() { std::cout << "multi_class" << std::endl; };
};

class single_class : public basis_class{
public:
    void funktion() { std::cout << "single_class" << std::endl; };
};

int main(){

    std::vector<basis_class*> vec;
    vec.push_back(new multi_class());
    vec.push_back(new single_class());

    for (std::vector<basis_class*>::const_iterator it = vec.begin(); it != vec.end(); it++){
        (*it)->funktion();
        // delete *it;
    }
    return 0;
}
Mal abgesehen von premature optimization is the root...;), frage mich wie performant so ein Konstrukt noch ist, bzw die Aufrufe in der for Schleife.
Die anderen Wege nach Rom mit std::function, unique_ptr, ggf lambdas auch alles sehr interessant!
 
Zuletzt bearbeitet:

m.c.ignaz

Lt. Junior Grade
Dabei seit
Apr. 2003
Beiträge
297
#13
Dein Beispiel mit Vererbung um std::unique_ptr und range based for loop erweitert: Wandbox Beispiel

Und nun willst du hunderte Klassen von Hand schreiben?
Zum Thema unterschiedliche Klassen mit unterschiedlichen Funktionen: Zumindest die Funktionssignatur muss gleich sein, sonst gehts natuerlich nicht.
Mehrfachausfuehrung? Du meinst ein weiterer Aufruf arbeitet auf den Ergebnisdaten des vorangegangenen usw.?

/Ich hab nochmal so druebergelesen: Versteh ich das richtig, dass du deine Klassen und Instanzen davon so rumschlummern hast und an einem gewissen Punkt moechtest du einmal durchiterieren und diese spezielle Funktion aufrufen, die den Klassenzustand aendert?
Wenn ja klingt das nach nem Observer-Pattern.

//Und warum fragst du dich wie performant dieses Konstrukt mit der For-Schleife ist? Wo siehst du denn ein eventuelles Performanceproblem dabei?
 
Zuletzt bearbeitet:
Dabei seit
Feb. 2017
Beiträge
229
#14
Ziel ist simpel gesagt Funktionen auszuführen dessen Auswahl zur Laufzeit bestimmt wurden und dazugehörige beständige Daten haben.
Und genau deswegen bieten sich Lambda-Funktionen mit std::function-Wrapper an. Wenn du von Hand mit virtuellen Methoden etc. hantierst, machst du im Grunde ohnehin nichts anderes als std::function.
 

T_55

Lt. Junior Grade
Ersteller dieses Themas
Dabei seit
Feb. 2013
Beiträge
279
#15
Hallo, ich hab nun std::function mal ausprobiert und folgendes funktioniert und das gefällt mir, ist weniger Schreibarbeit als bei den virtuellen Funktionen und kein new notwendig. Laut dem Artikel ist std::function auch billiger. https://probablydance.com/2012/12/16/the-importance-of-stdfunction/

Jetzt hätte ich nochmal eine Frage zu std::function was aus meiner Sicht fantastische Möglichkeiten eröffnet. Wenn man jetzt der std::function Funktionen mit oder ohne Parametern übergibt was wird dann jeweils genau gespeichert? Mal blöd gefragt, werden nur die Parameter gespeichert und ein Zeiger auf die Funktion oder wird jedesmal die Funktion mit Parametern als ganzes irgendwo reingelegt? Wenn Beispielsweise eine Funktion mehrfach in den vector kommt, mit jeweils unterschiedlichen Parametern, wie wirkt sich das dann auf den Speicherverbrauch aus?

Code:
#include <iostream>
#include <vector>
#include <functional>

void test()
{
    std::cout << "test" << std::endl;
}
void test2()
{
    std::cout << "test2" << std::endl;
}

int main()
{
    std::vector<std::function<void()>> vector;

    vector.push_back([]{ test(); });
    vector.push_back([]{ test2(); });

    for (int i = 0; i < vector.size(); ++i)
    {
        vector[i]();
    }

    return 0;
}
Gruß
 
Zuletzt bearbeitet:

m.c.ignaz

Lt. Junior Grade
Dabei seit
Apr. 2003
Beiträge
297
#17
Du musst deine Funktionen nicht noch extra in eine Lambda-Funktion einbetten. Und wenn du ueber den gesamten vector iterierst, bitte range-based For-loop benutzen. ;)

Code:
    #include <iostream>
    #include <vector>
    #include <functional>
     
    void test()
    {
        std::cout << "test" << std::endl;
    }
    void test2()
    {
        std::cout << "test2" << std::endl;
    }
     
    int main()
    {
        std::vector<std::function<void()>> vector;
     
        vector.push_back(test);
        vector.push_back(test2);
     
        for (auto& func : vector)
            func();
     
        return 0;
    }
So wie ich das verstehe, moechtest du jetzt Funktionen in deinen Vektor stopfen, die mal nen Parameter haben, mal nicht. Oder mal einen, mal mehrere. Du kannst aber nur Funktionen mit gleicher Signatur (hier "void funktionsname()") in deinen Vektor tun.

Ich finde diese ganze Idee/das Design nach wie vor etwas verdaechtig komisch und wuerde gern mal den vollen Kontext dazu sehen. ;)
 
Dabei seit
Nov. 2007
Beiträge
3.226
#18
Was du glaub erreichen willst, ist ein
Code:
​vector<function<void()>>v;
var a,b,c;
v.push_back([=](){ func(a,b,c);});
​v.push_back([=](){ func2(c,42);});
​
Dadurch machst du ein Parameter-Capture zur Erstellzeit der Lambda-Funktion.

​Lambda-Funktionen sind im Endeffekt nur syntaktischen Zucker
Code:
class Lambda{
​var _a;
​Lambda(var a):_a(a){}
​retval operator()(var b){blub(_a); return r;}
​};
​wird zu
Code:
​[=](var b){blub(a); return r;}
​@m.c.ignaz: Ich würde auch gerne mal die gesamte Problemstellung sehen :-).
 
Top