[12] Operatory przypisania
(Część C++ FAQ Lite, Copyright © 1991-2002, Marshall Cline, cline@parashift.com)


FAQ - sekcja [12]:


[12.1] Co to jest pseudo-przypisanie [ang. self-assignment] ?

Pseudo-przypisanie następuje wtedy gdy ktoś przypisuje obiekt do niego samego. Przykład:

 #include "Fred.hpp"    // deklaruje klasę Fred
 
 void userCode(Fred& x)
 {
   x = x;   
// Pseudoprzypisanie
 }

Oczywiście nikt nigdy nie popełnia tak jaskrawych przykładów pseudo-przypisań, ale odkąd więcej niż jeden wskaźnik lub referencja może odwoływać się do tego samego obiektu (aliasing), możesz utworzyć takie przypisanie nie wiedząc o tym:

 #include "Fred.hpp"    // deklaruje klasę Fred
 
 void userCode(Fred& x, Fred& y)
 {
   x = y;   
// może być pseudo-przypisaniem jeśli &x == &y
 }
 
 int main()
 {
   Fred z;
   userCode(z, z);
 }

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


[12.2] Dlaczego powinienem uważać na pseudo-przypisania?

Jeśli nie będziesz się przejmował pseudo-przypisaniami, narazisz swoich użytkowników na bardzo subtelne bugi, które mają bardzo subtelne i często katastrofalne symptomy. Na przykład, znajdująca się poniżej klasa spowoduje kompletną katastrofę w przypadku pseudo-przypisania:

 class Wilma { };
 
 class Fred {
 public:
   Fred()                : p_(new Wilma())      { }
   Fred(const Fred& f)   : p_(new Wilma(*f.p_)) { }
  ~Fred()                { delete p_; }
   Fred& operator= (const Fred& f)
     {
       
// Zły kod: Nie radzi sobie z pseudo-przypisaniem!
       delete p_;                
// Linia #1
       p_ = new Wilma(*f.p_);    
// Linia #2
       return *this;
     }
 private:
   Wilma* p_;
 };

Jeśli ktoś przypisze obiekt Fred do niego samego, linia #1 usunie this->p_ i f.p_ ponieważ *this i f są tym samym obiektem. Ale w linii #2 używamy *f.p_, który nie jest już poprawnym obiektem. To spowoduje poważną katastrofę.

Ty jako autor klasy Fred jesteś odpowiedzalny za to, by wykluczyć możliwość pseudo-przypisania obiektu Fred. Nie zakładaj, zę inni użytkownici nigdy nmie zrobią czegoś takiego z twoimi obiektemami. To jest tylko twoja wina jeśli obiekt nie poradzi sobie z pseudo-przypisaniem.

Na marginesie: Powyższe wyrażenie Fred::operator= (const Fred&) zawiera także drugi problem: Jeśli zostanie zgłoszony [ang. throw] wyjątek w czasie szacowania new Wilma(*f.p_) (np. wyjątek out-of-memory lub wyjątek w konstruktorze kopii Wilmy), this->p_ będzie wskaźnikiem zawiszonym [ang. dangling pointer] — będzie wskazywał na mijesce pamięci, które nie jest już prawidłowy. Można temu zapobiec poprzez alokowanie nowych obiektów przed kasowaniem starych.

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


[12.3] OK, OK, już dobrze; Zajmę się pseudo-przypisaniami. Jak to zrobić?

Powinieneś martwić się pseudo-przypisaniem za każdym razem kiedy tworzysz klasę. Nie oznacza to że musisz dodawać ekstra kod do wszystkich swoich klas: dopóki twoje obiekty płynnie radzą sobie z pseudo-przypisaniem dopóty nie ma znaczenia wstawianie dodatkowego kodu.

Jeśli musisz wstawić dodatkowy kod do swojego przypisania, oto prosty i efektywny sposób:

 Fred& Fred::operator= (const Fred& f)
 {
   if (this == &f) return *this;   
// Spokojnie poradzi sobie z pseudo-przypisaniem
 
   
// Zwykle przypisania wstawiasz tu...
 
   return *this;
 }

Taki jawny test nie jest zawsze konieczy. Na przykład, gdybyśmy mieli poprawić operator przypisania w poprzednim faq tak, by radził sobie z wyjątkami zgłaszanymi przez new i/lub z wyjątkami zgłaszanymi przez konstruktor kopii klasy Wilma, moglibyśmy zastosować poniższy kod. Zauważ, że ten kod posiada (sympatyczny) efekt uboczny, jakim jest automatyczne radzenie sobie również z pseudo-przypisaniem:

 Fred& Fred::operator= (const Fred& f)
 {
   
// Ten kod płynnie (albeit implicitly) obezwładnia
   
// pseudo-przypisanie
   Wilma* tmp = new Wilma(*f.p_);   
// Będzie OK jeśli jakiś
       
// wyjątek zostanie tu zgłoszony
   delete p_;
   p_ = tmp;
   return *this;
 }

W przypadkach takich jak powyższy przykład (gdzie pseudoprzypisanie jest nieszkodliwe ale wpływające na efektywność), część programistów chce poprawić ową efektywność poprzez dodanie całkiem niepotrzebnego testu, jak "if (this == &f) return *this;". Generalnie jest to przejaw złego myślenia. Dla przykładu, dodywanie powyżeszgo if do opertora Fred uczyni wszystkie inne przypadki przypisań mniej efektywnymi (dodatkowy (i niepotrzebny) test warunkowy). Jeśli pseudo-przypisanie pojawi się raz na tysiąc razy, if zmarnuje 99.9% cyklów czasowych.

GóraDółPoprzednia sekcjaNastępna sekcjaSzukaj w FAQ ]


E-Mail E-mail the author
C++ FAQ LiteSpis treściSkorowidzO autorze©Pobierz swoją własną kopię ]
Ostatnia aktualizacja Jun 17, 2002
Wersja polska: 0.1i Jul 12, 2004