Speicherverwaltung mit auto_ptr
nach "The C++ Standard Library" von Nicolai M. Josuttis
- Die C++ standard library enthält "smart pointern" - die auto_ptr -, um die Speicherverwaltung zu vereinfachen.
- auto_ptr reichen das Interface des Pointers durch, den sie besitzen
- die auto_ptr folgen dem RAII Idiom "resource acquisition is initialization" , bei dem die Resssourcebeanspruchung und Ressourcefreigabe an lokale Objekte gebunden wird (vgl. dazu Bjarne Stroustrup )
Motivation
void f(){
ClassA* ptr= new ClassA;
// tue etwas mit ptr
delete ptr;
}
- dieser Code hat zumindestens zwei Probleme:
- return statement zwischen new und delete
- eine Ausnahme kann geworfen werden
- beide Punkte führen zu einem Speicherloch
- Lösungsversuch
void f(){
ClassA* ptr= new ClassA;
try{
// tue etwas mit ptr
}
catch( ... ){
delete ptr;
throw;
}
delete ptr;
}
- der Code wird komplizierter und redundant
- falls ein neues Objekt entsprechend gesichert werden soll, wird' s noch komplizierter
- das Löschen eines null Pointers hat keine Auswirkung, falls schon das Speicher allozieren schieft geht
- auto_ptr als Lösung:
void f(){
std::auto_ptr<classA> ptr(new ClassA);
....
};
- auto_ptr fungiert hier als lokales Objekt
- sobald ptr zerstört wird, zerstört auto_ptr sein Objekt
new ClassA
#include <memory>
#include <string>
void bad(){
std::string* ptr= new std::string;
}
void good(){
std::auto_ptr< std::string > ptr(new std::string);
}
int main(){
bad();
good();
}
- der auto_ptr besitzt sein Objekt bzw. ein Objekt sollte maximal einen auto_ptr als Besitzer haben
- der auto_ptr will explizit sein Objekt erhalten:
std::auto_ptr ptr(new ClassA); //OK
std::auto_ptr ptr= new ClassA; //Fehler
std::auto_ptr ptr1( new ClassA);
std::auto_ptr ptr2( new ClassA); ptr1=ptr2 // OK
std::auto_ptr ptr; ptr= new ClassA //Fehler
Transfer of Ownership
- für auto_ptr gilt das Prizip der "strict ownership":
- durch den Kopierkonstruktor und den Zuweiungsoperator wechselt der Besitz
Kopierkonstruktor
std::auto_ptr<ClassA> ptr1(new classA);
std::auto_ptr<classA> ptr2(ptr1);
Zuweisungsoperator
std::auto_ptr<ClassA> ptr1(new classA);
std::auto_ptr<classA> ptr2;
ptr2= ptr1;
- in beiden Fällen geht der Besitz an ptr2 über und ptr1 ist danach ein Nullzeiger
- bei beiden Operation wird das Ursprungsobjekt modifiziert; d.h. die rechte Seite der Zuweisung wird modfiziert
- der Zuweisungsoperator und der Kopierkonstruktor erhalten nicht konstante Parameter:
auto_ptr( auto_ptr&) throw();
auto_ptr& operator= (auto_ptr&) throw();
- durch
std::auto_ptr<ClassA> ptr1(new classA);
std::auto_ptr<classA> ptr2(new classA);
ptr2= ptr1;
- wird ptr2 urspüngliches Objekt gelöscht
Source and Sink
Funktion als Senke von Daten
void sink( std::auto_ptr<ClassA> data);
- sink wird zur Senke der Daten
- sink gehört nun data und beim Beenden von sink wird data inklusive seines Besitzers gelöscht
Funktion als Quelle von Daten
std::auto_ptr<classA> source(){
std::auto_ptr<classA> ptr(new ClassA);
....
return ptr;
}
void g(){
std::auto_ptr<classA> p;
for ( int i=0; i<10; ++i){
p= source();
....
}
}
- ptr inklusiver seiner Daten werden beim rauskopieren gelöscht
- erhält p von der Quelle source die Daten
- durch die neue Zuweisung in der for Schleife wird p's ursprüngliche Objekt gelöscht
- letztendliche wird auch p und sein Inhalt gelöscht, sobald p out of scope geht
Warnungen
- folgender Code führt zu einem Laufzeitfehler
template < typename T >
void doSomething( std::auto_ptr<T> p );
....
std::auto_ptr <int> p( new int );
*p= 42;
doSomething( p );
*p= 18;
- der Speicher von p wird gelöscht, da sein Besitz an doSomething übergeht
template < typename T >
void doSomething( std::auto_ptr<T>& p );
- die Übergabe durch eine Referenz gilt als schlechtes Design, da die Besitzverhältnisse nicht mehr geklärt sind, den sowohl der aufrufende als auch der aufgerufene scope besitzen nun den auto_ptr
template < typename T >
void doSomething( const std::auto_ptr<T>& p );
- hingegen wird die Übergabe als eine konstante Referenz explizit durch den Standard unterbunden
- da alle Container die Objekte als konstante Referenz annehmen und dann intern erst mal kopieren, können diese keinen auto_ptr als Mitglied halten
- will man den Besitzübergang des Objekts von auto_ptr unterbinden, kann man diesen als const erklären:
std::auto_ptr<int> f(){
const std::auto_ptr<int> p(new int);
std::auto_ptr<int>q(new int);
*p= 42; // OK
p=q; // Error
q=p; // Error
return p; // Error
}
- die Zuweisung
*p= 42
ist möglich, da hier nur das von p referenzierte Objekt angesprochen wird - hingegen setzen
p=q; q=p und return p
die Besitzübergabe voraus, was auf einem konstanten auto_ptr nicht möglich ist
auto_ptr als Member
class ClassB{
private:
ClassA* ptr1;
ClassA* ptr2;
public:
ClassB( ClassA val1, ClassA val2): ptr1(new ClassA(val1)),ptr2(new ClassA(val2)){}
ClassB (const ClassB& x): ptr1(new ClassA(*x.ptr1)),ptr2(new ClassA(*x.ptr2)){}
const ClassB& operator= (const ClassB& x){
*ptr1= *x.ptr1;
*ptr2= *x.ptr2;
return *this;
}
~ClassB(){ delete ptr1; delete ptr2 }
...
};
- falls das zweite new in beiden Konstruktoren eine Ausnahme wirft, entsteht eine Speicherloch, da der Destruktor erst dann aufgerufen wird, wenn der Konstruktor fertig durchgeführt wurde
- hier bieten auto_ptr eine einfache Lösung
class ClassB{
private:
const std::auto_ptr<ClassA> ptr1;
const std::auto_ptr<ClassA> ptr2;
public:
ClassB( ClassA val1, ClassA val2): ptr1(new ClassA(val1)),ptr2(new ClassA(val2)){}
ClassB (const ClassB& x): ptr1(new ClassA(*x.ptr1)),ptr2(new ClassA(*x.ptr2)){}
const ClassB& operator= (const ClassB& x){
*ptr1= *x.ptr1; //
*ptr2= *x.ptr2;
return *this;
}
...
};
- der Kopierkonstruktor und der Zuweisungsoperator müssen implementiert werden, da die vom Compiler erzeugten die beiden auto_ptr direkt modifizieren würde, was nicht erwünscht und nicht möglich ist:
const ClassB& operator= (const ClassB& x){
ptr1= x.ptr1;
ptr2= x.ptr2;
return *this;
}
Manipulation der Daten
release
T* auto_ptr::release() throw();
- gibt das Objekt wieder frei
- als Returnwert erhält man die Adresse des Objekts oder den Nullzeiger
reset
void auto_ptr::reset( T* ptr= 0) throw();
- der auto_ptr wird mit ptr reinitialisiert
- das ursprüngliche Objekt wird gelöscht
- *this ist nun der Besitzer des Objekts
Missbrauch
auto_ptr' s können ihren Besitz nicht teilen:
void tschüs( auto_ptr <int > );
int* i =new int;
std::auto_ptr <int> p1(i);
std::auto_ptr <int> p2(i);
tschüs(p2);
*p1= 30; // undefiniertes Verhalten
auto_ptr' s können keine arrays verwalten, da dieser delete (nicht delete []) aufruft
auto_ptr' s erfüllen nicht die Anforderungen von Containerelementen
Quelle und Ziel einer Zuweisung- oder Kopieroperation ergibt bei auto_ptr verschiedene Objekte das initiale Kopieren im Container hätte fatale Folgen für die Quelle der Operation
Weiterlesen...