if, switch, funktionspointer für Optionen verwenden?



  • Hi, danke erstmal für die Antworten.
    Dachte mir schon, dass nicht klar rüberkommt was ich möchte 🙂
    Wusste nicht wie ichs besser sagen soll.

    Die Hauptfunktion soll auf verschiedene Mengen von Argumenten angewannt werden können.
    Eine solche Menge hat bestimmte Eigenschaften, die andere wiederum andere. Man möchte am Ende ein möglichst gutes Ergebnis haben. Dafür sind je nach Eingabemenge mal diese Optionen besser und mal andere. Um das Endergebnis zu verändern (um ggf. ein besseres zu bekommen) kann man dann die Optionen verändern.

    Die Optionen sind jedoch nich immer vom gleichen Typ. Man könnte villeicht besser sagen, die eine Option wäre ein fester random seed (statt einen bestimmten seed vorzugeben) und die andere die Zwischenergebnisse runden, und wieder eine andere Normieren usw.
    Die eine Option hat also mit einer anderen nicht unbedingt etwas gemein. An sich selbst haben die Optionen keine variablen Parameter. Brauchen also jeweils keine Arguement in der Eltern-Funktion.


  • Mod

    Überladung?



  • SeppJ schrieb:

    Überladung?

    Und wie am besten?

    Der Teil am Anfang und am Ende wäre immer gleich, nur in der Mitter soll optional etwas anderes passieren.
    Müsste ich dann den Code oben und unten mehrfach schreiben?



  • Du könntest die Option als ein Interface übergeben und beim Aufruf eine spezialisierte Klasse einsetzen, vom Prinzip her so ungefähr:

    //Interface
    class Option
    {
        virtual ~Option() = default;
        virtual vartyp doSomething(/*Daten*/);
    }
    
    //Implementiert Interface
    class OptFixSeed : public Option
    {
       vartyp doSomething(/*Daten*/) override;
    }
    
    // und in deiner Klasse/Funktion
    
    vartyp fun(vartyp2 argument, Option opt)
    {
       // Code
    
       auto result = opt.doSomething(/*Daten*/);
    
       // Code
    }
    

    Oder du lagerst den Code am Anfang und am Ende in jeweils separate Funktionen aus und rufst diese in den spezialisierten Funktionen auf, dann gibt es keine Doppelung.



  • Da C++ leider keine named parameters unterstützt (derzeit zweifellos der größte Mangel der Sprache), ist der übliche Weg, ab 3 (optionalen) Parametern eine Struktur dafür zu verwenden, also z.B.:

    struct FunOptions
    {
      uint32_t seed=0;
      std::function<void(Foo&)> callback;
    
      enum EStrategy {DEFAULT_STRATEGY,...};
      EStrategy strategy=DEFAULT_STRATEGY;
    
    [...]
    };
    


  • ;Rohan schrieb:

    Da C++ leider keine named parameters unterstützt (derzeit zweifellos der größte Mangel der Sprache)

    Ja, völlig zweifellos.



  • ;Rohan schrieb:

    Da C++ leider keine named parameters unterstützt (derzeit zweifellos der größte Mangel der Sprache), ist der übliche Weg, ab 3 (optionalen) Parametern eine Struktur dafür zu verwenden, also z.B.:

    struct FunOptions
    {
      uint32_t seed=0;
      std::function<void(Foo&)> callback;
    
      enum EStrategy {DEFAULT_STRATEGY,...};
      EStrategy strategy=DEFAULT_STRATEGY;
      
    [...]
    };
    

    soweit ich weiß unterstützt c++ namend parameter (oder hab ichd a was falsch verstanden?) 😕

    lg



  • HelLLPpErrS schrieb:

    (oder hab ichd a was falsch verstanden?)

    Ja



  • manni66 schrieb:

    HelLLPpErrS schrieb:

    (oder hab ichd a was falsch verstanden?)

    Ja

    mein fehler. 🙂

    lg



  • Hi, danke für die Antworten.

    Die Idee mit Interface schaut gut aus. (Habe ich bisher noch nicht gemacht.) Dann hätte ich für jede Option eine eigene implementation.
    Ich weiß nicht, ob man das in c++ noch oft macht aber kommt das dann der Übergabe eines Pointers auf eine Funktion (in C) gleich?. Eingabe und Ausgabeparameter wären jeweils gleich (bei pointer und interface). Je nach Option hätte ich dann die Übergabe eines Pointers auf die entsprechende Funktion.
    Die Lösung mit Interface schaut aber übersichtlicher aus.
    Ein Problem (bei beiden) wär dann höchstens noch, dass nicht jede Subfunktion die selben Ein- und Ausgabewerte hat. Eine verändert mal das eine, eine andere mal was anderes.

    @;Rohan :
    Noch eine Frage dazu, ob richtig verstanden.
    Bei dem Struct

    std::function<void(Foo&)> callback;
    

    wäre das dann die zu setztende Funktion, abhängig von der gewählten Option, mit dem Parameter Foo? Also das doSomething von der Lösung oben?

    vartyp fun(vartyp2 argument, FunOptions opt)
    {
       Foo foo;
       foo = // Code
    
       // statt: auto result = opt.doSomething(/*Daten*/);
       //dann:
       opt.callback(foo);
    
       // Code
    }
    void doSomething(Foo &foo){
        //code
    }
    
    void main()
    {
        vartyp result[1000000];
        vartyp2 argument[1000000];
        //code
        FunOptions opt;
        opt.strategy = DEFAULT_STRATEGY;
        opt.callback = doSomething;
    
        for(int i=0; i<1000000; i++){
          result[i] = fun(argument[i], opt);
        }
    }
    

    Das 'FunOptions.strategy' hätte dann nur informativen character?



  • mccpfbn schrieb:

    void main()
    {
        vartyp result[1000000];
    }
    

    mit so was wär' ich vorsichtig - das Array ähm Zeiger ähm ... ach egal, also dieses result[1000000] wird hier auf dem Stack angelegt. Die zulässige Größe von stack frames ist plattformabhängig begrenzt und kann kleiner sein als man erwartet 😋



  • HelLLPpErrS schrieb:

    soweit ich weiß unterstützt c++ namend parameter (oder hab ichd a was falsch verstanden?) 😕

    Bei diesem Konzept kann man beim Funktionsaufruf die Namen der Argumente explizit angeben (z.B. memcpy(.src=buf1, .dest=buf2, .size=n) ).
    Dadurch kannst du sie in beliebiger Reihenfolge angeben, insbesondere kannst du dir z.B. den 18. Parameter herauspicken und die restlichen beim Default belassen.
    Wegen diesem Luxus lässt sich das Verhalten von Funktionen in solchen Sprachen oft weitreichend konfigurieren.
    Dem Aufrufer kann es schlicht egal sein, ob die Funktion 2, 3 oder 100 Parameter hat. Das ist in C++ anders.

    mccpfbn schrieb:

    wäre das dann die zu setztende Funktion, abhängig von der gewählten Option, mit dem Parameter Foo? Also das doSomething von der Lösung oben?

    Richtig, std::function ist das C++-Äquivalent eines Funktionszeigers. std::function hat den Vorteil auch Funktoren, insbesondere Lambdafunktionen zu unterstützen. Diese sind für solche Zwecke meist vorzuziehen, da du bei Bedarf auch auf lokale Variablen der äußeren Funktion zugreifen kannst und vor allem, weil du damit zusammengehörigen Code nicht räumlich auseinanderreißt.

    opt.callback = [&](Foo& foo)
    {
       //code
    };
    

    Und diese Zeile stört mich ein wenig:

    opt.strategy = DEFAULT_STRATEGY;
    

    Teil des Sinns der Struktur ist schließlich, dass du Parameter, bei denen du mit dem Defaultwert zufrieden bist, NICHT explizit angeben/setzen musst.

    mccpfbn schrieb:

    Das 'FunOptions.strategy' hätte dann nur informativen character?

    Das wäre ein Beispiel, wie du der Funktion sagen kannst, wie sie vorgehen soll.
    Alles an möglichen Stellschrauben für das Verhalten der Funktion wandert in die Struktur, natürlich jeweils mit sinnvollen Defaults.



  • mccpfbn schrieb:

    Ein Problem (bei beiden) wär dann höchstens noch, dass nicht jede Subfunktion die selben Ein- und Ausgabewerte hat. Eine verändert mal das eine, eine andere mal was anderes.

    Ich kenne deine Daten nicht, aber eine Möglichkeit wäre es die Daten der Funktion per Referenz (oder als Zeiger) zu übergeben, dann kann diese direkt die Daten ändern.



  • danke für die Antworten.

    also dieses result[1000000] wird hier auf dem Stack angelegt

    Wollte nur zeigen, dass es viel ist. Aber Stack so klein? Sind ja nur wenige MB.

    Teil des Sinns der Struktur ist schließlich, dass du Parameter, bei denen du mit dem Defaultwert zufrieden bist, NICHT explizit angeben/setzen musst.

    Ja, den hatte ich nur genommen, weil im Beispiel oben noch keiner definiert war.

    Lambdafunktionen habe ich in C++ noch nicht verwendet, muss ich mir nochmal näher anschauen (mit der Bedeutung was in [] steht). Danke für den Hinweis.

    Ich kenne deine Daten nicht, aber eine Möglichkeit wäre es die Daten der Funktion per Referenz (oder als Zeiger) zu übergeben, dann kann diese direkt die Daten ändern.

    Also die Daten von der Hauptfunktion sind immer gleicher Art, nur die von doSomething könnten immer anders sein.

    In C kann man da einen void Pointer übergeben auf ein options-spezifisches struct und dann in der doSomething zum jeweils passenden Option-struct casten.
    Gibts sowas (in C++) auch schöner gecoded?

    Könnte man das alternativ mit den Lambadfunktionen machen? Die lokalen Variablen auf die sie zugreifen können müssen die dann in dem struct definiert sein oder gehn dann auch die von der Funktion, in der sie dann aufgerufen werden?

    also z.B:

    struct FunOptions
    {
      uint32_t seed=0;
      int lokaleVariable = 4711;//<------ oder im struct?
      std::function<void(Foo&)> callback;
    
      enum EStrategy {DEFAULT_STRATEGY,...};
      EStrategy strategy=DEFAULT_STRATEGY;
    
    };
    
    vartyp fun(vartyp2 argument, FunOptions opt)
    {
       Foo foo;
       foo = // Code
    
       int lokaleVariable = 42;//<------ oder diese?
       opt.callback(foo);
    
         // Code
    }
    
    void main()
    {
        vartyp result[1000000];
        vartyp2 argument[1000000];
        //code
        int lokaleVariable = 23; //<------ die hier?
        FunOptions opt;
        opt.strategy = LOCAL_STRATEGY;
        opt.callback = [&](Foo& foo)
        {
          std::cout << lokaleVariable;  // = 42, 23 oder 4711?
    
          //code
        };
        for(int i=0; i<1000000; i++){
          result[i] = fun(argument[i], opt);
        }
    }
    

    Welche Zahl wird dann ausgegeben (42, 23 oder 4711)?

    Das wäre ein Beispiel, wie du der Funktion sagen kannst, wie sie vorgehen soll.
    Alles an möglichen Stellschrauben für das Verhalten der Funktion wandert in die Struktur, natürlich jeweils mit sinnvollen Defaults.

    Aber dann habe ich das Problem nur verschoben oder nicht? Dann muss ich doch dann wieder irgenwo unterscheiden ob strategie1, 2, 3 usw., wo dann wieder das Problem kommt, ob ich es mit if's, switch, funktionpointer, interfaces, Lambadfunktionen usw. mache.



  • ;Rohan schrieb:

    HelLLPpErrS schrieb:

    soweit ich weiß unterstützt c++ namend parameter (oder hab ichd a was falsch verstanden?) 😕

    Bei diesem Konzept kann man beim Funktionsaufruf die Namen der Argumente explizit angeben (z.B. memcpy(.src=buf1, .dest=buf2, .size=n) ).
    Dadurch kannst du sie in beliebiger Reihenfolge angeben, insbesondere kannst du dir z.B. den 18. Parameter herauspicken und die restlichen beim Default belassen.
    Wegen diesem Luxus lässt sich das Verhalten von Funktionen in solchen Sprachen oft weitreichend konfigurieren.
    Dem Aufrufer kann es schlicht egal sein, ob die Funktion 2, 3 oder 100 Parameter hat. Das ist in C++ anders.

    jo, hatte das jetzt auch gelesen, mein fehler. 🙂
    danke aber für die erklärung. 🙂

    aber wozu braucht man das? 😕
    so warnt mich der compiler wenigstens wenn ich parameter vergesse bzw nicht angebe da diese z.B. nicht ordentlich dokumentiert wurden. 🙄

    ich sehe auch nicht den vorteil den es bringen sollte? (hab sowas aber auch nicht gebraucht bis jetzt ... 😕 )
    man kann auch einfach einen "default" wert übergeben oder was hätte das für nachteile?
    oder durch vererbung ähnliches verhalten erzeugen ...

    klar ist dann kein "einfach" parameter weglassen aber ich kann die parameter entsprechend d. aufgabe flexibel anpassen und habe kaum bzw. keinen code dupliziert warum sollte ich dann named paramter brauchen?

    oder warum macht die übergabe eines parameters weniger hier einen großen unterschied ? 😕

    lg



  • mccpfbn schrieb:

    Könnte man das alternativ mit den Lambadfunktionen machen? Die lokalen Variablen auf die sie zugreifen können müssen die dann in dem struct definiert sein oder gehn dann auch die von der Funktion, in der sie dann aufgerufen werden?

    Ja, eine Lambdafunktion ist hier die richtige Lösung.
    In deinem Beispiel wird 23 ausgegeben.

    mccpfbn schrieb:

    Aber dann habe ich das Problem nur verschoben oder nicht? Dann muss ich doch dann wieder irgenwo unterscheiden ob strategie1, 2, 3 usw., wo dann wieder das Problem kommt, ob ich es mit if's, switch, funktionpointer, interfaces, Lambadfunktionen usw. mache.

    Sicher. Ich bin davon ausgegangen, dass es z.B. drei sinnvolle Strategien gibt, von denen sich der Aufrufer eine aussuchen darf. Wenn es keine sinnvollen Vorgaben gibt (z.B. bei einer Filter- oder Gewichtungsfunktion) -> std::function. Lässt sich natürlich auch kombinieren. Sind die Strategien jeweils sehr komplex und nicht einfach mit wenigen Zeilen Code umsetzbar, so ist normalerweise ein Interface die richtige Wahl, insbesondere dann, wenn der Nutzer eigene übergeben können soll. Es kommt immer auf den konkreten Fall an.

    HelLLPpErrS schrieb:

    ich sehe auch nicht den vorteil den es bringen sollte? (hab sowas aber auch nicht gebraucht bis jetzt ... 😕 )
    man kann auch einfach einen "default" wert übergeben oder was hätte das für nachteile?

    Einen Funktionsaufruf mit einem Parameter hinzuschreiben dauert 5 Sekunden. Mit allen Parametern 3 Minuten.
    Beim Lesen: mit einem Parameter brauchst du 0.5 Sekunden, andernfalls brauchst du 10 Minuten.
    Siehe z.B. die jQuery.ajax()-Funktion. Sie hat 34 optionale Parameter, von denen du meist nur drei brauchst: url, success und error. Die restlichen 31 willst du nicht angeben müssen und schon gar nicht in einer exakt definierten Reihenfolge. Musst du auch nicht, denn auch wenn Javascript keine named parameters in der Reinform unterstützt, erfüllen Objekte in der Sprache hier exakt den gleichen Zweck.



  • Könnte ich die Lambadafunktion anderers definieren, so dass 42 ausgegeben wird? (ohne globale variablen)

    Sicher. Ich bin davon ausgegangen, dass es z.B. drei sinnvolle Strategien gibt, von denen sich der Aufrufer eine aussuchen darf

    Ja, auch bei drei wär doch dann das Problem. Ich müsste in dem Beispiel-for 1000000 überprüfen welche strategie gewählt wurde, obwohl sie für alle 1000000 gleich ist.



  • mccpfbn schrieb:

    Könnte ich die Lambadafunktion anderers definieren, so dass 42 ausgegeben wird? (ohne globale variablen)

    Klar, indem du die 42 der Funktion als Argument mitgibst oder als Attribut von Foo. Oder wenn dir nach Abenteuer zumute ist, indem du den Wert der lokalen Variable in main() direkt per Referenz oder Zeiger änderst.

    mccpfbn schrieb:

    Ja, auch bei drei wär doch dann das Problem. Ich müsste in dem Beispiel-for 1000000 überprüfen welche strategie gewählt wurde, obwohl sie für alle 1000000 gleich ist.

    Ah, verstehe. Wenn Performance wichtig ist, wäre mir das ein Dorn in Auge. Die zigfache Überprüfung kostet weniger als du vielleicht denkst, die Struktur des Codes kann aber u.U. weitere Optimierungen verhindern, die sonst möglich wären. Ab diesem Punkt kann man keine allgemeine Aussagen machen*, sondern muss das konkrete Problem kennen. Es ist vermutlich sinnvoll, die Funktion von Anfang an so auszulegen, dass beliebig große Batches pro Aufruf abgearbeitet werden. Solche Fallunterscheidungen werden dann nur ein einziges Mal gemacht.

    * eine allgemeine Aussage geht allerdings durchaus: es ist immer die Lösung zu wählen, bei der das Produkt aus Einfachheit (der inverse Arbeitsaufwand), Wartbarkeit und Performance maximal ist.



  • Dann müsste Foo alle potentiellen Parameter beinhalten, die je nach Wahl der Funktion zum größten Teil nicht gebraucht werden. Ich glaub ich muss mir was anders einfallen lassen. Aber danke an alle Helfer.

    Ich habe habe aber mal mit den Lambda Funktionen rumprobiert. Falls ich da auf lokale Variablen außerhalb der Funktion zugreifen will müssen die static sein. Gehen da auch welche die nicht static sind?



  • mal ein Beispiel:

    #include <iostream>
    
    class asdf
    {
        public:
    		int value;
    		asdf(int b){value=b+40;}
    
    		int (*getFunc())(int)
    		{
    			int (*test)(int a);
                //test  = [&value] (int a) -> int{ return a+value;  }; <--sowas in der art solls sein, geht nicht
    			test  = [] (int a) -> int{ return a;  }; //geht
    			return test;
    		}
    
    };
    
    int main(){
        asdf qwer(23);	
        int (*afun)(int a);
        afun = qwer.getFunc();
    	std::cout << afun(2) <<"\n";  //hier kommt 2 raus, soll aber 23+40 + 2 rauskommen
    }
    

Anmelden zum Antworten