Teilweise Initialisierungsliste möglich?



  • Hallo.
    Ist es möglich einen Teil der member-variablen in der initialisierungsliste zu initialisieren und dem Rest im Konstruktorbody Werte zuzuweisen? Kompilieren tut der Code, allerdings frage ich mich, ob es von den Konventionen her ok ist.

    Hier mal ein Beispiel:

    ComplexCalc::ComplexCalc(double re, double im)
        :m_re(re),
         m_im(im),
         m_magnitude(sqrt((re * re) + (im * im)))
    {
        if(re == 0 && im == 0) m_angle = 0;
        else if(re == 0 && im > 0) m_angle = 90;
        else if(re == 0 && im < 0) m_angle = -90;
        else if(re > 0 && im == 0) m_angle = 0;
        else if(re < 0 && im == 0) m_angle = 180;
        else
        {
            m_angle = (360 / (2 * M_PI)) * atan(im / re); 
            if(re < 0 && (im > 0 || im < 0)) m_angle += 180;
            else if(re > 0 && im < 0) m_angle += 360;
        }
    }
    

    Gruß Alfons



  • Ja, das ist möglich, aber zumindest in dem Fall wäre es IMHO sinnvoller, diese Berechnung in eine private Memberfunktion auszulagern und m_angle darüber zu initialisieren.



  • So in etwa?

    ComplexCalc::ComplexCalc(double re, double im)
        :m_re(re),
         m_im(im),
         m_magnitude(sqrt((re * re) + (im * im))),
         m_angle(computeAngle())
    {
    }
    
    double ComplexCalc::computeAngle()
    {
        double angle = 0;
    
        if(m_re == 0 && m_im == 0) angle = 0;
        else if(m_re == 0 && m_im > 0) angle = 90;
        else if(m_re == 0 && m_im < 0) angle = -90;
        else if(m_re > 0 && m_im == 0) angle = 0;
        else if(m_re < 0 && m_im == 0) angle = 180;
        else
        {
            angle = (360 / (2 * M_PI)) * atan(m_im / m_re);
            if(m_re < 0 && (m_im > 0 || m_im < 0)) angle += 180;
            else if(m_re > 0 && m_im < 0) angle += 360;
        }
    
        return angle;
    }
    


  • Bei solchen Fragen kommen viele Meinungen. Du hast dann die Qual der Wahl.

    Ich würde vielleicht auch eine Funktion nehmen, aber keine Memberfunktion, sondern eine lokal in einem anonymen Namespace definierte, der re und im übergeben werden.

    Begründung: wenn deine Membervariablen in der Klasse nicht in der richtigen Reihenfolge definiert werden, benutzt du in der Rechnung uninitialisierte Variablen. Die Reihenfolge im Konstruktor ist irrelevant! Übergibtst du dagegen der freien Funktion die Parameter des Konstruktors, ist die Reihenfolge egal.

    Alternative: du machst es wie in deinem zweiten Post, allerdings mit einer void Funktion, die den Wert m_angle direkt setzt und im Body des Konstruktors aufgerufen wird.



  • Das sollte keine Memberfunktion sein.

    Ich würde das auch nicht in einen anonymen namespace oder sonstiges packen.

    Ich würde einfach eine zusätzliche, freie Funktion definieren. Die berechnet aus einem Complex (oder meinetwegen auch re/im) den Phasenwinkel. Siehe std::arg.

    P.S.: Ich würde mir überlegen, ob es Sinn macht gleichzeitig beide Koordinatenformen zu speichern. Evtl. ist es besser, immer mit Re/Im zu rechnen.



  • @HarteWare

    Ich würde das auch nicht in einen anonymen namespace

    Du weißt, wozu der gut ist?



  • @manni66 Einhalten der ODR m. E. nach

    Ich dachte auch hier würde eine complex Klasse nachprogrammiert werden, aber "ComplexCalc" ist evtl. doch etwas anderes.



  • Die eigentliche Frage ist hier mal wieder nach dem Sinn des Projektes. Wie soll sich ComplexCalc von std::complex<double> unterscheiden?
    Warum werden m_magnitude und m_angle (noch dazu in Grad!) vorberechnet? Man kann z.B. auf das Wurzelziehen verzichten, wenn man nur die Beträge vergleichen will.

    Von daher: Die Antwort von @manni66 passt, wenn es im Allgemeinen um das Initialisierungsproblem geht. Im Speziellen hier hätte ich aber wohl auch zu einer veröffentlichten Funktion analog zu std::abs und std::arg gegriffen (oder diese als öffentliche Memberfunktionen implementiert) - zusammen mit einem Verzicht, die Werte vorzuberechnen. Außer natürlich diese Werte werden häufig gebraucht und die Aufgabe dieser Klasse ist das Speichern dieser Werte.



  • @wob sagte in Teilweise Initialisierungsliste möglich?:

    Die eigentliche Frage ist hier mal wieder nach dem Sinn des Projektes. Wie soll sich ComplexCalc von std::complex<double> unterscheiden?
    Warum werden m_magnitude und m_angle (noch dazu in Grad!) vorberechnet? Man kann z.B. auf das Wurzelziehen verzichten, wenn man nur die Beträge vergleichen will.

    Von daher: Die Antwort von @manni66 passt, wenn es im Allgemeinen um das Initialisierungsproblem geht. Im Speziellen hier hätte ich aber wohl auch zu einer veröffentlichten Funktion analog zu std::abs und std::arg gegriffen (oder diese als öffentliche Memberfunktionen implementiert) - zusammen mit einem Verzicht, die Werte vorzuberechnen. Außer natürlich diese Werte werden häufig gebraucht und die Aufgabe dieser Klasse ist das Speichern dieser Werte.

    Also mit std::complex bin ich nicht vertraut, die Klasse habe ich zu Übungszwecken erstellt. Mir ist klar, dass es in der Standard-Bibliothek bessere Lösungen gibt, aber ich denke als Übung kann eine eigens erstellte Lösung nicht schaden.



  • @alfons19 Ich finde es ehrlich gesagt erschreckend dass es (anscheinend?) erlaubt ist eine nicht-statische Memberfunktion in der Initialisierungsliste zu verwenden. Verwende lieber eine freie Funktion der du den Real- und Imaginärteil als Parameter mitgibst.



  • @hustbaer sagte in Teilweise Initialisierungsliste möglich?:

    Ich finde es ehrlich gesagt erschreckend dass es (anscheinend?) erlaubt ist eine nicht-statische Memberfunktion in der Initialisierungsliste zu verwenden.

    Jein. [class.base.init]/16
    Aber ich würds bleiben lassen.



  • @Swordfish sagte in Teilweise Initialisierungsliste möglich?:

    @hustbaer sagte in Teilweise Initialisierungsliste möglich?:

    Ich finde es ehrlich gesagt erschreckend dass es (anscheinend?) erlaubt ist eine nicht-statische Memberfunktion in der Initialisierungsliste zu verwenden.

    Jein. [class.base.init]/16
    Aber ich würds bleiben lassen.

    Ja, ich auch. Klar kann man sagen: darf man die Member die noch nicht initialisiert wurden halt in der Funktion dann nicht verwenden. Aber man kanns denke ich auch wirklich bleiben lassen.



  • Eigentlich wollte ich nur auf Dein "(anscheinend?)" antworten. Hätte vielleicht nicht den ganzen Satz zitieren sollen.



  • Hatte ich eh so verstanden. Ich wollte nur nochmal bekräftigen dass ich es auch für eine wirklich schlechte Idee halt.
    BTW: Danke für den Link. Ich hatte es nicht nachgesehen sondern nur ausprobiert, daher "anscheinend" 🙂



  • This post is deleted!


  • @hustbaer sagte in Teilweise Initialisierungsliste möglich?:

    @alfons19 Ich finde es ehrlich gesagt erschreckend dass es (anscheinend?) erlaubt ist eine nicht-statische Memberfunktion in der Initialisierungsliste zu verwenden. Verwende lieber eine freie Funktion der du den Real- und Imaginärteil als Parameter mitgibst.

    Was spricht denn dagegen? Ich habs jetzt mal so geändert:

    ComplexCalc::ComplexCalc(double re, double im)
        :m_re(re),
         m_im(im),
         m_magnitude(sqrt((re * re) + (im * im)))
    {
        computeAngle();
    }
    

    Wobei computeAngle() eine private Memberfunktion ist.

    void ComplexCalc::computeAngle()
    {
        if(m_re == 0 && m_im == 0) m_angle = 0;
        else if(m_re == 0 && m_im > 0) m_angle = 90;
        else if(m_re == 0 && m_im < 0) m_angle = -90;
        else if(m_re > 0 && m_im == 0) m_angle = 0;
        else if(m_re < 0 && m_im == 0) m_angle = 180;
        else
        {
            m_angle = (180 / M_PI) * atan(m_im / m_re);
            if(m_re < 0 && (m_im > 0 || m_im < 0)) m_angle += 180;
            else if(m_re > 0 && m_im < 0) m_angle += 360;
        }
    }
    


  • @alfons19 Weil irgendwann irgendein Irrer von ComplexCalc erbt und du dann eventuell in der Kacke sitzt. Ich mag jetzt nicht näher darüber nachdenken. Tu's nicht.



  • @Swordfish "Tu's nicht" kann man ja gerne vertreten, aber ich sehe beim besten Willen nicht, wie da irgendein Irrer Alfons in die Kacke setzen sollte.
    Ich lehne mich mal weit aus dem Fenster und behaupte: das macht 0 Unterschied, ob in dem Beispiel computeAngle frei oder member ist (bzgl. "gefährlich").



  • Der irre Alfons ist nicht das Problem.

    Das Problem ist dass du dann eine Memberfunktion hast, in der du auf bestimmte Member nicht zugreifen kannst ohne dadurch "undefined behavior" zu bekommen. Weil sie nämlich noch nicht konstruiert wurden. Um zu verstehen dass und warum das alles OK ist so wie es dasteht muss man viel zu viel wissen und sich über viel zu viele Dinge Gedanken machen.

    Die Zeit & Hirnzellen kann man sinnvoller nutzen.

    ps: Natürlich ist sowas auch "gefährlich". In diesem einfachen Beispiel vielleicht nicht so sehr. Aber im Allgemeinen schon. Denn wenn jemand Änderungen an der Klasse macht der sich nicht darüber im Klaren ist dass bestimmte Funktionen in der Initializerliste auf halb konstruierte Objekte aufgerufen werden und/oder was das bedeutet... dann kann dabei schnell UB rauskommen. Und nicht nur formales UB sondern ganz reales UB.



  • @alfons19 sagte in Teilweise Initialisierungsliste möglich?:

    Was spricht denn dagegen?

    Ein anderer wirklich dagegen sprechende Grund ist, dass du nun für jede komplexe Zahl, ob du willst oder nicht, Betrag und Phasenwinkel berechnest. Insbesondere sind sqrt und atan nicht unbedingt schnelle Operationen. Daher ist es oft sinnvoll, diese nicht schon "auf Verdacht" zu berechnen.

    Noch ein anderer Grund ist Testbarkeit: freie Funktionen lassen sich sehr einfach testen. Memberfunktionen nicht so leicht. Du kannst dein computeAngle nicht separat testen, sondern nur im Zusammenhang über eine ComplexCalc-Variable. Dabei hängt es nur von 2 doubles ab und nicht von m_magnitude. Du berechnest zwangsweise jedes mal m_magnitude mit, wenn du nur computeAngle willst. Wenn du dagegen aus computeAngle eine Funktion machst, die re und im als Parameter bekommt und einen double liefert, wird es schon viel klarer, was die Funktion tut und von welchen Inputs sie abhängt. Es ist oft hilfreich, exakt zu wissen, welche Werte in eine Funktion eingehen.

    Auf jeden Fall sollte computeAngle nicht die Membervariable ändern. Expliziter ist es, den Wert einfach zu returnen.


Log in to reply