C++Talk.NET Forum Index C++Talk.NET
C++ language newsgroups
 
Archives   FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 



 
Post new topic   Reply to topic    C++Talk.NET Forum Index -> C++ (German)
View previous topic :: View next topic  
Author Message
Marcel Müller
Guest





PostPosted: Mon Mar 19, 2007 4:39 am    Post subject: Reply with quote



Hallo,

also das Thema Smartpointer ist ja relativ alt und ausgetreten. Aber es
gibt natürlich immernoch einige Haken und Ösen. Allen voran die
Probleme, die entstehen, falls Objekte im Coding nicht immer sauber mit
denselben Typen von Smartpointern gehalten werden.

Daraus kam mir die Idee, dass es durchaus eine Eigenschaft eines Objekts
sein kann (oder sogar soll), von welchen Smartpointern es verwaltet
werden darf - Beispielsweise mit Referenzzähler. Bedeutet: der
Smartpointer dockt an einem Interface an. Das vereinfacht auch die
Implementierung erheblich.

Das kann man jetzt verschieden implementieren. Entweder man macht ein
echtes Interface. Das macht natürlich jedes Objekt zu einem komplexen
Typ mit virtuellen Funktionen. Alternative B: Das Interface ist nur ein
POD und reserviert etwas Speicher beim Objekt. Beides kostet ein Wort im
Speicher jedes Objekts. Ich habe jetzt aus Performancegründen erstmal
mit der letztgenannten Alternative experimentiert.

/* Definition */

// logisches Interface für Objekte, die verwaltet werden können.
class Iref_count
{ friend class ref_ptr_base;
private:
volatile unsigned Count;
protected:
Iref_count() : Count(0) {}
};

// Teil des Referenzzählenden Smartpointer, der nicht als Template
// implementiert werden muss.
class ref_ptr_base
{protected:
Iref_count* Ptr; // Der Pointer
protected:
ref_ptr_base(const Iref_count* ptr = NULL);
ref_ptr_base(const ref_ptr_base& r);
// Destruktor schützen, bevor jemand Mist damit baut.
~ref_ptr_base() {}
// Kern des Zuweisungsoperators,
// liefert nicht NULL, wenn etwas (typisiert) gelöscht werden muss.
Iref_count* reassign(const Iref_count* ptr);
// Kern des typisierten Destruktors,
// liefert nicht NULL, wenn etwas (typisiert) gelöscht werden muss.
Iref_count* unassign();
};

// Der eignetliche Smartpointer
template <class T>
class ref_ptr : private ref_ptr_base
{public:
// Smartpointer aus Pointer generieren.
// Hier wird implizit geprüft, ob T von Iref_count erbt.
ref_ptr(T* ptr = NULL) : ref_ptr_base(ptr) {};
// Generierter Copy-Konstruktor ist OK.
// Objekt ggf. typisiert löschen.
~ref_ptr() { delete static_cast<T*>(unassign()); }
// Gut, der Operator T* ist Geschmackssache...
operator T*() const { return static_cast<T*>(Ptr); }
T& operator*() const { return *static_cast<T*>(Ptr); }
T* operator->() const { return static_cast<T*>(Ptr); }
ref_ptr<T>& operator=(T* ptr)
{ delete static_cast<T*>(reassign(ptr));
return *this; }
ref_ptr<T>& operator=(const ref_ptr<T>& r)
{ delete static_cast<T*>(reassign(r.Ptr));
return *this; }
friend operator==(const ref_ptr<T>& l, const ref_ptr<T>& r);
};

template <class T>
inline bool operator==(const ref_ptr<T>& l, const ref_ptr<T>& r)
{ return l.Ptr == r.Ptr;
}

/* Implementierung */

ref_ptr_base::ref_ptr_base(const Iref_count* ptr)
: Ptr(const_cast<Iref_count*>(ptr)) // const wird in Template behandelt
{ if (Ptr)
++Ptr->Count;
}
ref_ptr_base::ref_ptr_base(const ref_ptr_base& r)
: Ptr(r.Ptr)
{ if (Ptr)
++Ptr->Count;
}

