if, switch, funktionspointer für Optionen verwenden?



  • Hi,
    ich habe eine Frage, wie man für einen speziellen Fall am besten/effektivsten coded.

    Ich habe eine Funktion:

    vartyp fun(vartyp2 argument, vartypOpt option){
          //viel code hier abhängig von argument
    
          //##A##hier soll, je nach option etwas unterschiedlichen passieren
    
          //viel code hier abhängig von argument und option
    
          vartyp var;
          return var;
    }
    

    Die Funktion 'fun' wird sehr oft mit vielen Argumenten ausgeführt.
    Je nach gewählter Option soll sie (für alle Argumente eines Durchgangs) das selbe machen. Eine Option könnte z.B. ein anderer Seed für random sein, eine andere Normierung, eine Zwischenfunktion usw.

    zu ##A##:
    wie macht man das am besten:
    (im Fall 'vartypOpt' ist z.B. char)
    1)switch

    switch(option){
         case 0: erg=subFun0(..); break;
         case 1: erg=subFun0(..); break;
         case 2: erg=subFun0(..); break;
         case 3: erg=subFun0(..); break;
         ....
    }
    
    1. if
    if(option == 1){
         erg=subFun0(..);
      }
      else if(option ==2){
         erg=subFun1(..);
      }else if .....
    
    1. funktion pointer
    erg = option(..)
    

    4)für jede option eine eigene Funktion schreiben (die dann jeweils große Teile des Codes doppelt haben)
    5)generische Methode

    oder etwas anderes?


  • Mod

    Beste Option: Gar nichts davon. Wozu sollte man das jemals brauchen?



  • SeppJ schrieb:

    Beste Option: Gar nichts davon. Wozu sollte man das jemals brauchen?

    😃 👍

    mccpfbn schrieb:

    Hi,
    Ich habe eine Funktion:

    vartyp fun(vartyp2 argument, vartypOpt option){
          //viel code hier abhängig von argument
         
          //##A##hier soll, je nach option etwas unterschiedlichen passieren
    
          //viel code hier abhängig von argument und option
    
          vartyp var;
          return var;
    }
    

    Die Funktion 'fun' wird sehr oft mit vielen Argumenten ausgeführt.
    Je nach gewählter Option soll sie (für alle Argumente eines Durchgangs) das selbe machen. Eine Option könnte z.B. ein anderer Seed für random sein, eine andere Normierung, eine Zwischenfunktion usw.

    wenn ich dich richtig verstehe (bin mir da nicht ganz sicher...), dann übergibst du das am besten eifach als funktionsparameter.

    also so: (nur ein beispiel)

    vartyp fun (vartyp2 argument, vartypOpt option, typ andererSeedfürRandom, typ andereNormierung )
    {
        ... 
    }
    

    usw. dann kannst du diese für jeden aufruf der funktion fun übergeben und so die selbe "aufgabe" mit unterschiedlichen "optionen" ausführen.
    (keine ahung ob du das meinst 😕 )

    hoff hat etwas geholfen
    lg



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


Log in to reply