[vertagt] Copy-Constructor bei Call-by-Value



  • erik.vikinger schrieb:

    Dravere schrieb:

    Bei C++ gibt es keinen ABI Standard.

    😮 Echt? Nichtmal grobe Guide-Lines? Ich hab z.B. mal in einer ABI-Spezifikation (glaube für ARM) gelesen wie der this-pointer bei Methodenaufrufen übergeben werden soll, hatte mich damals aber nicht weiter damit beschäftigt, und bin daher davon ausgegangen das C++ ähnlich vollständig geregelt ist wie C. Gibt es denn wenigstens ein paar C++-Regeln für Call-by-Value mit Klassen?

    Alles, was man haben und erfüllen muss, um einen C++ Compiler zu bauen, ist der C++ Standard und da wird keinerlei Implementierungsdetail besprochen, geschweige den etwas, dass sich auf asm Ebene befindet. Du musst dich aber genau an den Standard halten und das impliziert bei gewissen Sachen eine übliche Implementierung. Aber der Punkt ist, dass man da völlig frei ist und den Standard einhält, kann man jegliche Optimierung machen.

    erik.vikinger schrieb:

    Dravere schrieb:

    Nicht ohne Grund wünsche ich mir schon lange vom C++ Standard Komittee einen C++ ABI Standard 🙂

    Ich jetzt auch. Ist denn sowas schon in Arbeit?

    Ich bezweifle das stark und ich bezweifle auch, dass das Stanrdisierungskomittee die richtigen Leute dafür wären. Im Moment haben sie genug zu tun mit dem C++0x. 😉 - Sich jetzt noch mit solchen Detailfragen zu beschäftigen wäre einfach zu viel, wenn man bedenkt, wie lange es geht bis alleine die Sprache genügend für einen Standard beschrieben ist, dann kann man das gut verstehen.



  • Hallo,

    drakon schrieb:

    Alles, was man haben und erfüllen muss, um einen C++ Compiler zu bauen, ist der C++ Standard und da wird keinerlei Implementierungsdetail besprochen,

    Verstanden.
    Ist denn zumindest geregelt wer (caller oder callee) bei Call-by-Value den Copy-Constructor aufruft? Wie wird das eigentlich beim Return-Value gemacht? Zumindest ein paar Dinge müssten dazu doch im C++-Standard stehen. Oder muss man sich das aus allgemeinen Handhabungsregeln für Klassen herleiten?

    Grüße
    Erik



  • SeppJ schrieb:

    So ein technischer Kram steht nicht im Standard, das ist alleine eine Sache für den Compilerhersteller. Warum sollte den Programmierer sowas interessieren? Der Compilerhersteller wird schon wissen, was er da tut.

    Das reicht doch als Antwort oder?

    Schau doch einfac selber in den Standard und du wirst sehn, dass der sich nicht mit solchen technischen Einzelheiten aufhält.


  • Mod

    erik.vikinger schrieb:

    Ist denn zumindest geregelt wer (caller oder callee) bei Call-by-Value den Copy-Constructor aufruft?

    Wer? Das würde der Computer sein.
    Die Frage verstehe ich in mehr als einer Hinsicht nicht. Der Standard spezifiziert, welche Nebeneffekte ein bestimmtes Stück Programmcode hat und ggf. in welcher Reihenfolge diese Effekte auftreten, wenn und soweit es das beobachtbare Verhalten des Programmes beeinflusst.
    Eine dieser Regeln (5.2.2/8) besagt:

    [...] All side effects of argument expression evaluations take effect before the function is entered. [...]

    Das ist allerdings nicht unbedingt die geforderte Antwort.


  • Administrator

    erik.vikinger schrieb:

    Dravere schrieb:

    Wie? Wieso muss ich das bei einem Smart-Pointer wissen? Leuchtet mir nicht so wirklich ein.

    Ich dachte der smart-pointer muss genau wissen das und wie er kopiert wird damit er das referenzierte Objekt freigeben kann wenn es keinen smart-pointer mehr gibt der darauf verweist.

    Das ist die Implementation eines Shared Pointers (es gibt noch andere Smart Pointer). Allerdings geschieht dies alles auf C++ Ebene. Ein Shared Pointer hat seinen eigenen operator= und Kopierkonstruktor. Darin wird der Zähler inkrementiert und dann im Destruktor dekrementiert. Wenn er nach dem dekrementieren 0 ist, so wird das Objekt gelöscht, auf welches er zeigt.

    Eine ganz billige Implementation:

    template<typename T>
    class SharedPtr
    {
    private:
      int* m_counter;
      T* m_object;
    
    public:
      explicit SharedPtr(T* ptr)
        : m_counter(new int(1))
        , m_object(ptr)
      {
      }
    
      SharedPtr(SharedPtr const& obj)
        : m_counter(obj.m_counter)
        , m_object(obj.m_object)
      {
        ++*m_counter;
      }
    
      ~SharedPtr()
      {
        --*m_counter;
    
        if(!*m_counter)
        {
          delete m_object;
          delete m_counter;
        }
      }
    
      SharedPtr& operator =(SharedPtr const& obj)
      {
        if(this == &obj) return *this;
    
        m_counter = obj.m_counter;
        m_object = obj.m_object;
    
        ++*m_counter;
      }
    
      T* operator ->() const
      {
        return m_object;
      }
    };
    

    (wirklich ganz billig, aber soll ja nur zur Veranschaulichung dienen. :))

    Grüssli



  • erik.vikinger schrieb:

    Dravere schrieb:

    Wie? Wieso muss ich das bei einem Smart-Pointer wissen? Leuchtet mir nicht so wirklich ein.

    Ich dachte der smart-pointer muss genau wissen das und wie er kopiert wird damit er das referenzierte Objekt freigeben kann wenn es keinen smart-pointer mehr gibt der darauf verweist. Ich kenne mich aber nicht wirklich mit smart-pointer aus, kann also auch falsch sein was ich darüber denke.

    Um einen Smart-Pointer zu schreiben, reicht Standard-C++. Mit Kopierkonstruktor, Zuweisungsoperator und Destruktor kann das gewünschte Verhalten auf eine ziemlich naheliegende Weise implementiert werden. Die Reihenfolge der für das Programm relevanten Anweisungen (bezüglich Programmlogik und Seiteneffekten) wird vom Standard in fast allen vernünftigen Fällen vorgeschrieben. Wäre ja schlimm, wenn man für einfachste Logik wie Reference Counting die Compiler-Implementierung kennen müsste.



  • @Dravere
    Warum hast du den counter dynamisch angelegt? - Du hast so nämlich ein potentielles Memoryleak.


  • Administrator

    drakon schrieb:

    @Dravere
    Warum hast du den counter dynamisch angelegt? - Du hast so nämlich ein potentielles Memoryleak.

    Überleg mal ganz stark. Jedes SharedPtr Objekt muss einen Referenzzähler haben und dieser Referenzzähler muss ... aja, von allen geteilt werden. Wie willst du das anders machen? 🙂

    Normalerweise verpackt man das ganze ein wenig schöner in eine interne Struktur, welche dann noch zusätzliche Daten für das Freigeben des Speichers mitabspeichert. Aber du kommst nicht drum herum, dies irgendwie dynamisch anzulegen, weil es eben alle SharedPtr, welche auf dieses Objekt verweisen, untereinander teilen müssen.

    Grüssli



  • drakon schrieb:

    @Dravere
    Warum hast du den counter dynamisch angelegt? - Du hast so nämlich ein potentielles Memoryleak.

    Wahrscheinlich damit alle Instanzen auf den Counter zugreifen können. 😉

    Allerdings würde ich den Zuweisungsoperator über Copy&Swap implementieren, auch wenn hier Exceptionsicherheit kein grosses Problem darstellt. Swap sollte jeder Smart-Pointer sowieso anbieten.


  • Administrator

    Nexus schrieb:

    Allerdings würde ich den Zuweisungsoperator über Copy&Swap implementieren, auch wenn hier Exceptionsicherheit kein grosses Problem darstellt. Swap sollte jeder Smart-Pointer sowieso anbieten.

    Ich sagte ja ganz billig :p
    Bei einer eigenen Implementation würde ich das ganz anders machen ... mehr als ganz anders 😃

    Grüssli



  • Ok, ja. Da habe ich nicht richtig geschaut. Aber trotzdem, wenn beim Konstruktor bei dem new eine Exception geworfen wird, dann wird der Speicher des übergebenen Zeigers nicht freigegeben.

    Sprich ich würde die Allokation in den Konstruktor verschieben und dann dort eine allfällige Exception fangen, den Speicher freigeben und weiterwerfen.



  • Hm, naja. Wenn man bei einem new int eine std::bad_alloc mitten ins Gesicht bekommt, ist wohl einiges nicht mehr gut. Es ist ja keine Klasse, bei der ein Konstruktor fehlschlagen könnte. Und ich weiss ja nicht, wie viel Sinn es macht, std::bad_alloc s immer zu fangen... Oder hast du bei dir um jede Speicheranforderung einen try-catch -Block?


  • Administrator

    ... was versteht ihr nicht an ***"BILLIG"***? 😃 🤡

    Es geht hier um eine Veranschaulichung und mehr nicht. Wenn schon würde ich sowieso boost::shared_ptr oder std::tr1::shared_ptr empfehlen 😉

    Grüssli



  • Nexus schrieb:

    Oder hast du bei dir um jede Speicheranforderung einen try-catch -Block?

    Nein, aber das ist auch gar nicht nötig. Es ist ja durchaus erlaubt, dass von dort eine Exception fliegt, aber nicht so, dass ein Memory Leak entsteht. Das Problem hast du üblicherweise ja nicht (eben wegen genau Smart Pointers), aber in dem Falle hier schon und man kann es recht leicht korrekt machen. Und ich fände es traurig, wenn ein Mittel, dass mir eben solche Fehler verhindern soll selbst einen drin hat. 😉

    Sein Beispiel war ja nur zur Veranschaulichung gedacht, klar, aber es war so offensichtlich. 🙂

    Achja und wenn ich mir den =-Operator so anschaue:

    int main ()
    {
     SharedPtr<int> s1 ( new int ); // 1
     SharedPtr<int> s2 ( new int );
    
     s1 = s2;
    }
    

    Wo genau wird da der int von 1 freigegeben? :p - Wärst wohl besser bei dem üblichen Idiom geblieben. 😉



  • Hallo,

    camper schrieb:

    Der Standard spezifiziert, welche Nebeneffekte ein bestimmtes Stück Programmcode hat und ggf. in welcher Reihenfolge diese Effekte auftreten, wenn und soweit es das beobachtbare Verhalten des Programmes beeinflusst.

    Ein Copy-Constructor kann ja Nebeneffekte haben (und wenn ich nur ein cout<<"Hallo" rein packe) weshalb ich davon ausgegangen bin das es dazu zumindest ein paar Anmerkungen im Standard gibt. Das Dinge wie eben Calling-Conventions oder Stack-Layouts, das alles schreibe ich gerade, nicht im Standard geregelt sind ist mir klar.

    camper schrieb:

    Eine dieser Regeln (5.2.2/8) besagt:

    [...] All side effects of argument expression evaluations take effect before the function is entered. [...]

    Das ist allerdings nicht unbedingt die geforderte Antwort.

    Ich finde schon dass das ungefähr nach meiner Fragestellung klingt.

    Wenn der neue Standard dann irgendwann mal endlich fertig ist sollte ich mir wohl mal die Zeit nehmen ihn zu lesen. Bis dahin lasse ich dieses Thema erstmal ruhen und bin glücklich das alle relevanten Spezifikationen für C soweit fertig sind.

    Ich danke Euch allen für die rege Teilhabe an meinem Problem!
    Erik



  • @erik.vikinger:
    Der Standard regelt das alles nach dem "as if" Prinzip. Er schreibt die beobachtbaren Effekte vor, die ein Programm zeigen muss. Wie die Implementierung das genau macht ist dagegen nicht vorgeschrieben.

    Da sich aber nun an den beobachtbaren Effekten nichts ändert, ganz egal ob der Caller oder Callee die Kopie erstellt, ist diesbezüglich auch nichts vorgeschrieben. Die beobachtbaren Effekte bleiben ja gleich, inklusive Reihenfolge etc.

    Und Dinge wie Register, der Stack/Stack-Pointer o.ä., sind was den Standard angeht nicht "beobachtbar". Von daher gibt es da auch keine Vorschriften.

    Der Standard verbietet auch z.B. Inlining nicht. Und bei Inlining gibt es keinen Caller und keinen Callee mehr, da kein Call mehr stattfindet. Die Frage wer nun den Copy-Ctor bzw. auch den Dtor aufruft stellt sich damit garnicht mehr.


Anmelden zum Antworten