Standardkonstrukor



  • Eine dumme Frage. Was hält ihr von const Rückgabewerten?

    Ab und zu sehe ich nämlich Stilblüten in der folgenden Form.

    const bool Foo(const int a, const bool& b = true)
    


  • Der Gedanke hinter dem Code scheint dabei folgendermaßen zu sein: "Const Correctness = Ich mach mal überall const dran bis der Compiler meckert"



  • @Quiche-Lorraine

    Hier (mit bool) sehe ich zwar keinen Nutzen, aber wenn der Rückgabewert kein bool ist, sondern eine Klasse, dann macht das const schon Sinn.


  • Mod

    @Quiche-Lorraine sagte in Standardkonstrukor:

    Eine dumme Frage. Was hält ihr von const Rückgabewerten?

    Ab und zu sehe ich nämlich Stilblüten in der folgenden Form.

    const bool Foo(const int a, const bool& b = true)
    

    Gefährliches, veraltetes Halbwissen. Die Absicht war, vor langer, langer Zeit einmal, dass man Vollnoobs, die sowieso nichts damit anfangen könnten, vor an den Haaren herbei gezogenen Fehlern zu schützen. A la

    const MyClass foo();
    
    foo() = MyClass();  // Würde dadurch verhindert
    

    Würde niemand mit mehr als 5 Minuten Erfahrung jemals auch nur auf die Idee kommen, muss man also eigentlich gar keine Rücksicht drauf nehmen.

    Die Sache ist aber gefährlich geworden mit Move-Semantik, wo es jetzt richtige, sinnvolle Optimierungen in normalem Code verhindert, für den oben genannten nicht-vorhandenen Mehrwert.
    Siehe auch: https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rf-out

    Wenn man das also sieht, weiß man, dass man es mit jemandem zu tun hat, der seit 15 Jahren (C++11 ist viel älter als 2011…) nichts gelernt hat; und auch vorher schon war der const-Rückgabewert von fragwürdigem Nutzen.



  • @SeppJ sagte in Standardkonstrukor:

    vor an den Haaren herbei gezogenen Fehlern zu schützen

    Wobei dein Beispiel ja jetzt extra schlecht gewählt ist. Besser:

    struct A
    {
    ...
            A() = default;
            A(int pi)
    };
    A foo() {...}
    ...
    foo() = 42 // Tippfehler, wollte == schreiben. Kompiler ist aber glücklich
    

    Wollte jetzt hier aber auch keine Werbung für das const machen.
    Ich blätter von Zeit zu Zeit immer mal im alten Meyers und war das const noch als Tipp beim +-operator, daher bin ich da überhaupt erst drauf gekommen.


  • Mod

    @Jockelx sagte in Standardkonstrukor:

    @SeppJ sagte in Standardkonstrukor:

    vor an den Haaren herbei gezogenen Fehlern zu schützen

    Wobei dein Beispiel ja jetzt extra schlecht gewählt ist. Besser:

    struct A
    {
    ...
        int operator()(const A& a);
    };
    A foo() {...}
    ...
    foo() = 42 // Tippfehler, wollte == schreiben. Kompiler ist aber glücklich
    

    Es gibt gleich mehrere Gruende aus denen das nicht kompiliert, nicht zuletzt weil ein Zuweisungsoperator immer als Member aufgerufen wird.

    For the operators =, [], or ->, the set of non-member candidates is empty;

    Vielleicht wolltest Du eher einen converting ctor von int deklarieren?

    Das man prvalues von Klassentyp ueberhaupt etwas zuweisen kann, ist nur fuer ausgekluegelte Proxies gedacht. Fuer gewoehnliche Klassen sollte der operator= wahrscheinlich per ref-qualifier beschraenkt werden (was aber in der Praxis natuerlich niemand macht).


  • Mod

    @john-0
    Tatsaechlich habe ich mich gerade mit einem Arbeitskollegen unterhalten, der mir erklaert hat, dass in Fortran die Etiquette besteht, dass man nie einen Parameter aendern sollte, gerade weil dies zu boesen Fehlern fuehrt, wenn man Konstanten etc. als Argumente hat (und ganz frueher, aber das war in den 90ern schon folklore, konnte man auf bestimmten Maschinen sogar die integer literals aendern, weil sie im Datensegment gespeichert waren).
    Ein Fortran Fanatiker wuerde also das const sehen und sich nichts dabei denken, weil er sowieso annehmen wuerde, dass in einer Sprache, in der alles by reference ist, dieselbe Konvention aus den selben Gruenden gilt. 💡



  • @Columbo sagte in Standardkonstrukor:

    Vielleicht wolltest Du eher einen converting ctor von int deklarieren?

    Äh, keine Ahnung, wo ich da gerade war...aber ja, danke, das wollte ich. Habe es oben korrigiert.

    Edit:
    Naja, ich sehe gerade, dass ich da doch etwas rum basteln muss, um ein nicht zu-sehr-konstruiertes Beispiel zu kriegen.
    Da ich aber wie gesagt, ebenfalls das const da nicht haben will, habe ich auch keine Lust mehr was zu basteln.



  • Ich habe jetzt aber doch nochmal im Meyers (effective c++, item 3) nachgeschaut:
    Da sagt er, man solle den *-Operator einer 'Rational'-Klasse so deklarieren:

    const Rational operator*(const Rational& lhs, const Rational& rhs);
    

    Ist das einer der wenigen Tipps die Mist sind in dem Buch oder übersehe ich irgendwas, weil es ein operator ist und keine allgemeine Funktion?
    (und klar, das Buch ist pre-c++11, was den Tipp vielleicht damals noch legitimer machte).


  • Mod

    @Jockelx Der Grund ist doch schon genannt worden: Man soll nicht versehentlich r * s = t schreiben koennen. Allerdings waere Meyers sicherlich einverstanden, statt dem Rueckgabetyp jedes Operators const beizufuegen, einfach dem Zuweisungsoperator einen ref-qualifier zu verpassen:

    Rational& operator=(const Rational&) &;
    Rational& operator=(long) &;
    // ...
    

    Es ist tatsaechlich etwas enttäuschend, dass ein altes Proposal, welches jedem stdlib Zuweisungsoperator einen ref-qualifier verpassen sollte, vom LWG abgelehnt wurde.



  • @Columbo sagte in Standardkonstrukor:

    Der Grund ist doch schon genannt worden

    Ja, unter anderem von mir. Allerdings gab es die Behauptung von immerhin @SeppJ

    @SeppJ sagte in Standardkonstrukor:

    Gefährliches, veraltetes Halbwissen. Die Absicht war, vor langer, langer Zeit einmal, dass man Vollnoobs, die sowieso nichts damit anfangen könnten, vor an den Haaren herbei gezogenen Fehlern zu schützen.
    Würde niemand mit mehr als 5 Minuten Erfahrung jemals auch nur auf die Idee kommen, muss man also eigentlich gar keine Rücksicht drauf nehmen.

    Und da idR weder Meyers noch @SeppJ unsinn erzählen, wollte ich wissen, ob es sich bei operatoren anders verhält. Aber die Antwort hast du ja schon mit deinem Proposal gegeben, dass es zumnindest von mehreren im operator-Fall als problematisch angesehen wird.


  • Mod

    Ich bin nicht mit @SeppJ einverstanden, dass der Fehlermodus 'an den Haaren herbei gezogen' ist, was die Autoren des von mir angeführten proposals ähnlich gesehen haben (genau wie Meyers).

    Allerdings muss man eben auch einraeumen, dass der Fehlermodus in der Praxis selten auftreten kann, weil a) Compilerwarnungen, und b) es weitere Annahmen erfordert. Bspw. kann

    if (foo() = MyClass()) 
    

    überhaupt nur wohlgeformt sein wenn MyClass nach bool konvertierbar ist. Deshalb hat Meyers mit Rational ein treffliches Beispiel konstruiert, da Rational womöglich implizit nach double konvertierbar sein könnte. Per se sind das die meisten Klassen aber nicht.


  • Mod

    @Jockelx sagte in Standardkonstrukor:

    @Columbo sagte in Standardkonstrukor:

    Der Grund ist doch schon genannt worden

    Ja, unter anderem von mir. Allerdings gab es die Behauptung von immerhin @SeppJ

    @SeppJ sagte in Standardkonstrukor:

    Gefährliches, veraltetes Halbwissen. Die Absicht war, vor langer, langer Zeit einmal, dass man Vollnoobs, die sowieso nichts damit anfangen könnten, vor an den Haaren herbei gezogenen Fehlern zu schützen.
    Würde niemand mit mehr als 5 Minuten Erfahrung jemals auch nur auf die Idee kommen, muss man also eigentlich gar keine Rücksicht drauf nehmen.

    Und da idR weder Meyers noch @SeppJ unsinn erzählen, wollte ich wissen, ob es sich bei operatoren anders verhält. Aber die Antwort hast du ja schon mit deinem Proposal gegeben, dass es zumnindest von mehreren im operator-Fall als problematisch angesehen wird.

    Früher war's halt ein Vorteil in konstruierten Situationen, aber hatte auch keine Nachteile. Daher konnte man es halt machen, wenn man wollte, braucht man aber auch nicht.

    Post-C++11 ist es aber aktiv gefährlich.



  • @Columbo sagte in Standardkonstrukor:

    @john-0
    Tatsaechlich habe ich mich gerade mit einem Arbeitskollegen unterhalten, der mir erklaert hat, dass in Fortran die Etiquette besteht, dass man nie einen Parameter aendern sollte, gerade weil dies zu boesen Fehlern fuehrt, wenn man Konstanten etc. als Argumente hat (und ganz frueher, aber das war in den 90ern schon folklore, konnte man auf bestimmten Maschinen sogar die integer literals aendern, weil sie im Datensegment gespeichert waren).

    Das stimmt eher nicht. Beispiel finden sich etwa in der offiziellen BLAS Referenzimplementation z.B. DGEMM. Es war usus im Kopf der Routine das zu dokumentieren, so dass man wusste wie ein Parameter genutzt werden darf. Aber da reden wir über Fortran77 und älter, und bei so altem Fortran wurden meistens COMMON Blöcke genutzt, bei denen das Problem ohnehin nicht existiert. Ab Fortran90 gibt es das Intent Statement, mit dem man das auch durch den Compiler prüfen lassen kann.
    Das sah dann ungefähr so aus.

    C     OLD FORTRAN STYLE
    C     THIS SHORT PROGRAM DEMONSTRATE PARAMETER HANDLING WITHIN FORTRAN
    C     SUBROUTINES AND FUNCTIONS
    
          COMMON/FOOCOM/ MODE,RCUT
          COMPLEX Z
          DATA Z /(1.0,3.141592654)/
    
          CALL FOO(Z)
    
          WRITE(*,*) MODE,RCUT,Z
    
          END PROGRAM
    
          SUBROUTINE FOO(Z)
    C     IN OUT: Z, /FOOCOM/ RCUT
    C     IN: /FOOCOM/ MODE
          COMPLEX Z
    C   Die Definition des COMMON Blocks und der Konstante würde man bei größeren Programmen in eine Include datei auslagern.
          PARAMETER (PI=3.141592654)
          COMMON/FOOCOM/ MODE,RCUT
    
          IF(MODE.EQ.1)THEN
          RCUT=RCUT*PI
          Z=Z**2
          ELSE
          RCUT=RCUT*2*PI
          Z=Z**0.5
          ENDIF
          END SUBROUTINE
    
          BLOCK DATA
          COMMON/FOOCOM/ MODE,RCUT
          DATA MODE,RCUT /1,3.0/
          END
    

    Im aktuellen Fortran wurde man das Programm eher im folgenden Stil formulieren.

    program param_new
        use iso_fortran_env
        implicit none
    
        real(kind=real64), parameter :: PI = 3.141592654
        real(kind=real64) :: rcut = 3.0
        integer :: mode = 1
        complex(kind=real64) :: z = (1.0,PI)
    
        call foo(mode, rcut, z)
    
        WRITE(*,*) MODE,RCUT,Z
    contains
        subroutine foo(mode,rcut,z)
            integer, intent(in) :: mode
            real(kind=real64), intent(in out) :: rcut
            complex(kind=real64), intent(in out) :: z
    
            if (mode == 1) then
                rcut = rcut * PI
                z = z ** 2
            else
                rcut = rcut * 2 * PI
                z = z ** 0.5
            end if
        end subroutine foo
    end program param_new
    

    Ein Fortran Fanatiker wuerde also das const sehen

    Nein, da man das in der Regel so nicht gemacht hat. Es gibt genügend Beispiel im Netz wie man das früher gemacht hat, die Variante, die dein Kollege ansprach, ist eher ungewöhnlich.


Anmelden zum Antworten