Zugriffsverweigerung bei Destruktor
-
queer_boy schrieb:
statt einer zuweisung solltest du in konstruktoren immer die elementinitialisierungsliste verwenden:
Enemy::Enemy(const Enemy& NewEnemy) : Barrel(new sf::Sprite(Image)) { }wenn du mehr als einen member dynamisch per new initialisierst, ist die "länge" des copy-ctors außerdem noch dein geringstes problem:
class Foo { int *a, *b; public: Foo (Foo const& other) : a(new int(other.a)), b(new int(other.b)) { } };falls
new int(other.b)fehlschlägt, wer löscht dannnew int(other.a)?genau wegen solcher Dinge gehören news für normale pointer nicht in die Initialisierungsliste. Wenn innerhalb der Initialisierungsliste eine Exception ausgelöst wird, hat man sich sofort ein Speicherleck eingehandelt. Selbst im Ctor Body muss man dann noch mit einem lokalen try/catch Block fehlgeschlagene news abfangen. Deshalb bei lebenswichtigen Pointermembern lieber gleich smartpointer verwenden.
Mehr zum Thema: http://www.gotw.ca/gotw/066.htm
-
pumuckl schrieb:
...
Mehr zum Thema: http://www.gotw.ca/gotw/066.htmebenda schrieb:
...
The short answer is: By using a function-try-block.For example, here's how we can write a function-try-block for C's constructor:
// Example 1(a): Constructor function-try-block // C::C() try : A ( /*...*/ ) // optional initialization list , b_( /*...*/ ) { } catch( ... ) { // We get here if either A::A() or B::B() throws. // If A::A() succeeds and then B::B() throws, the // language guarantees that A::~A() will be called // to destroy the already-created A base subobject // before control reaches this catch block. }

Wieder mal bestätigt sich "Man lernt nie aus" !!!
Von der Konstruktion hatte ich noch nie gehört.Gleich ausprobiert (g++ 3.3.3) und (für mich) Erstaunliches festgestellt: Dieser catch-Block "fängt" zwar die Exception, sie fliegt danach aber trotzdem weiter.
Wie auc hier steht:
... What's less obvious, but clearly stated in the standard, is that if the catch block does not throw ... and control reaches the end of the catch block of a constructor or destructor, then the original exception is automatically rethrown.
da muss ich nochmal ein wenig mit rumspielen und -lesen.

Danke,
Simon2.
-
Vielen Dank für die zahlreichen Antworten! Ich hätte nicht gedacht, dass wegen eines einzigen Zeigers ein solcher Mehraufwand erforderlich ist

Aber das ist sowieso gerade eine gute Übung für mich, zumal ich bis jetzt noch nicht viel von Kopierkonstruktor, konstanten Funktionen etc. wusste.Doch noch was zu den const-Funktionen: Ich verstehe schon, dass in diesem Zusammenhang (wenn Enemy konstant ist) auch die Memberfunktionen einen konstanten Rückgabewert haben müssen. Jedoch gebe ich bei meinen Funktionen mit Rückgabetyp sowieso immer einen Wert zurück, spricht da etwas dagegen, alle Funktionen à la GetAnything() konstant zu deklarieren? Wäre das nur bei der Rückgabe von Zeigern oder Referenzen von Bedeutung? Ich verstehe nicht ganz, welchen tatsächlichen Unterschied es macht ob ich jetzt short GetType() oder short GetType() const schreibe (ich meine allgemein, nicht nur wenn die Klasse selber konstant übergeben wird)... solange ich in der Funktion nur return Type; aufgeschrieben habe, kann ja eh nichts verändert werden, oder?
Noch was: ist const short GetType() const das gleiche wie short GetType() const?
-
Wenn eine Memberfunktion const deklariert ist bedeutet das nicht, dass der Rückgabewert const ist, sondern, das die Funktion aufgerufen wird, wenn die dahinterliegende Instanz const ist. Es darf also in einer solchen Funktion kein Member der Klasse verändert werden.
-
Nexus schrieb:
Vielen Dank für die zahlreichen Antworten! Ich hätte nicht gedacht, dass wegen eines einzigen Zeigers ein solcher Mehraufwand erforderlich ist ;)...
Da merkst Du mal, welch Vorteil es ist, "Objekte"/Stackvariablen zu haben.