Iref_count* ref_ptr_base::reassign(const Iref_count* ptr)
{ // Erst neuen Zähler inkrementieren, damit x=x geht.
if (ptr)
++const_cast<Iref_count*>(ptr)->Count;
// jetzt alten Ptr freigeben.
Iref_count* ret = NULL;
if (Ptr)
{ --Ptr->Count;
if (Ptr->Count == 0)
ret = Ptr; // muss typisiert gelöscht werden
}
Ptr = const_cast<Iref_count*>(ptr);
return ret;
}

Iref_count* ref_ptr_base::unassign()
{ if (Ptr)
{ --Ptr->Count;
if (Ptr->Count == 0)
return Ptr;
}
return NULL;
}


/* Benutzung */

class my_obj : public Iref_count
{public:
my_obj() { std::cout << "my_obj() @ " << this << std::endl; }
~my_obj() { std::cout << "~my_obj() @ " << this << std::endl; }
};

void foo(my_obj*);
ref_ptr<my_obj> bar();

int main()
{ ref_ptr<my_obj> p = new my_obj();
foo(p);
p = NULL;
foo(bar()); // Zulässig, weil temporäres Objekt während foo() bleibt
return 0;
}

static ref_ptr<my_obj> sta_p;

void foo(my_obj* arg)
{ sta_p = arg; // Zulässig, weil gemeinsamer Referenzzähler.
}

ref_ptr<my_obj> bar()
{ return sta_p;
};


Was spricht gegen eine solche Lösung?

Es ist natürlich kein Ersatz für alle Eventualitäten, aber mir gefällt
zum einen die einfache Implementation, die ohne zusätzliche Heap-Objekte
auskommt (im Gegensatz zu boost::shared_ptr), zum anderen ist der
Overhead mit einem Datenwort pro Objekt-Instanz auch kaum zu
unterbieten. Der Smartpointer selbst hat auch die gleiche Größe, wie ein
normaler Pointer. Man kommt ohne laufzeitraubende V-Table-Calls aus und
wenn man die ++ und -- Operationen durch plattformspezifische, atomare
Instruktionen ersetzt, ist das Ganze sogar noch thread-safe und
lock-free - also verschiedene Threads dürfen mit ref_ptr-Instanzen
Arbeiten, die auf dasselbe Objekt zeigen. Da außer bei delete keine
Exceptions geworfen werden können, sollte es auch exception-sicher sein..


Marcel
Back to top
Torsten Robitzki
Guest





PostPosted: Mon Mar 19, 2007 2:58 pm    Post subject: Reply with quote



Marcel Müller wrote:

Quote:
Hallo,

also das Thema Smartpointer ist ja relativ alt und ausgetreten. Aber es
gibt natürlich immernoch einige Haken und Ösen. Allen voran die
Probleme, die entstehen, falls Objekte im Coding nicht immer sauber mit
denselben Typen von Smartpointern gehalten werden.

Wann tritt das auf? Sprich Du verwendest im Projekt smartpointer
unterschiedlicher "Hersteller"? Wenn ich smartpointer verwende, reiche
ich Objekte trotz dem sehr häufig als Referenz herum. Die Notwendikeit
eine Referenz zu halten ist meist gar nicht nötig.

Quote:
Was spricht gegen eine solche Lösung?

Es muß von einer Basisklasse geerbt werden.

Quote:
Es ist natürlich kein Ersatz für alle Eventualitäten, aber mir gefällt
...

Ja, Deine Implementierung hat denke ich auch einige Vorteil gegenüber
anderen. Was mich an boost stört, ist das ich nicht angeben kann, das
ich die Zeiger nicht zwischen verschiedenen Threads kommunizieren möchte
und das der Overhead für kleine, nicht polymorphe Type recht groß erscheint.

Quote:
Da außer bei delete keine
Exceptions geworfen werden können, sollte es auch exception-sicher sein.

Werfen von Ausnahmen im Destruktor ist eh keine gute Idee, von daher
kannst Du das vernachlässigen.

mfg Torsten
Back to top
Thomas J. Gritzan
Guest





PostPosted: Mon Mar 19, 2007 8:52 pm    Post subject: Reply with quote



Marcel Müller schrieb:
Quote:
Hallo,

