Bin ich eigentlich der einzige, der sowas macht?
-
Punkt 1 ist mir natürlich vollkommen klar. In meinem Fall sind die Objekte aber sowieso standardmäßig nicht kopierbar.
-
Also ich wuerde sowas der besseren Lesbarkeit halber mit einem auto_ptr realisieren..
-
Wenn man einen guten Tag hat:
class Foo { boost::scoped_ptr<SomeClass> ptr; SomeClass& member; ... }; Foo::Foo() : ptr(new SomeClass), member(*ptr) { }
Kostet wahrscheinlich etwas mehr Speicher, aber was solls, wir haben's ja :p
-
Also jetzt muss ich doch mal Optimizer zustimmen. Ich mache auch so etwas, Referenzen im Konstruktor mittels "new" auf einen gültiges Objekt referenzieren zu lassen und in Destruktor wieder zu löschen.
Was spricht dagenen? Es gibt zwischen Pointern und Referenzen keinen Unterschied, ausser dass man mal mit "." und mal mit "->" die Elemente anspricht. Und das eine Referenz nicht Null sein kann, stimmt ja auch nicht,int &i(* ((int*)0) ); if(&i == 0) then std::cout << "&i == 0" << std::endl; else std::cout << "Schmeiss Deinen Rechner weg!" << std::endl;
Vielmehr geht es bei Referenzen darum, dass man davon ausgehen sollte, dass sie auf etwas gütliges zeigen. Optimizer hätte dies auch mit pointern machen können, würde dann nichts daran ändern, dass bei einem Fehlgeschlagenen new einmal der Pointer null ist bzw. sonst die Adresse hinter der Referenz. Wenn es darum geht sich die ganzen Includes zu sparen, aber die Objekte im jeden Fall initialisert sein "sollen", quasi so als ob man sie "by value" als Klassenelemente definiert hätte, ist es von der Semantik in meinen Augen korrekter Referenzen zu verwenden, anstatt Pointern. Einen Pointer muss man halt vor JEDER Verwendung auf NULL hin überprüfen, von einer Referenz geht man davon aus, dass sie gültig ist. Wenn man es dann ganz genau nimmt, müsst man also im Konstruktor nach dem new noch überprüfen, ob dies nicht fehltgeschlagen ist und dann evtl. selber eine Exception werfen - aber sonst wird nirgendwo mehr die Adresse überprüft.
-
standard new wirft ja selber ne exception wenns fehlschlägt,entweder man fängt sie ab, oder man lässt sie durch, beide male sollte das programm beendet werden,weil eh alles verlorenb ist, wenn sogar new schon fehlschlägt
-
wischmop2 schrieb:
Und das eine Referenz nicht Null sein kann, stimmt ja auch nicht
Doch. Dein Code hat undefiniertes Verhalten. Bitte fang keine Diskussion dazu an, das hatten wir hier schon mindestens einmal zu oft. Mach dich im Standard schlau.
Einen Pointer muss man halt vor JEDER Verwendung auf NULL hin überprüfen
Warum? Man überprüft natürlich nur dann auf einen Nullpointer, wenn man von der Programmlogik her nicht davon ausgehen kann, dass er garantiert nicht 0 ist. Dass die Programmlogik stimmt kann man mit Assertions sicherstellen.
Wenn man es dann ganz genau nimmt, müsst man also im Konstruktor nach dem new noch überprüfen, ob dies nicht fehltgeschlagen ist und dann evtl. selber eine Exception werfen - aber sonst wird nirgendwo mehr die Adresse überprüft.
Wenn new fehlschlägt, wirfst es schon eine Exception. Vielleicht benutzt du einen veralteten Compiler, wo das nicht der Fall ist ... in dem Fall hättest du natürlich Recht. Wenn die Objektkonstruktion fehlschlägt, sollte eine Exception geworfen werden, ein halbfertiges Objekt nützt ja niemandem was.
-
Optimizer schrieb:
Mach ich ab und zu mal, damit ich in der Header nicht die SomeClass-Header inkludieren muss. Da ich es eigentlich noch nirgendwo anders gesehen habe, muss es wohl keine gute Idee sein.
ja, die idee ist scheiße.
-verwende zeiger, wenn du zeiger meinst!
-das new dauernd ist schneckenlahm.
-kannst locker auf andere wege impls verstecken. mußt halt den mut haben, auch mal ne non.standard-zeile zu schreiben und dann dem sutter nicht alles glauben.
-
-verwende zeiger, wenn du zeiger meinst!
Ich meine aber nicht Zeiger. Das ist eine Komposition, das SomeClass gehört fest zu dem Foo, von dem es konstruiert wird.
-kannst locker auf andere wege impls verstecken. mußt halt den mut haben, auch mal ne non.standard-zeile zu schreiben und dann dem sutter nicht alles glauben.
Den Mut dazu hab ich schon. Jetzt muss ich nur noch wissen, wie?
-
Bashar schrieb:
Dein Code hat undefiniertes Verhalten. Bitte fang keine Diskussion dazu an, das hatten wir hier schon mindestens einmal zu oft. Mach dich im Standard schlau.
Gut, will jetzt hier nicht mit Programmbeispielen kommen, die eh nichts darüber sagen, wie es der Standard vorsieht, sondern nur etwas darüber sagen würden, was ein Compiler damit macht. Aber vielleicht kannst Du mir mal einen Link zum Standard geben, wo das beschrieben ist was Du sagst. Habe in Büchern nämlich immer nur gelesen, dass man das mich machen soll. Oder habe ich Dich mit "undefiniertem Verhalten" falsch verstanden? Darf man etwa eine Referenz nicht mit dem Inhalt (in wirklichkeit ja dann doch der Adresse) eines Pointers initialisieren? Dass man auf die Elemente nicht zugreifen darf, wenn der Pointer auf die Adresse 0 (oder von meinetwegen auch NULL) zeigt ist klar, aber das gilt halt auch für Pointer. (Es sei denn, das NULL auf eine Sentinel-Instanz zeigt, aber dann gilt dies auch für die Referenz). Also, will darüber nicht diskutieren, nur ein Verweis, wo ich dies selber nachlesen kann, würde mich freuen, damit ich soetwas nicht mehr mache - auch wenn's Funktioniert.
Bashar schrieb:
Warum? Man überprüft natürlich nur dann auf einen Nullpointer, wenn man von der Programmlogik her nicht davon ausgehen kann, dass er garantiert nicht 0 ist. Dass die Programmlogik stimmt kann man mit Assertions sicherstellen.
Was meinst Du mit "Assertions sicherstellen" ? Etwa falls p ein Pointer ist, von dem man nicht weiss ob er gültig ist, so etwas ?
assert(p); p->foo();
Naja, das wäre ja eine Überprüfung. Ich meine eigentlich nur, dass man von einer Referenz davon ausgehen kann (sollte), dass sich dahinter etwas gütiges verbirgt, Du hast ja selber geschrieben, dass eine Referenz mit NULL nach Standard strunz ist - wo ich Dir ja auch zustimme. Wenn ich aber einen fremden Code verwende, weiss ich bei einem Pointer eben nicht (sofern es nicht irgend wo dokumentiert ist), dass ein Pointer gültig ist und deshalb meinte ich, dass ich dies überprüfen muss, im Gegensatz zur Referenz.
Bashar schrieb:
Wenn new fehlschlägt, wirfst es schon eine Exception. Vielleicht benutzt du einen veralteten Compiler, wo das nicht der Fall ist ... in dem Fall hättest du natürlich Recht. Wenn die Objektkonstruktion fehlschlägt, sollte eine Exception geworfen werden, ein halbfertiges Objekt nützt ja niemandem was.
Ack - zuvor wurde nur etwas von einer "nothrow-Variante" geschrieben - wollte nur diesen Fall mit abdecken.
Last but not least - mir ist nun nicht klar, was gegen Optimizers Methode spricht, Referenzen als Membervariablen zu verwenden?
-
Nur um das mit den Null Referenzen nochmal zu klären. Im Stroustrup (3.Aufl.) Steht in Kapitel 15.4.1.1 "dynamic_cast von Referenzen"
"Um ein polymorphes Verhalten zu erreichen , muss ein Objekt über einen Zeiger oder eine Referenz manipuliert werden. Wenn ein dynamic_cast für einen Zeigertyp benutzt wird, bedeutet 0 einen Fehler. Das ist bei Referencen weder möglich noch erwünscht."
und dann gehts weiter dass die stattdessen einen bad_cast werfen.
mfg, KdeE
-
Das dereferenzieren eines NULL-Zeigers erzeugt undefiniertes Verhalten. Und das machst du ja hier.
int &i(* ((int*)0) );
-
Dann besteht aber nach dem, was bisher gesagt wurde, kein Unterschied zwischen NULL-Zeiger und NULL-Referenzen. *einmisch*
Trotzdem sorge ich natürlich dafür, dass Referenzen bei mir nicht NULL sind, das widerspricht meiner Intuition.
-
Mal ne frage nebenbei: Hier ist dauernd die rede von Exception abfangen. Wie soll das gehen?
catch in der initialisierungsliste "rethrowed" automatisch, falls keine andere Exception geworfen wird. Ein Abfangen ist deshalb gar nicht möglich. ODer bin ich da falsch informiert.
-
a) new ausserhalb fangen
b) kein new in die initialisierungsliste
-
ann besteht aber nach dem, was bisher gesagt wurde, kein Unterschied zwischen NULL-Zeiger und NULL-Referenzen. *einmisch*
Nonsense. Das ist eine Nullaussage. Es gibt in Standard-C++ *keine* Nullreferenz. Demzufolge kannst du auch nichts damit vergleichen.
Nocheinmal: Es gibt *keine* Nullreferenz. Jeder denkbare Weg der zu sowas wie einer Nullreferenz (eine Referenz die nicht an ein vorhandenes Objekt gebunden wird) führen könnte, hat mindestens in einem Teilschritt undefiniertes Verhalten. Hat aber mindestens ein Teilschritt undefiniertes Verhalten, dann hat der ganze Ausdruck undefiniertes Verhalten. Einmal undefiniert, immer undefiniert. Von diesem Moment an, kannst du alle weiteres Vergessen. Du hast die Definition verletzt. Weitere Schlussfolgerungen sind sinnlos.Referenzen haben eine vollständig andere Semantik als Pointer, auch wenn sie häufig über solche realisiert werden. Eine Referenz ist ein *Alias* für ein *vorhandenes* Objekt. Eine Referenz muss *immer* an ein *zuvor* existierendes Objekt gebunden werden.
catch in der initialisierungsliste "rethrowed" automatisch, falls keine andere Exception geworfen wird. Ein Abfangen ist deshalb gar nicht möglich. ODer bin ich da falsch informiert.
Nein. Das ist schon richtig.
Aber vielleicht kannst Du mir mal einen Link zum Standard geben, wo das beschrieben ist was Du sagst
Ich habe hier gerade leider keinen Standard. Aus dem Kopf würde ich jetzt aber mal auf 8.5 (oder 8.5.3?) tippen. Such nach Referenz und Initialisierung. Nullpointer wäre auch noch ein Stichwort.
darf man etwa eine Referenz nicht mit dem Inhalt (in wirklichkeit ja dann doch der Adresse) eines Pointers initialisieren?
Darf man. Aber nur, wenn der Pointer garantiert != 0 ist.
Das ist legal:Person* getPerson(); ... Person* p = getPerson(); assert(p); Person& rp = *p;
Das hingegen:
Person& rp = *getPerson();
hat *undefiniertes Verhalten*, falls getPerson einen Nullpointer liefert und zwar bereits *bevor* die Referenz gebunden wird. Hier entsteht also *keine* Nullreferenz, da zuvor bereist die Definition von Standard-C++ verletzt wurde und demzufolge keine weiteren Aussagen abgeleitet werden können.
-
-kannst locker auf andere wege impls verstecken. mußt halt den mut haben, auch mal ne non.standard-zeile zu schreiben und dann dem sutter nicht alles glauben.
Den Mut dazu hab ich schon. Jetzt muss ich nur noch wissen, wie? :)[/quote]
noch hatte ich nicht den echten bedarf, habs also nicht ausgearbeitet.
aber den echten bedarf, nicht immer die <windows.h> mitzucompilieren, hatte ich schon. daher hab ich dort abgeholfen. wirst schon den kram ausbauen können, wie du magst. und immer dran denken: abstraktion darf das prog nicht lahm machen.CriticalSection.h
#ifndef CRITICALSECTION #define CRITICALSECTION #include "HiddenPod.h" #include "NoCopy.h" struct _CRITICAL_SECTION; class CriticalSection{ private: HiddenPod<_CRITICAL_SECTION,24,4> critSect; NOCOPY; public: CriticalSection(); ~CriticalSection(); void lock(); void unlock(); }; #endif
CriticalSection.cpp
#include "CriticalSection.h" #include <windows.h> CriticalSection::CriticalSection(){ InitializeCriticalSection(&critSect); } CriticalSection::~CriticalSection(){ DeleteCriticalSection(&critSect); } void CriticalSection::lock(){ EnterCriticalSection(&critSect); } void CriticalSection::unlock(){ LeaveCriticalSection(&critSect); }
und wenn ich in der main.cpp mal ne critsect braucht, inkludiere ich halt critsect, aber muss nicht jedesmal wegen der <windows.h> warten.
der benötigte rest (dateien einfach reinkopiert, wirst schon erkennen, wo dateigrenzen sind):
#ifndef NOCOPY_H #define NOCOPY_H class NoCopy{ private: NoCopy(NoCopy const&); NoCopy& operator=(NoCopy const&); public: NoCopy(){ } }; #ifndef NDEBUG #define NOCOPY NoCopy _noCopy##__LINE__ #else #define NOCOPY typedef int _noCopy##__LINE__ #endif #endif #ifndef HIDDENPOD_H #define HIDDENPOD_H #include "Aligned.h" template<class POD,size_t SIZE,size_t ALIGN> struct HiddenPod{ Aligned<char[SIZE],ALIGN> data; POD* operator&(){ return reinterpret_cast<POD*>(data()); } }; #endif #ifndef ALIGNED_H #define ALIGNED_H #include <cstddef> #include "StaticAssert.h" template<class T,size_t ALIGNMENT> struct Aligned{ STATIC_ASSERT(false); }; template<class T> struct Aligned<T,4>{ T data __attribute__((__aligned__(4))); T* operator()(){ return &data; } T const* operator()() const{ return &data; } }; template<class T> struct Aligned<T,8>{ T data __attribute__((__aligned__(8))); T* operator()(){ return &data; } T const* operator()() const{ return &data; } }; #endif
ps: ich könnte dich bei http://www.coollist.com/group.cgi?id=lang1 brauchen.
edit: uos, da fehlen in der CriticalSection.cpp noch zwei STATIC_ASSERT, umd zu prüfen, ob in der CriticalSection.h size und alignement korrekt geraten wurden.
-
wischmop2 schrieb:
Aber vielleicht kannst Du mir mal einen Link zum Standard geben, wo das beschrieben ist was Du sagst.
http://anubis.dkuug.dk/jtc1/sc22/open/n2356/
Unter 8.3.2§4 findest du folgenden Satz:
in particular, a null reference cannot exist in a well-
defined program, because the only way to create such a reference would
be to bind it to the "object" obtained by dereferencing a null
pointer, which causes undefined behavior.Allerdings ist das eine "Note", also kein normativer Bestandteil des Standards. Ich such gerade schon wie verrückt nach der richtigen Stelle, aber das scheint nicht ganz so einfach zu sein, wie ich mir das gedacht habe.
Was meinst Du mit "Assertions sicherstellen" ? Etwa falls p ein Pointer ist, von dem man nicht weiss ob er gültig ist, so etwas ?
assert(p); p->foo();
Naja, das wäre ja eine Überprüfung.
Technisch ja. Die Grundidee ist aber eine andere. Wenn ich eine Bedingung überprüfe, dann lasse ich zwei Ergebnisse zu. Entweder sie ist wahr oder sie ist falsch. Beide Ergebnisse sind möglich, für beide sind unterschiedliche Konsequenzen ausprogrammiert.
Eine Assertion dagegen macht eine unausgesprochene Annahme über eine bestimte Bedingung explizit. Ein Fehlschlag deutet auf einen Logikfehler im Programm hin, und die einzig mögliche Reaktion ist, den Programmierer darauf aufmerksam zu machen (idR Programmabbruch). Es handelt sich dabei um eine reine Debug-Maßnahme, im fertigen Programm werden die Assertions durch das Makro NDEBUG deaktiviert.
-
Wenn ich dein konkretes Beispiel betrachte, sehe ich überhaupt keinen Grund warum ich die Variante mit new verwenden sollte. Ich sehe keinen einzigen Vorteil, wie die Antworten jedoch zeigen sind andererseits "potentielle" Gefahren vorhanden.
Also im Sinne der Fehlervermeidung, warum sollte jemand nicht die Standard-Vorgehensweise wählen ???
-
@volkard
Gibt es einen besonderen Grund, warum du bei deinem NoCopy nicht auf die EBO baust?
-
ok, alles nochmal zurück spulen - Klar, nie Null-Referenz - http://anubis.dkuug.dk/jtc1/sc22/open/n2356/ ist akzeptiert, Stroustrup natürlich ebenfalls.
Aber zurück zum Topic, es ging darum, ob man solche Referenzen als Membervariablen machen darf, statt pointer. Sie referenzieren auf ein gültiges Objekt, haben somit ein definiertes Verhalten und eben weil Referenzen auf ein gültiges Objekt zeigen, finde ich das eingangs von Optimizer beschriebene Vorgehen besser als Pointer - einem fremden Programmierer vermittelt dies etwas mehr Information als ein Pointer und zwar dass ein Null-Pointer nicht zu beachten ist (oft hat ein Null-Pointer ja auch eine Aussage und sollte nicht durch assert behandelt werden).
Ist dieses Vorgehen nun ok oder gibt es Gründe die dagegen sprechen (bisher haben sich ja alle genannten Gegengründe aufgelöst).