Das Problem macht nämlich nicht der Zeiger, sondern die (Möglichkeit der) dynamische(n) Verwaltung.
Sie hat auch Vorteile, aber die kosten eben auch etwas (wie z.B. hier).Gruß,
Simon2.
-
@ Simon2
Mit der Zeit werde ich kaum darum herum kommen, deshalb ist es nicht mal so schlecht... Ich frag mich nur ein bisschen, da ich bis jetzt schon ganze 2D-Spiele geschrieben habe, ohne jemals etwas von Kopierkonstruktor etc. gehört zu haben
@ Braunstein
Danke für die Erläuterung, das hab ich nicht ganz richtig begriffen. Ich versteh folgenden Unterschied aber immer noch nicht, hat es hier etwas mit dem Rückgabetyp zu tun (da ja das Schlüsselwort const vorne steht):const short GetType() const; short GetType() const;Zudem ist mir immer noch nicht klar, was dafür spricht, Memberfunktionen wie GetType(), die nur einen Wert zurückgeben, als nicht-konstant zu deklarieren.
-
Nexus schrieb:
Zudem ist mir immer noch nicht klar, was dafür spricht, Memberfunktionen wie GetType(), die nur einen Wert zurückgeben, als nicht-konstant zu deklarieren.
Gar nichts. Diese Funktionen sollten auch immer const sein. Es gibt auch da sicher Ausnahmefälle (lazy evaluation?).
const short GetType() const;Konstante Memberfunktion die ein const short zurückgibt. Das wäre hier recht unsinnig, da der Rückgabewert eh kopiert wird.
short GetType() const;Konstante Memberfunktion die ein short (nicht konstant) zurückgibt.
In diesem Fall sind aber beide Funktionen identisch. Das wären ja auch keine möglichen Überladungen, da der Rückgabetyp ja nicht zur Funktionssignatur gehört.
Sinn machen konstante Rückgabewerte ja eher bei Referenzen oder Zeigern.
-
Okay, vielen Dank für die schnelle Antwort!
Dann mache ich eben alle GetBlabla-Funktionen konstant
-
Braunstein schrieb:
const short GetType() const;Konstante Memberfunktion die ein const short zurückgibt. Das wäre hier recht unsinnig, da der Rückgabewert eh kopiert wird.
short GetType() const;Konstante Memberfunktion die ein short (nicht konstant) zurückgibt.
In diesem Fall sind aber beide Funktionen identisch.Treffsicher daneben.
Beide Deklarationen deklarieren ein und dieselbe Funktion. (Top-level) cv-Qualifizierer des Rückgabewertes sind nicht Teil des Funktionsstyps, wenn der Rückgabewert skalar ist. Das wird klar, wenn man sich überlegt, dass das Ergebnis eines entsprechenden Funktionsaufrufes ein rvalue ist, und skalare rvalues sind niemals cv-qualifiziert (weil es keine Objekte sondern nur der Wert an sich - das Bitmuster, wenn man so will - sind).
-
Okay, das verwirrt mich jetzt ein bisschen...
Versteh ich dich richtig, dass nur ein Wert (R-Value) zurückgegeben wird, und es keine Rolle spielt, ob der konstant ist oder nicht, weil er sowieso nicht verändert wird? (Sorry kenn mich eben nicht so aus mit deinen Fachausdrücken :D).Heisst das in diesem Fall, man kann das erste const von const short GetType() const immer weglassen? Wieso ermöglicht es dann die C++-Syntax, es vorher zu schreiben, wenn es so oder so überflüssig ist?
-
Heisst das in diesem Fall, man kann das erste const von const short GetType() const immer weglassen?
ja, weil short ein skalarer Typ ist. Das besondere an skalaren Typen ist, dass sie eine einzelne unteilbare Einheit bilden: man kann hier also sehr schön trennen zwischen dem Speicher (=Objekt) eines solchen Typs und dem Zustand dieses Speichers. Ein Ausdruck skalaren Typs (bei Klassen/Arrays/Funktionen/Referenzen wird es komplizierter) kann nun (ganz allgemein) entweder auf ein Objekt (=Speicherstelle) verweisen oder nur den Wert (=Zustand) an sich bezeichnen. Ersteres stellt einen lvalue-Ausdruck dar, letzteres ein rvalue. Nun ist aber klar, dass die Eigenschaft, konstant oder volatile zu sein, nur bestimmtem Speicher zukommen kann. Bezogen auf einen bestimmten Zustand (man denke zum Beispiel auch ein ein einfaches Literal wie 1 - dem entspricht ein bestimmtes Bitmuster vom Typ int) sind diese Begriffe sinnlos. Die Konsequenz ist, dass solche (skalaren) Rvalues niemals cv-qualifiziert sind. Immer dann, wenn einer Konvertierung eines Lvalues in ein Rvalue erfolgt (das entspricht im Allgemeinen dem Lesen einer Variablen, also der Bestimmung des Zustands einer bestimmten Speicherstelle), gehen evtl. vorhandene Qualifikationen des Objektes verloren, nach dem Lesevorgang haben diese keine Bedeutung mehr.
Der Rest ergibt sich damit im Grunde von selbst. Der Compiler unterscheidet nicht zwischen beiden Deklarationen, weil er es gar nicht kann.Nexus schrieb:
Wieso ermöglicht es dann die C++-Syntax, es vorher zu schreiben, wenn es so oder so überflüssig ist?
Das ist keine syntaktische Frage. Der Rückgabetyp könnte ja auch durch ein typedef konstant erscheinen (der tatsächlich deklarierte Rückgabetyp ist auch dann wiederum unqualifiziert), dann sieht man das der Deklaration gar nicht unmittelbar an. Zudem, was wichtiger ist, ist nicht jeder Ausdruck skalar, und für bestimmte Typen (primär Klassen) existieren cv-qualifizierte Rvalues.
Bleibt noch anzumerken, dass es - meiner Meinung nach - fast nie sinnvoll ist, den Rückgabetyp konstant zu machen (wie oben geklärt, spielt es sowieso nur bei Klassen eine Rolle). Die Gründe dafür sind nicht sonderlich kompliziert. Hauptsächlich geht es um Kapselung - eine Funktion sollte so wenig wie möglich vom Aufrufer verlangen (=möglichst allgemeiner Parameter) und möglichst viel versprechen (=Garantien über die Rückgabe liefern, mit einem veränderbaren Objekt kann man mehr machen, als mit einem konstanten), aber das sprengt ein bisschen den Rahmen dieser Diskussion.
-
Okay, vielen Dank für diese umfassende Antwort!
-
camper schrieb:
Bleibt noch anzumerken, dass es - meiner Meinung nach - fast nie sinnvoll ist, den Rückgabetyp konstant zu machen (wie oben geklärt, spielt es sowieso nur bei Klassen eine Rolle). Die Gründe dafür sind nicht sonderlich kompliziert. Hauptsächlich geht es um Kapselung - eine Funktion sollte so wenig wie möglich vom Aufrufer verlangen (=möglichst allgemeiner Parameter) und möglichst viel versprechen (=Garantien über die Rückgabe liefern, mit einem veränderbaren Objekt kann man mehr machen, als mit einem konstanten), aber das sprengt ein bisschen den Rahmen dieser Diskussion.
Und deshalb ist es gut
(a+b)=c;
zu erlauben?
-
Shade Of Mine schrieb:
camper schrieb:
Bleibt noch anzumerken, dass es - meiner Meinung nach - fast nie sinnvoll ist, den Rückgabetyp konstant zu machen (wie oben geklärt, spielt es sowieso nur bei Klassen eine Rolle). Die Gründe dafür sind nicht sonderlich kompliziert. Hauptsächlich geht es um Kapselung - eine Funktion sollte so wenig wie möglich vom Aufrufer verlangen (=möglichst allgemeiner Parameter) und möglichst viel versprechen (=Garantien über die Rückgabe liefern, mit einem veränderbaren Objekt kann man mehr machen, als mit einem konstanten), aber das sprengt ein bisschen den Rahmen dieser Diskussion.
Und deshalb ist es gut
(a+b)=c;
zu erlauben?Ich sehe daran nicht so viel Schlimmes. Die geerbte C-Syntax erlaubt ohnehin eine Menge Mist. Schreibt man das mit Klammern, ist sowieso Machiavelli am Werk - oder jemand mit einem Klammerfetisch (etwas, das ich nicht besonders mag, es kann helfen Präzedenz zu klären, aber häufig wird ein Ausdruck auch unverständlicher, wenn man nur nach am Klammerabzählen ist). Gefährlich könnte es werden, wenn wir eigentlich einen Vergleich schreiben wollten. Das passiert für gewöhnlich in if,while-statements; und hier sind moderne Compiler in der Regel sehr mitteilungsfreudig - es ist also imo kein praktisches Problem, wenn man die Warnungen des Compilers gelegentlich liest. Es bliebe der Fall, wenn der eigentlich gewollte Vergleich nur ein Subausdruck ist. Solche Fälle sind eher selten und per se schwerer zu verstehen, hier ist in jedem Falle erhöhte Aufmerksamkeit gefordert, oder man verzichtet darauf - auch ein C-Erbe i.w.S.
Wäre es gut, es nicht zu erlauben? Sicherlich, aber das Mittel ist ungeeignet. Der Defekt, wenn man das so bezeichnen will, liegt ja beim Zuweisungsoperator, nicht bei der Addition.
Und dann wäre da noch
c=a+b
Ist obiges Problem wirklich so gravierend, dass wir mit Freude auf Move-Kopiererei verzichten würden? Da habe ich große Zweifel.
Persönlich finde ich, dass das a+b=c Problem unnötig aufgebauscht wurde. Hier wird eine relativ isolierte Problematik zum Anlass genommen, potentiell das Design nahezu jeder Funktion zu beeinflussen.
-
camper schrieb:
Ist obiges Problem wirklich so gravierend, dass wir mit Freude auf Move-Kopiererei verzichten würden?
Warum sollte ein move nicht funktionieren? Wenn ich mir zB Mojo ansehe - dann wuerde (a+b)=c wohl sowieso nicht klappen.
oder uebersehe ich etwas? welchen nachteil hat es, ein (a+b)=c zu verbieten und sich so an die builtin semantic zu halten. denn zuweisung an temopraere objekte ist immer boese. es sei denn jemand hat eine situation wo das sinn machen kann...
-
Shade Of Mine schrieb:
camper schrieb:
Ist obiges Problem wirklich so gravierend, dass wir mit Freude auf Move-Kopiererei verzichten würden?
Warum sollte ein move nicht funktionieren? Wenn ich mir zB Mojo ansehe - dann wuerde (a+b)=c wohl sowieso nicht klappen.
oder uebersehe ich etwas? welchen nachteil hat es, ein (a+b)=c zu verbieten und sich so an die builtin semantic zu halten. denn zuweisung an temopraere objekte ist immer boese. es sei denn jemand hat eine situation wo das sinn machen kann...
Ich glaube wir reden aneinander vorbei. Ich habe nichts dagegen a+b=c zu verbieten, sofern die Methode nicht darin besteht, Funktionsergebnisse konstant zu machen. Die Frage ist doch gar nicht, ob es gut oder schlecht ist so etwas zu verbieten, sondern ob es den Preis wert ist, indem automatisch andere sinnvolle Konstrukte gleichzeitig unmöglich werden.
Ob Zuweisung an temporäre Objekte wirklich so furchtbar böse ist, kann auch bezweifelt werden oder ist eine Frage, wie das zu verstehen sein soll: im Allgemeinen ist es nur sinnlos, als böse würde ich etwas erst bezeichnen, wenn es versehentlich etwas anderes als das Offensichtliche tut oder leicht unbemerkt als versehentlicher Schreibfehler entsteht. Zu Ersteres fällt mir nichts ein, für Letzteres gibt es meiner Erfahrung nach adäquate Compilerunterstützung.Wenn a+b konstant ist (und nur darum geht es), kann c=a+b keine move-Unterstützung anbieten (unabhängig vom System, ob nun mit Mojo, Abrahams' Methode oder rvalue-Referenzen) ohne undefiniertes Verhalten zu provozieren. Move-Unterstützung ist nur ein Fall, es gibt viele Fälle in denen wir ein temporäres Objekt zwar nicht zuweisen aber doch verändern wollen. In Abwesenheit von Move-Semantik (und Expression Templates etc), könnte zum Beispiel so etwas sinnvoll sein:
a=((a*b)+=c);
oder
foo((a+b).trim()) (a,b: irgenwelche strings)
keines dieser Beispiele ist für sich genommen besonders wichtig und man könnte ohne Weiteres darauf verzichten (in der Regel machbar, wenn wir eine zusätzliche Variable an Stelle des temporären Objektes einführen und auf Copy-Elision vertrauen), in ihrer Gesamtheit erscheint mir das Verbot dieser ganzen Klasse von Konstrukten übertrieben nur um ein relativ obskures Problem mit der Zuweisung zu lösen, mit dem ich noch nie zu tun hatte. Abgesehen von diesen Beispielen gefällt es mir schon systematisch nicht, weil an der falschen Stelle angesetzt wird und auch nicht aus sich selbst heraus begründet werden kann, mit der Aufgabe der Funktion, die betroffen ist, hat das Ganze schließlich nichts zu tun. Die Argumentation mit der "built-in-Semantic" ist da ein bisschen fragwürdig. Wenn wir dahin wollen, müssen wir dem Ergebnis solcher Operatoren den Objektcharakter wegnehmen (offensichtlich unmöglich), mit const kommen wir nicht dahin.Ich halte es nicht für richtig, dem Nutzer Konstrukte, die wir ggf. mißbilligen, zu verbieten, wenn eine Empfehlung in dieser Hinsicht genügen müsste. Zumindest erscheint mir das Problem einfach nicht schwerwiegend genug um einen dermaßen massiven Eingriff in die Deklaration jeder einzelnen Funktion, die Klassenobjekte zurückgibt, zu rechtfertigen.