[vertagt] Copy-Constructor bei Call-by-Value
-
erik.vikinger schrieb:
Das würde bedeuten das man Object-Files von verschiedenen Compilerversionen nicht zusammenlinken könnte.
Korrekt und ist in C++ auch der Fall.
erik.vikinger schrieb:
Ich denke schon das sowas in irgendwelchen CPU-Architektur spezifischen ABI-Spezifikationen drin stehen müsste.
Ja, das steht irgendwo, wird aber von den Kompilerherstellern erstellt und ist bei C++ kein Standard. C dagegen hat einen ABI Standard.
erik.vikinger schrieb:
Ich denke für Klassen wie smart-pointer u.ä. sollte man schon ungefähr wissen wie sowas abläuft.
Wie? Wieso muss ich das bei einem Smart-Pointer wissen? Leuchtet mir nicht so wirklich ein.
erik.vikinger schrieb:
Sicher? Das würde IMHO ziemliche Probleme verursachen, wenn das jeder so machen dürfte wie er will.
Das ist sehr sicher. Bei C++ gibt es keinen ABI Standard. Bei C gibt es einen ABI Standard, wie schon weiter oben geschrieben. Und ja, das gibt Probleme, massig sogar. Nicht ohne Grund wünsche ich mir schon lange vom C++ Standard Komittee einen C++ ABI Standard

erik.vikinger schrieb:
Bei internen statischen Funktionen kann der Compiler sich frei austoben aber bei Funktionen die aus anderen Modulen aufgerufen werden sollen muss der Compiler sich an die geltende ABI-Spezifikation und entsprechende Calling-Conventions halten.
Welche es eben bei C++ nicht gibt, weshalb man meistens Libraries nicht zwischen Kompilern austauschen kann. Also Programm und Bibliothek muss vom gleichen Kompiler erstellt worden sein, wenn man C++ Dinge über die Bibliotheksgrenze hinweg verwenden möchte. Nicht ohne Grund, greift man oft bei solchen Dingen auf C zurück. Auch in C++ mit:
extern "C" void foo();Diese Funktion wird nun als C Funktion exportiert und ist somit einem ABI Standard unterworfen.
erik.vikinger schrieb:
Ich dachte nur das da C++ verschiedene Zusagen macht, sowas wie das jedes Objekt wieder ordentlich abgeräumt und dessen Destructor aufgerufen wird wenn sein Gültigkeitsbereich vorbei ist.
Schön und gut, aber es gibt keine weiteren Aussagen darüber, wie das ein Kompiler genau umsetzen muss. Dass wird einfach gefordert, die Umsetzung wird den Kompilerherstellern frei überlassen.
Grüssli
-
Hallo,
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.
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?Dravere schrieb:
Bei C gibt es einen ABI Standard,
Den hab ich fast fertig und dabei sind mir nun ein paar Dinge für C++ eingefallen.
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?
Danke
Erik
-
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.
-
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.
-
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.
-
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.
-
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 inteinestd::bad_allocmitten 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_allocs immer zu fangen... Oder hast du bei dir um jede Speicheranforderung einentry-catch-Block?
-
... was versteht ihr nicht an ***"BILLIG"***?

Es geht hier um eine Veranschaulichung und mehr nicht. Wenn schon würde ich sowieso
boost::shared_ptroderstd::tr1::shared_ptrempfehlen
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.