Copy-Constructor und operator*



  • Weil er nicht gerufen wird?

    int main() 
     { 
         dummy a; 
         dummy b; 
         dummy c( a * b);
         return 0; 
     }
    

    MfG Kimmi



  • eine classe mit einem konstruktor z.b. myclass(int)

    kann entweder mit
    myclass a(5)
    oder
    myclass a = 5 aufgerufen,

    darum wird der nicht aufgerufen



  • kimmi schrieb:

    Weil er nicht gerufen wird?

    int main() 
     { 
         dummy a; 
         dummy b; 
         dummy c( a * b);
         return 0; 
     }
    

    MfG Kimmi

    Und wo ist der Unterschied zu

    int main() 
     { 
         dummy a; 
         dummy b; 
         dummy tmp;
         tmp = ( a * b);
         dummy c = tmp;
         return 0; 
     }
    

    ... wo dann der CopyCtor auferufen wird ? Macht es soviel aus, wenn operator*() ein temporäres Objekt erzeugt ?

    Gruß,

    Simon2.


  • Mod

    Simon2 schrieb:

    Aber warum wird der CopyCtor genutzt, wenn operator*() eine "dummy const&" zurückgibt ?

    Denk mal über

    typedef const dummy const_dummy;
    int main()
    {
        dummy x = dummy();
        dummy y = const_dummy();
    }
    

    nach hinsichtlich der Voraussetzungen für die Auslassung des CopyOps.



  • camper schrieb:

    ...der Voraussetzungen für die Auslassung des CopyOps.

    Leider kenne ich nicht "die Voraussetzungen für die Auslassung des CopyOps"...

    ... und warum der CopyCtor ausgelassen wird, weiß ich auch nicht.

    Gruß,

    Simon2.



  • Für mich riecht das ganz klar nach RVO. Eigentlich sollte ein Compiler bei dem Code mit ausgeschalteten Optimierungen einen Aufruf des copy-ctor erzeugen, aber egal.



  • hustbaer schrieb:

    ...aber egal.

    Finde ich jetzt nicht .... immerhin schien camper eine Lösung zu haben (die er leider noch nicht aufgedeckt hat).
    Außerdem sollte RVO doch Seiteneffekte erkennen/berücksichtigen .. das tut sie aber im vorliegenden Fall nicht (habe mal spaßeshalber einen CopyCtor mit Seiteneffekt gebastelt und der wird auch nicht aufgerufen).
    Und schließlich ist es IMO kein sooo exotischer Fall, dass man sowieso nicht in diese Falle tappen könnte.

    Wäre schon schön, wenn hier klar würde, wo der (Denk-)fehler liegt.

    Gruß,

    Simon2.



  • Ich nehme an RVO ist eine selbstverständliche Optimierung und keine, die man je nach Bedarf einschalten können soll (wäre imo auch logisch gesehen gut).

    Was das Beispiel angeht, hat Simon2 ja schon gemutmaßt, dass es sich um RVO handelt. Hier wird das Objekt tmp (welches im operator* erzeugt und zurückgegeben wird) direkt in c hineinkonstruiert, es wird also kein Copy-Ctor benötigt.

    Dummy c = a*b; // äquivalent zu
    Dummy c( a*b ); // äquivalent zu
    Dummy c( a.operator*( b ) );
    
    // Pseudocode für operator*
    Dummy operator*( Dummy const& left, Dummy const& right ) 
        returns: Dummy tmp;
    {
    }
    
    // Ablauf nach RVO:
    Dummy c; 
    operator*( c, a, b );
    void operator*( Dummy& target, Dummy const& left, Dummy const& right )
    {
    }
    

    Dummy "c" ist also Dummy "tmp", weshalb nirgendwo etwas kopiert werden muss.

    Bei der Rückgabe einer (konstanten) Referenz hingegen sollte klar sein, dass die Möglichkeit nicht besteht, die Initialisierung des Objekts in den Operator zu verlegen, da eine Rückgabe einer Referenz auf ein lokales Objekt undefiniert wäre. Demnach muss das zurückgegebene Objekt ausserhalb der Funktion existieren (this-Objekt, global, jedoch nicht im Aufrufer, es seidenn der Operator gibt einen der Operanden zurück ...), wodurch dessen Konstruktion nicht von der Funktion in den Aufrufer verlegt werden kann, wodurch eine Kopie erforderlich wird.

    Dummy c( a*b ); // Kopie erforderlich
    Dummy const& operator*( Dummy const& left, Dummy const& right )
    {
        return somethingNotLocal;
    }
    

    PS: Dieser Beitrag dient auch für mich selbst dazu, die Zusammenhänge auseinanderzuklamüsern. Sollte hier erwiesenermassen Quatsch stehen würde ich Euch (camper) bitten, das möglichst klarzustellen 😉



  • Hi,

    mir scheint's auch immer mehr RVO zu sein...

    allerdings bin ich ein wenig beunruhigt, weil es mein Verständnis von der Konstruierei doch ein wenig durcheinanderwirbelt.

    Letztlich bleibt:

    dummy f() { ... };
    
    int main() {
       dummy a = f();
    ...
    

    In dem Fall bestimmt nicht "der Erzeuger von a", welcher Konstruktor aufgerufen wird (der evtl. erwartete CopyCtor), sondern der Implementierer von f().
    Das beschränkt ein wenig die Möglichkeiten des dummy-Entwicklers, weil hier RVO wohl letztlich von einer "Standard-Copy-Konstruktion" (also Objekte sind hinterher im Speicher identisch) ausgeht und irgendwelche exotischen Seiteneffekte (fällt mir gerade kein Beispiel ein; Referenzcounting würde sich daran ja nicht stören) nicht einbauen kann.
    Naja, immerhin kann er das Kopieren noch ganz verbieten ...

    Hmmm strange....

    Gruß,

    Simon2.



  • Sieh es doch mal so.Die Compiler sind jetzt so intelligent, dass sie Dinge optimieren an die du nie denken würdest, egal ob du das so willst oder nicht. 🙂
    Ich hatte den Test auch gemacht und mich schon gewundert, das der g++ im debugmodus den CopyCTor rausoptimiert. Sobald im Operator* etwas vernünftiges stand war er aber wieder drin.



  • Hi,

    was war denn "etwas Vernünftiges" ?

    Gruß,

    Simon2.



  • dummy operator*( const dummy & other )
    {
        //code
        cout << "Operator*" << endl;
        dummy tmp(other); //lokales Obj für Matrixmultiplikation
                    // mache multiplikation
        return tmp;
    };
    


  • Braunstein schrieb:

    dummy operator*( const dummy & other )
    {
        //code
        cout << "Operator*" << endl;
        dummy tmp(other); //lokales Obj für Matrixmultiplikation
                    // mache multiplikation
        return tmp;
    };
    

    Naja, den CopyCtor explizit aufrufen, kann ja jeder.... 😉
    ... und RVO findet ja trotzdem statt.

    Mein "Vernünftiges" sah nämlich so aus:

    class dummy {
       int i;
    // ...
    };
    
    dummy operator*( const dummy & other )
    {
        //code
        cout << "Operator*" << endl;
        dummy tmp;
        tmp.i = i * other.i;
        return tmp;
    };
    

    .... und da wird KEIN CopyCtor aufgerufen, obwohl es mir als sehr vernünftige Implementation dieses operators erschien... 😃

    Gruß,

    Simon2.



  • Das sehe ich auch so. ....... jetzt! 😃
    Obwohl die Verwendung des CopyCtor hier den weiteren Aufruf eines Default-Konstrukltors bedingen würde (für das temporäre Objekt). Sonst gäbe es ja nichts zum kopieren. Und genau das scheint der Compiler ja wegzuoptimieren.
    Ich gebe zu, der direkte Aufruf des CopyCTor war kein so gutes Beispiel.



  • Simon2 schrieb:

    Außerdem sollte RVO doch Seiteneffekte erkennen/berücksichtigen ..

    Wäre mir neu. Wie kommst du darauf?
    RVO ist nur eine der ganzen Stellen wo der Programmierer nicht genau wissen kann wo bzw. wie oft seine Objekte rumkopiert werden...

    Ein Copy-Ctor muss - damit eine Klasse in C++ sinnvoll einsetzbar ist - immer so geschrieben sein dass

    Foo a = x;
    

    zu

    Foo tmp = x;
    Foo a = tmp;
    

    äquivalent ist -- also was x und a angeht.



  • Also ich verwende VS2005.NET Standardedition.
    Und wenn ich

    #include <iostream> 
    
    using namespace std; 
    
    class dummy 
    { 
        public: 
            dummy() 
            { 
                cout << "Default-Constructor" << endl; 
            }; 
            dummy( const dummy& other ) 
            { 
                cout << "Copy-Constructor" << endl; 
                            //mache deep copy 
            }; 
            dummy operator*( const dummy & other ) 
            { 
                //code 
                cout << "Operator*" << endl; 
                dummy tmp; //lokales Obj für Matrixmultiplikation 
                            // mache multiplikation 
                return tmp; 
            }; 
            dummy& operator=(const dummy& other) 
            { 
                cout << "Operator=" << endl; 
    
                            //erstelle deep-copy etc. 
    
                return *this; 
            }; 
    }; 
    
    int main() 
    { 
        dummy a; 
        dummy b; 
        dummy c = a * b; 
        return 0; 
    }
    

    mit den Standardeinstellungen compiliere, sieht die Ausgabe so aus:

    Default-Constructor
    Default-Constructor
    Operator*
    Default-Constructor
    Copy-Constructor

    Also bei mir wird der Copykonstruktor demnach aufgerufen oO.



  • Kahino schrieb:

    Also ich verwende VS2005.NET Standardedition.
    Und wenn ich [...] mit den Standardeinstellungen compiliere [wird bei mir] der Copykonstruktor ... aufgerufen oO.

    Hast Du im Debug- oder im Release-Modus kompiliert? Im Release-Modus ist der Standard für die Optimierungen in VS 2005 /O2, also Optimierung auf Geschwindigkeit. Bei Debug-Modus hingegen werden standardmäßig keine Optimierungen vorgenommen.



  • hustbaer schrieb:

    Simon2 schrieb:

    Außerdem sollte RVO doch Seiteneffekte erkennen/berücksichtigen ..

    Wäre mir neu. Wie kommst du darauf?...

    Intuition. 😃

    ... aber offensichtlich eine falsche.

    hustbaer schrieb:

    ...
    Ein Copy-Ctor muss - damit eine Klasse in C++ sinnvoll einsetzbar ist - immer so geschrieben sein dass

    Foo a = x;
    

    zu

    Foo tmp = x;
    Foo a = tmp;
    

    äquivalent ist -- also was x und a angeht.

    Jupp, genau darauf sind wir hier (habe mit Arbeitskollegen eine zeitlang darüber diskutiert) inzwischen auch gekommen.

    Letztlich hatte ich außerdem gedacht, dass RVO ein "Sprachfeature" sei und weniger eine "abschaltbare Optimierungsoption" ... .

    Das war mir eben neu.

    Danke,

    Simon2.



  • Konrad Rudolph schrieb:

    Kahino schrieb:

    Also ich verwende VS2005.NET Standardedition.
    Und wenn ich [...] mit den Standardeinstellungen compiliere [wird bei mir] der Copykonstruktor ... aufgerufen oO.

    Hast Du im Debug- oder im Release-Modus kompiliert? Im Release-Modus ist der Standard für die Optimierungen in VS 2005 /O2, also Optimierung auf Geschwindigkeit. Bei Debug-Modus hingegen werden standardmäßig keine Optimierungen vorgenommen.

    Ist das beim gcc eigentlich auch abschaltbar ?
    Noopt (-O0) habe ich eigentlich angeschaltet.

    Gruß,

    Simon2.


  • Mod

    RVO und allgemein die Auslassung des Copy-Ctors sind keine solchen Optimierungen, die man üblicherweise durch Compileroptionen beeinflussen kann. Bei letzteren geht es i.allg. um die Veränderungen von Parametern des Programmes, die durch den Standard nicht festgelegt werden (wie etwa die Dauer des Programmablaufs) oder jedenfalls weitgehend der Implementation vorbehalten sind (etwa das genaue Verhalten von Gleitkommaoperationen). Dem gegenüber verändert die Auslassung des Copy-Ctors das beobachtbare Verhalten i.S.d. Standards. Sie verändert also die Interpretation des Programmes. Es ist also tatsächlich - wenn man diese Einteilung wünscht - ein Merkmal der Sprache und keine Fähigkeit des Compilers an sich.

    Was brauchen wir für die Auslassung des Copy-Ctors?
    Wenn der Copy-Ctor ausgelassen wird, so nimmt das zu initialisierende Objekt die Identität des initialisierenden Ausdrucks an, im Gegenzug wird letzterer nicht (vorzeitig) zerstört. Die semantische Veränderung des Programmes darf aber nur im Nichtausführen eben des Copy-Ctors bzw. Destruktors bestehen. D.h. kann man zu irgendeinem Zeitpunkt sowohl auf den initialisierenden Ausdurck als auch auf das zu initialisierende Objekt gleichzeitig zugreufen, dann müssen diese logisch getrennt - und eben nicht als identisch im Objekt - erscheinen, die Auslassung des Copy-Ctors ist dann folglich ausgeschlossen. Schlussfolgerung:
    Der initialisierende Ausdruck muss stets ein temporäres Objekt sein. Zudem - das dürfte ohnehin klar sein - müssen die dynamischen Typen abgesehen von cv-Qualifikation (soweit diese cv-Qualifikation die Aufrufbarkeit des Copy-Ctors nicht berührt) identisch sein. Damit sind Referenzen (als Initialisier) in doppelter Hinsicht ausgeschlossen: Einerseits sind es keine temporären Objekte, andererseits gibt es für die Übereinstimmung des dynamischen Typs keine Garantie. Sind diese beiden Bedingungen erfüllt:
    - temporäres Objekt
    - identischer Typ
    kann der Copy-Ctor überall dort ausgelassen werden, wo der Standard das vorsieht. RVO ist hier im Grunde nur ein Spezialfall. NRVO ist eine Erweiterung, dort ist der initialisierende Ausdruck zwar kein temporäres Objekt, würde aber nach dem Return-Statement ohnehin zerstört werden.


Anmelden zum Antworten