Sind temporäre Objekte immer 'const'?



  • Hallo,

    ich bin gerade auf eine Frage bezüglich der constness von temporären Objekten gestoßen. Gleich mal vorab etwas code, dann die Fragestellung:

    #include <iostream>
    
    using namespace std;
    
    class Bruch
    {
        private:
        int m_Zaehler;
        int m_Nenner;
    
        public:
        Bruch( int = 1, int = 1 );
        int zaehler() const;
        int nenner() const;
        void setZaehler( int );
    
        Bruch operator * ( const Bruch & );
        friend ostream & operator << ( ostream &, const Bruch & );
    };
    
    Bruch::Bruch( int zaehler, int nenner )
    {
        m_Zaehler = zaehler;
        m_Nenner = nenner;
    }
    
    int Bruch::zaehler() const
    {
        return m_Zaehler;
    }
    
    int Bruch::nenner() const
    {
        return m_Nenner;
    }
    
    void Bruch::setZaehler( int zaehler )
    {
        m_Zaehler = zaehler;
    }
    
    Bruch Bruch::operator * ( const Bruch &bruch )
    {
        return Bruch( bruch.zaehler() * this->zaehler(), bruch.nenner() * this->nenner() );
    }
    
    ostream & operator << ( ostream &stream, const Bruch &bruch )
    {
        stream << bruch.zaehler() << "/" << bruch.nenner();
        return stream;
    }
    
    int main( int argc, char **argv )
    {  
        Bruch a( 3, 4 );
        Bruch b( 7, 8 );
    
        cout <<  a * b << endl;
    }
    

    In obiger Klasse ist der multiplikations Operator, sowie der Bitshift zwecks Stream Ausgabe überladen.

    Frage 1: Wenn ich in der Überladung von << den const qualifier des Bruch Parameters entfernen meckert der Compiler (in Zeile 58) daß er keine Überladung für einen Parameter vom Typ const Bruch findet - daraus schließe ich dass temporäre Objekte const sind?

    Frage 2: Wenn temporäre Objekte const sein sollten, warum kann ich dann folgenden Code ausführen ohne dass der Compiler meckert:

    ( a * b ).setZaehler( 3 );
    

    Damit würde ich ja bei einem konstanten Objekt eine Änderung durchführen. Wenn ich mir von vornherein eine Konstante Instanz anlege, schlägt obiger aufruf natürlich fehl:

    const Bruch b( 4, 5 );
    b.setZaehler( 3 ); // Compiler meckert logischerweise
    

    Könnte jemand etwas Licht ins Dunkel bringen?

    Danke und Grüße,
    Egon



  • Nein, sind sie nicht.
    Aber: Temporäre Objekte dürfen nur an const-Referenzen gebunden werden, nicht aber an normale Referenzen.
    Beispiel:

    int const   & i1 = 10; //geht
        int         & i2 = 10; //geht nicht
    

    Und zu Frage 2: Lass Deinen operator* mal einen const Bruch zurückgeben.



  • zu Frage 1) "schließe ich dass temporäre Objekte const sind" => Nein. Du musst const auch in Zeile 47 entfernen. Ausserdem gibt es keine temporaeren Objekte im Falle des ueberladenen stream-Operators.

    zu Frage 2) Temporaere Objekte muessen nicht const sein.

    Lass Deinen operator* mal einen const Bruch zurückgeben.

    Halte ich nicht fuer sinnvoll, da ich nach einer Multiplikation vielleicht noch was damit machen moechte (gut, in diesem Minimalbeispiel gibts da nicht viel).



  • knivil schrieb:

    zu Frage 1) "schließe ich dass temporäre Objekte const sind" => Nein. Du musst const auch in Zeile 47 entfernen.

    Nein, muss er nicht. Ausgabestreams ändern im Normalfall den Zustand des auszugebenden Objektes nicht. Dementsprechend sollte das auszugebende Objekt auch als const-Referenz übergeben werden.

    knivil schrieb:

    Ausserdem gibt es keine temporaeren Objekte im Falle des ueberladenen stream-Operators.

    Und wie lange existiert Deiner Meinung nach dann das Ergebnis von a * b ?



  • egrath schrieb:

    Frage 1: Wenn ich in der Überladung von << den const qualifier des Bruch Parameters entfernen meckert der Compiler (in Zeile 58) daß er keine Überladung für einen Parameter vom Typ const Bruch findet - daraus schließe ich dass temporäre Objekte const sind?

    Kannst du uns die Fehlermeldung des Compilers mal zeigen? Hast du daran gedacht nicht nur bei der Methodenimplementation sondern auch bei der friend-Deklaration das const zu entfernen? temporäre Objekte sind nicht per se const.

    Was deinen operator* angeht, den kannst du definitiv deutlich verbessern, z.B. dass er nicht beide argumente const lässt ist relativ unsinnig.
    <Werbung> Im Artikel-Forum (bzw. im Magazin) gibts einen Artikel zur Operatorüberladung in C++, der kann dir da weiter helfen 😉 </Werbung>



  • Hallo,
    danke für die Auskunft, jetzt ist mir einiges klarer!
    Grüße,
    Egon



  • Tachyon schrieb:

    knivil schrieb:

    Ausserdem gibt es keine temporaeren Objekte im Falle des ueberladenen stream-Operators.

    Und wie lange existiert Deiner Meinung nach dann (a * b)?

    Ich bezog mich nur auf den stream-Operator.

    Nein, muss er nicht. Ausgabestreams ändern im Normalfall den Zustand des auszugebenden Objektes nicht. Dementsprechend sollte das auszugebende Objekt auch als const-Referenz übergeben werden.

    Er sollte schon konsistente Signaturen verwenden. Sinnvoll ist es in der Tat nicht, ausser man moechte direkt auf die Member zugreifen und nicht den Weg ueber getter-Methoden nehmen.



  • pumuckl schrieb:

    Kannst du uns die Fehlermeldung des Compilers mal zeigen?

    Hallo pumuckl,
    wenn ich const aus der deklaration und der implementierung entferne (Zeile 18 u. 47), gibt mir der compiler folgende Fehlermeldung:

    test.cc: In function ‘int main(int, char**)’:
    test.cc:58: error: no match for ‘operator<<’ in ‘std::cout << a.Bruch::operator*(((const Bruch&)((const Bruch*)(& b))))’
    /usr/include/c++/4.3/ostream:112: note: candidates are: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>& (*)(std::basic_ostream<_CharT, _Traits>&)) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/ostream:121: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ios<_CharT, _Traits>& (*)(std::basic_ios<_CharT, _Traits>&)) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/ostream:131: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::ios_base& (*)(std::ios_base&)) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/ostream:169: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(long int) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/ostream:173: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(long unsigned int) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/ostream:177: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(bool) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/bits/ostream.tcc:97: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(short int) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/ostream:184: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(short unsigned int) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/bits/ostream.tcc:111: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(int) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/ostream:195: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(unsigned int) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/ostream:204: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(long long int) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/ostream:208: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(long long unsigned int) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/ostream:213: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(double) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/ostream:217: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(float) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/ostream:225: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(long double) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/ostream:229: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(const void*) [with _CharT = char, _Traits = std::char_traits<char>]
    /usr/include/c++/4.3/bits/ostream.tcc:125: note:                 std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_streambuf<_CharT, _Traits>*) [with _CharT = char, _Traits = std::char_traits<char>]
    test.cc:47: note:                 std::ostream& operator<<(std::ostream&, Bruch&)
    

    Grüße,
    Egon



  • egrath schrieb:

    ...

    Wie gesagt, Du kannst temporäre Objekte nicht an non-const-Referenzen binden.

    Beispiel:

    void test(int & a)
    {
        a = 20; //moeglich, a ist non-const
    }
    
    //angenommen, Folgendes wuerde gehen:
    test(10);
    //wie sieht der Wert hier nach aus?
    


  • Ja, Tachyon hat recht. Hab es grad selbst ausprobiert, wobei ich die Logik dahinter nur teilweise nachvollziehen kann.



  • knivil schrieb:

    ...wobei ich die Logik dahinter nur teilweise nachvollziehen kann.

    Das liegt hauptsächlich daran, dass "temporäre Objekte" auch solche Dinge wie Literale sein können (siehe mein Beispiel). Wenn Du sowas non-const übergeben könntest wäre es möglich, innerhalb einer Funktion das Literal zu befrickeln -> undefiniertes Verhalten. Außerdem sind die Ergebnisse von Operationen mit POD-Typen immer L-Werte.



  • Das sehe ich ein, aber bei "richtigen" Objekten ... Naja, lieber einheitlich als fuer jeden scheiss 'ne Ausnahme.



  • knivil schrieb:

    Das sehe ich ein, aber bei "richtigen" Objekten ... Naja, lieber einheitlich als fuer jeden scheiss 'ne Ausnahme.

    Naja, eine Funktion weiss ja nicht, ob ein Literal, ein temporäres Objekt oder eine Variable übergeben wird. Denke mal an Funktionen in dlls, die zur Laufzeit gebunden werden. Wie willst Du da unterscheiden?



  • Referenzen müssen ursprünglich wie Zeiger auf etwas verweisen. Bei den Const-Referenzen kann das auch ein temporäres Objekt sein.

    Das hat unter anderem den Vorteil für Defaultparameter:

    void Function(const std::string& Text = "Hallo Welt");
    

Log in to reply