Niebezpieczny std::auto_ptr
W bibliotece standardowej umieszczona została bardzo przydatna klasa (oczywiście jak przystało na bibliotekę STL jest klasą wzorcową). Mówię tu o klasie inteligentnego wskaźnika std::auto_ptr. Jest to klasa o niezwykle wysokim stopniu przydatności, gdyż pozwala na automatyczne zwalnianie pamięci przydzielonej dynamicznie za pomocą operatora new.
Przypomnijmy sobie jak się to używa i jak to działa:
tworzymy następującą klasę:
class MyClass
{
int m_id;
public:
MyClass(int id) : m_id(id)
{
std::cout << "Utworzono obiekt numer " << m_id << std::endl;
}
~MyClass()
{
std::cout << "Obiekt numer " << m_id << " został usunięty" << std::endl;
}
};
w kodzie programu umieszczamy taki oto prosty test:
{ // zakres 1 (ang. scope 1)
std::auto_ptr pMyObject(new MyClass(1));
{ // zakres 2 (ang. scope 2)
std::auto_ptr pMyOtherObject(new MyClass(2));
}
}
Jak widzisz mimo, że utworzyliśmy zmienną dynamiczną za pomocą new, nie korzystamy w ogóle z delete.
Dlaczego ? Ponieważ robi to za nas inteligentny wskaźnik. Odciąża nas od konieczności pamiętania o czynności usuwania zmiennych dynamicznych i jednocześnie upraszcza kod. Ma to szczególnie istotne znaczenie gdy mamy bardzo złożone struktury danych. Przecież i Ty i Ja nie jesteśmy maszynami i mamy prawo się pomylić czy o czymś zapomnieć.
Ile za to płacimy ? Otóż to udogodnienie kosztuje nas niewiele, gdyż klasa auto_ptr zajmuje mało miejsca i opłaca się jej używać nawet dla małych struktur (w takim przypadku zyskujemy komfort pomimo dodatkowej pamięci).
Istnieje jednak pewien kruczek, o którym powinieneś wiedzieć zanim zaczniesz używać tej klasy, a szczególnie wtedy, gdy obiekty tej klasy będą używane jako parametry funkcji/metod czy będą wartością zwracaną. Zwykłe przypisanie jednego wskaźnika automatycznego do drugiego to, jak pamiętasz uruchomi mechanizm klonowania obiektu poprzez operator przypisania. Klasa auto_ptr podczas kopiowania wydziedzicza wskazywany obiekt (czyli obiekt który utworzyliśmy za pomocą new).
Spójrz na przykład:
{ // zakres 1 (ang. scope 1)
std::auto_ptr pMyObject(new MyClass(1));
{ // zakres 2 (ang. scope 2)
std::auto_ptr pMyOtherObject(new MyClass(2));
pMyObject = pMyOtherObject;
}
}
Oznacza to, że podczas przypisania pMyObject = pMyOtherObject; wskaźnik na obiekt nr 2 pamiętany w pMyOtherObject zostanie skopiowany do wskaźnika pMyObject i tym samym obiekt nr 1 zostanie usunięty i jego pamięć automatycznie zwolniona. Po tym fakcie następuje wydziedziczenie i wskaźnik automatyczny pMyOtherObject zostanie zresetowany (nie przechowuje już wskaźnika na obiekt nr 2). Mówimy o wydziedziczeniu na rzecz innego wskaźnika, gdyż prawa własności do obiektu są przenoszone, a nie współdzielone. Prawo własności oznacza, że właściciel (ang. owner) ma obowiązek zarządzać pamięcią obiektu w odróżnieniu od rodzica (ang. parent), który dostarcza swoim dzieciom tylko cech (tak jak w genetyce wygląd, osobowość, etc).
Jeśli zdefiniujemy dwie funkcje o sygnaturach jak niżej
void myFunc1(std::auto_ptr); void myFunc2(std::auto_ptr&);
to wywołując myFunc1(pMyOtherObject) spowodujemy usunięcie obiektu nr 2 pod koniec działania tej funkcji ponieważ wykonane zostało kopiowanie automatycznego wskaźnika do zmiennej reprezentującej parametr funkcji, a parametry przestają istnieć gdy tylko konczy się wykonywać funkcja. Taka sytuacja nie zajdzie gdy wywołamy funkcję myFunc2(pMyOtherObject).
PAMIĘTAJ! Nie twórz parametrów typu std::auto_ptr, ale jako referencje do nich std::auto_ptr&.
Najświeższe komentarze