also das Thema Smartpointer ist ja relativ alt und ausgetreten. Aber es
gibt natürlich immernoch einige Haken und Ösen. Allen voran die
Probleme, die entstehen, falls Objekte im Coding nicht immer sauber mit
denselben Typen von Smartpointern gehalten werden.

Das tritt aber nur bei non-intrusive Zeigern auf (wo der Counter nicht im
Objekt gespeichert wird).

Quote:
Daraus kam mir die Idee, dass es durchaus eine Eigenschaft eines Objekts
sein kann (oder sogar soll), von welchen Smartpointern es verwaltet
werden darf - Beispielsweise mit Referenzzähler. Bedeutet: der
Smartpointer dockt an einem Interface an. Das vereinfacht auch die
Implementierung erheblich.

Gibt es schon:
http://www.boost.org/libs/smart_ptr/smart_ptr.htm

Siehe intrusive_ptr. Der funktioniert auch mit verschiedenen Interfaces,
wie z.B. den COM-Klassen unter Windows, die AddRef() und Release() haben.

shared_ptr ist nicht der einzige Smart-Pointer, den es bei Boost gibt.

--
Thomas
http://www.netmeister.org/news/learn2quote.html
Back to top
Marcel Müller
Guest





PostPosted: Tue Mar 20, 2007 9:25 pm    Post subject: Reply with quote

Thomas J. Gritzan wrote:
Quote:
Gibt es schon:

War ja eigentlich klar.

Quote:
Siehe intrusive_ptr. Der funktioniert auch mit verschiedenen Interfaces,
wie z.B. den COM-Klassen unter Windows, die AddRef() und Release() haben.

shared_ptr ist nicht der einzige Smart-Pointer, den es bei Boost gibt.

Schon klar, aber intrusive_ptr ist der einzige Boost Smart-Pointer
dessen Sinn ich bisher nie verstanden hatte. Die Tatsache, dass er
praktisch leer ist, und die ganze Arbeit zwei vom User zu schreibenden
Funktionen überlässt mag dazu beigetragen haben. Ein Beispiel in der
Doku hätte auch mehr gesagt als tausend (richtige) Worte.

Letztlich kann man die beiden Konzepte ja auch verschmelzen indem man
die beiden Funktionen einmal für den Basistyp definiert. Schade ist nur,
dass das delete nur generisch funktioniert, wenn das Interface einen
virtuellen Destruktor hat. Also muss man die Release-Funktion trotzdem
exakt typisieren.

Im vorliegenden Projekt bringt mir das alles leider eher wenig, da der
verwendete C++-Compiler bestimmt 10 Jahre alt ist, und definitiv keine
Boost-Library überlebt. Er kann nicht mal Namespaces.


Marcel
Back to top
Marcel Müller
Guest





PostPosted: Wed Mar 21, 2007 1:11 am    Post subject: Reply with quote

Torsten Robitzki wrote:
Quote:
Wann tritt das auf? Sprich Du verwendest im Projekt smartpointer
unterschiedlicher "Hersteller"?

Naja, der eine Programmierer hat auf std::auto_ptr gesetzt, der nächste
nimmt boost::*_ptr - rien ne va plus.
Man übergebe ein Objekt an eine C-Bibliothek oder System-API und bekomme
es in einer Callback-Funktion zurück - weg ist er, der Smartpointer -
jedenfalls ohne zusätzliche Capriolen.

Quote:
Wenn ich smartpointer verwende, reiche
ich Objekte trotz dem sehr häufig als Referenz herum. Die Notwendikeit
eine Referenz zu halten ist meist gar nicht nötig.

Ja, als Funktionsargument geht das oft noch, aber als Returnwert einer
Funktion i.a. nicht. Da sind erfahrungsgemäß zwei Copy-Konstruktoren
fällig, wenn der Compiler nicht gerade einen wegoptimiert.


Quote:
Da außer bei delete keine Exceptions geworfen werden können, sollte es
auch exception-sicher sein.

Werfen von Ausnahmen im Destruktor ist eh keine gute Idee, von daher
kannst Du das vernachlässigen.

Das wollte ich damit sagen.


Marcel
Back to top
Post new topic   Reply to topic    C++Talk.NET Forum Index -> C++ (German) All times are GMT
Page 1 of 1

 
 


Powered by phpBB © 2001, 2006 phpBB Group