Klasse die sich selbst als Pointer sieht
-
Hi,
Die smartpointer Klasse sieht wie folgt aus:
template<class T> class smartpointer { public: smartpointer(T *p = 0); ~smartpointer(); T* operator->() const; T& operator*() const; smartpointer& operator=(T *p); operator bool() const; void loescheobject(); private: void operator=(const smartpointer&); smartpointer(const smartpointer&); T* zeigeraufobject; };
Allerdings weiß ich nicht welchen operator ich überladen muss, damit der folgende Aufruf "wieder" gültig wird.
class B { virtual void hi() { cout << "Hier ist B" << endl; }; }; void bypointer(const B *p) { cout << "Aufruf: bypointer(const smartpointer *p)" << endl; p->hi(); } int main() { smartpointer<B> spab = new B; bypointer(spab); // <<< hier meckert logischerweise der Compiler return 0; }
Möglich währe ja ein:
T* get(void) const;
Aber ich hätts gern etwas eleganter
MfG
Scarabol
-
Falls du einfach nur den Funktionsaufruf entfernt haben willst, kannst du dir ja einen operator T* für deine Klasse basteln.
Dann darfst du aber auch darauf achten, dass man damit keine Scherzchen treiben kann. Ansonsten wird mit dem Pointer irgendwas gemacht, was der Smartpointer nicht ermöglicht und der Compiler castet das implizit auf den Rohpointer um das zu ermöglichen.
Ich hatte mal was ähnliches mitoperator T const * const () const;
gelöst. Sicherlich zu viel const, aber lieber einmal zu viel als einmal zu wenig...
Kann aber gut sein, dass ich um diese Uhrzeit einen eleganten Weg übersehe...
-
get() ist schon top. Du möchtest es ja eigentlich nicht elegant haben, weil jede Rückgabe des Zeigers eine potentielle Lücke im Smart-Pointer System ist und du das deswegen so unelegant und auffällig wie möglich haben willst. Denn nach get() kann jemand den Zeiger woanders speichern und dann haste wieder den gleichen Müll wie vorher ohne SmartPointer.
Lass das mal besser so mit get
-
Scarabol schrieb:
Die smartpointer Klasse sieht wie folgt aus:
template<class T> class smartpointer { public: smartpointer(T *p = 0); ~smartpointer(); T* operator->() const; T& operator*() const; smartpointer& operator=(T *p); operator bool() const; void loescheobject(); private: void operator=(const smartpointer&); smartpointer(const smartpointer&); T* zeigeraufobject; };
Ich nehme an, dass "smart" hier heißt, dass im Destruktor ein "delete zeigeraufobject;" steht. Dieser Ansatz ist gefährlich, weil durch Deinen nicht-expliziten Konstruktor versehentlich ein Zeiger vom Typ T* zu smart_ptr<T> konvertiert werden kann. Jetzt willst Du noch den umgekehrten Weg, dass ein smart_ptr<T> implizit zu T* konvertierbar ist. Das lässt sich über einen Konvertierungsoperator erledigen.
Vom dem Ansatz rate ich aber ab, weil es fehleranfällig ist; denn eine versehentliche Konvertierung hin und/oder zurück führt zu Doppel-Löschversuchen. Deine smart_ptr-Objekte werden dadurch auch "explizit kopierbar":
smart_ptr<int> x = new int; smart_ptr<int> y (x); // oops!
Deswegen bietet zB boost::scoped_ptr keine solcher impliziten Konvertierungen und auch keinen solchen Zuweisungsoperator an.
Du solltest nicht lechtfertig implizite Konvertierungen anbieten. Denk daran, was passieren würde, wenn man versehentlich so eine Konvertierung benutzt.
Implizite Konvertierungen bzgl Smart-Pointern ist zB in Fällen
auto_ptr
rvalue -->unique_ptr
unique_ptr
rvalue -->shared_ptr
sinnvoll. Da kann eigentlich nichts schiefgehen. Der Zeiger im Quell-Objekt wird dabei natürlich genullt.kk
-
Danke an alle, hat geholfen
Ich muss den smartpointer<T> Zeiger an eine externe DLL weiterleiten, daher der implizite cast.
krümelkacker schrieb:
[...]
Jetzt willst Du noch den umgekehrten Weg, dass ein smart_ptr<T> implizit zu T* konvertierbar ist. Das lässt sich über einen Konvertierungsoperator erledigen.
[...]Verrätst du mir trotzdem wie?
Ich benutze erstmal die Methode getptr(), damit ich weitere Fehlerquellen ausschließen kann.
1. Jetzt muss ich nurnoch alle 140 Dateien meines Projekts mit smartpointern ausstatten. Irgendwelche Vorschläge?
2. Warum ist der -> Operator eigtl. kein Problem?
3. Ich hab die smartpointer Klasse jetzt um folgenden Hinweis ergänzt:
template<class T> inline T* smartpointer<T>::getptr(void) const { // Warnmeldung für den Programmierer ausgeben #define __STR2__(x) #x #define __STR1__(x) __STR2__(x) #define __LOC__ __FILE__ "("__STR1__(__LINE__)") : warning : T* smartpointer<T>::getptr() : " #pragma message(__LOC__"Using this may be very unsafe, pls ensure right handling and DON'T use if not neccessary.") assert(zeigeraufobject); return zeigeraufobject; }
Wenn ich nun in MSVC doppelklicke auf die Warnmeldung springt er in die smartpointer.h Datei und zeigt die #pragma message Zeile an.
Diese Verhalten würde aber bei einer häufigen Verwendung von getptr() nur Verwirrung stiften, da jedesmal die gleiche Stelle angezeigt wird.
Kann man irgendwie eine Ebene hochspringen und die Stelle anzeigen, an der getptr() aufgerufen wird?MfG
Scarabol
-
Scarabol schrieb:
Jetzt muss ich nurnoch alle 140 Dateien meines Projekts mit smartpointern ausstatten.
wieso?
-
Weil sich (mittlerweile) unerklärliches Verhalten einstellt und ich die Pointer in Verdacht habe.
MfG
Scarabol
-
Ich würde die get-Funktion als freie friend-Funktion implementieren. In Modern C++ Design wird das gut erklärt.
Wenn das Objekt eine Methode get() hat, ist der Unterschied zwischen ptr->get() und ptr.get() ziemlich gering und führt zu möglichen Fehlern.
Bei get(ptr) ist das nicht möglich.
-
314159265358979 schrieb:
Ich würde die get-Funktion als freie friend-Funktion implementieren. In Modern C++ Design wird das gut erklärt.
Wenn das Objekt eine Methode get() hat, ist der Unterschied zwischen ptr->get() und ptr.get() ziemlich gering und führt zu möglichen Fehlern.
Bei get(ptr) ist das nicht möglich.Danke für den Tipp, ich habs wie folgt probiert:
template<class T> class smartpointer; template<class T> T* getptr(smartpointer<T>& obj) { // Warnmeldung für den Programmierer ausgeben #define __STR2__(x) #x #define __STR1__(x) __STR2__(x) #define __LOC__ __FILE__ "("__STR1__(__LINE__)") : warning : T* smartpointer<T>::getptr() : " #pragma message(__LOC__"Using this may be very unsafe, pls ensure right handling and DON'T use if not neccessary.") assert(obj.zeigeraufobject); return obj.zeigeraufobject; } template<class T> class smartpointer { public: // [...] friend T* getptr(smartpointer<T>& obj); // [...] };
**
Problem ist das der Linker einen Fehler (nicht aufgelöster externer Verweis) ausspukt, wenn ich smartpointer für mehr als eine Klasse verwende. Was hab ich falsch gemacht?
**MfG
Scarabol
-
Passt zwar nicht so zu deiner Fehlerbeschreibung, dürfte aber trotzdem ein Problem sein: benutze das inline Keyword um die ODR nicht zu verletzen.
Edit:
Und zu deiner Fehlermeldung:
Tue die freie Funktion getptr(..) Vorwärtsdekl., dann die Klasse smartpointer definieren und dann die freie Funktion getptr(..) definieren.
-
Meinst du so:
template<class T> T* getptr(smartpointer<T>& obj); template<class T> class smartpointer { public:
Dann ist smartpointer<T> ja nicht definiert und bei:
template<class T> class smartpointer; template<class T> T* getptr(smartpointer<T>& obj); template<class T> class smartpointer { public:
Erhalte ich die gleiche Fehlermeldung wie oben.
MfG
Scarabol
-
Scarabol schrieb:
Ich muss den smartpointer<T> Zeiger an eine externe DLL weiterleiten, daher der implizite cast.
Überall wo du
operator T*
einsetzen würdest, kannst duGet()
verwenden – nur halt explizit.Ich kann mich krümelkacker nur anschliessen: Vermeide implizite Konvertierungen, denn nur so hast du die volle Kontrolle über den Besitz. Mach den Konstruktor
explicit
. Was für eine Besitzsemantik hat der Zeiger überhaupt?314159265358979 schrieb:
Ich würde die get-Funktion als freie friend-Funktion implementieren. In Modern C++ Design wird das gut erklärt.
Wenn das Objekt eine Methode get() hat, ist der Unterschied zwischen ptr->get() und ptr.get() ziemlich gering und führt zu möglichen Fehlern.
Bei get(ptr) ist das nicht möglich.Die Argumentation hat mich in Modern C++ Design nie so richtig überzeugt. Einerseits kann man genausogut aus Versehen
get(*ptr)
schreiben, andererseits wird der Unterschied mit aussagekräftigeren Namen als "get" auch grösser. Und drittens sind Smart-Pointer spätestens seitshared_ptr
einigermassen bekannt, und auch ihr Klassen-Status ist nicht überraschend. Von daher würde ich eher zu diesen konsistent sein. Ich sehe den riesigen Vorteil von globalen Funktionen hier nicht.
-
Hier ist die komplette Klasse als Headerdatei:
smartpointer.h#ifndef inc_smartpointer_h #define inc_smartpointer_h #include <cassert> template<class T> class smartpointer { public: smartpointer(T *p = 0); ~smartpointer(); T* operator->() const; T& operator*() const; // getptr gibt direkt den internen Zeiger auf das T-Objekt heraus. Also vorsicht und bitte nur im äußersten Notfall einsetzen T* getptr(void) const; smartpointer& operator=(T *p); operator bool() const; void loescheobject(); private: void operator=(const smartpointer&); smartpointer(const smartpointer&); T* zeigeraufobject; }; template<class T> inline smartpointer<T>::smartpointer(T *p) : zeigeraufobject(p) { } template<class T> inline smartpointer<T>::~smartpointer() { loescheobject(); } template<class T> inline T* smartpointer<T>::operator->() const { assert(zeigeraufobject); return zeigeraufobject; } template<class T> inline T& smartpointer<T>::operator*() const { assert(zeigeraufobject); return *zeigeraufobject; } template<class T> inline T* smartpointer<T>::getptr(void) const { // Warnmeldung für den Programmierer ausgeben #define __STR2__(x) #x #define __STR1__(x) __STR2__(x) #define __LOC__ __FILE__ "("__STR1__(__LINE__)") : warning : T* smartpointer<T>::getptr() : " #pragma message(__LOC__"Using this may be very unsafe, pls ensure right handling and DON'T use if not neccessary.") assert(zeigeraufobject); return zeigeraufobject; } template<class T> inline smartpointer<T>& smartpointer<T>::operator=(T* p) { assert(zeigeraufobject == 0); zeigeraufobject = p; return *this; } template<class T> inline smartpointer<T>::operator bool() const { return bool(zeigeraufobject); } template<class T> inline void smartpointer<T>::loescheobject() { delete zeigeraufobject; zeigeraufobject = 0; } #endif
MfG
Scarabol
-
Nexus schrieb:
Die Argumentation hat mich in Modern C++ Design nie so richtig überzeugt. Einerseits kann man genausogut aus Versehen
get(*ptr)
schreiben, andererseits wird der Unterschied mit aussagekräftigeren Namen als "get" auch grösser. Und drittens sind Smart-Pointer spätestens seitshared_ptr
einigermassen bekannt, und auch ihr Klassen-Status ist nicht überraschend. Von daher würde ich eher zu diesen konsistent sein. Ich sehe den riesigen Vorteil von globalen Funktionen hier nicht.Angenommen, das Objekt hat eine Methode get() die auch einen T* zurückgibt. Ein Tippfehler würde reichen, um etwas völlig anderes zu machen. Klar, dieser Fall wird sicher nicht oft vorkommen, aber möglich ist es.
-
Wie gesagt: Da gibt es noch einiges zu tun, um eine robuste Semantik zu gewährleisten.
- Konstruktor
explicit
- Zuweisungsoperator von rohem Zeiger entfernen
operator bool
durch Safe-Bool-Idiom ersetzen, da sonst Dinge wiea == b
mit unerwartetem Ergebnis möglich sindgetptr()
darf eigentlich Nullzeiger zurückgeben, daher keinassert
verwenden
Hast du kein Boost? Sonst könntest du
boost::scoped_ptr
verwenden, der ist schon voll optimiert und getestet. Ist auch eine Header-Only-Datei, du müsstest also nichts linken.
- Konstruktor
-
314159265358979 schrieb:
Angenommen, das Objekt hat eine Methode get() die auch einen T* zurückgibt. Ein Tippfehler würde reichen, um etwas völlig anderes zu machen. Klar, dieser Fall wird sicher nicht oft vorkommen, aber möglich ist es.
Ja, ich habe deine Aussage schon verstanden. Aber wie gesagt: Ein Tippfehler kann auch zu einem zusätzlichen
*
führen. Und generell kommt mir das ein bisschen vor wieif (a == 4) // man muss hier if (4 == a) // schreiben, weil der allgegenwärtige // Fehler a = 4 so verhindert wird!
-
Das mit dem zusätzlichen * wird wohl nicht funktionieren, da die Funktion einen smart_ptr<T> nimmt und dessen Konstruktor i.d.R. explicit ist
-
Vereinfacht:
X* Get(SmartPtr<X> p); X* Get(X x); SmartPtr<X> ptr; Get(*p); // !
Aber das war ohnehin nur eine Bemerkung am Rande. Das kommt derart selten vor, da lohnt es sich nicht stundenlang darüber zu diskutieren. Bei einem besseren Namen als "Get" kommt die Situation erst recht nie vor. Ich habe ja noch andere Punkte genannt und wollte primär aussagen, dass ich die Variante mit der freien Funktion nicht für so viel besser halte wie hier dargestellt. Und wenn ich sie verfechtete, würde ich andere Argumente in den Vordergrund rücken (z.B. Generizität).
-
Nexus schrieb:
Wie gesagt: Da gibt es noch einiges zu tun, um eine robuste Semantik zu gewährleisten.
- Konstruktor
explicit
- Zuweisungsoperator von rohem Zeiger entfernen
operator bool
durch Safe-Bool-Idiom ersetzen, da sonst Dinge wiea == b
mit unerwartetem Ergebnis möglich sindgetptr()
darf eigentlich Nullzeiger zurückgeben, daher keinassert
verwenden
Versteh ich soweit alles aber was hat es mit dem safe bool auf sich und wieso darf ich den Zuweisungsoperator für rohe Zeiger nicht benutzen?
MfG
Scarabol
- Konstruktor
-
Google wird dir weiterhelfen.