Wednesday, December 8, 2010

Przeciążenie operatora przypisania

Podczas pisania programu zbiór klas, które stworzyłem dosyć się rozrósł i nieuchronnie sposób kodowania zmierzał do korzystania z zaawansowanych właściwości języka C++. Pierwszym wartym opisania jest operator przypisania, czyli "=". Dlaczego warto go używać, a raczej trzeba.

Po pierwsze zacząłem programować bardziej w stylu programowania kompozycyjnego niż obiektowego - jeszcze nie wiem czy to dobry pomysł. Zastanawiam się nad wprowadzeniem dziedziczenia ponieważ zdarza się, że muszę definiować takie same metody w różnych klasach np. set_name(string).
Operator przypisania (operator=) jest przydatny podczas takiego projektowania:

class A
{
int a;
};

class B
{
A _a;
public:
void set_a (A);
void set_a2 (int);
};

Jak widzimy mamy dwie wersję funkcji set_a(). Pierwsza pobiera argument typu a więc w definicji funkcji zostaje tylko przypisać do do tego mieszczącego się w klasie b.

void B::set_a (A _newa)
{
_a = _newa;
}

I tu pojawia się potrzeba implementacji operatora przypisania.
Z drugiej zaś strony, gdy chcielibyśmy ukryć wewnętrzną budowę klasy A (hy po co...) należałoby użyć drugiej wersji metody


void B::set_a2 (int _newval)
{
// np. set()
_a.set(_newval);;
}

Problem pojawia się wtedy, gdy klasa A ma wiele właściwości i nadal "rośnie". Wtedy prototyp i definicja metody nie wygląda czytelnie.

Tak, teraz mamy dwie drogi. Albo definiujemy klasę B jako dziedziczącą po A i mamy w klasie B bezproblemowy dostęp do składowych klasy A w sekcji protected, albo używamy kompozycji. Mimo wszystko dziedziczenie nie rozwiązuje problemu kopiowania obiektów, które de fakto zawierają wiele danych. Tworzenie dynamicznych obiektów i wskaźniki do nich jako składowe w tym przypadku uprościłyby sprawę. Wtedy nie ma potrzeby kopiowania składowej po składowej. Usuwamy starą pamięć i do wskaźnika przypisujemy wartość nowego obiektu. Podpatrzyłem taki pomysł w źródłach projektu Scintilla w pliku Editor.h:

Editor &operator=(const Editor &) { return *this; }

Ta definicja przeciążonego operatora bardzo mi się podoba jednak moja jeszcze jest inna:

class Location
{

private:
string    name;
t_classroom classroom_table;

public:
Location& operator= (const Location& cp);
};


Location& Location::operator= (const Location& cp)
{
if (this != &cp)
{
name = cp.name;
classroom_table = cp.classroom_table;
}
return *this;
}

Przy okazji odkryłem pewną właściwość C++, o której nie wiedziałem, albo nie pamiętam. Jak widzimy zmienna name jest prywatna, a jednak w definicji operatora używamy jej jakby była publiczna. Sprawdziłem to pisząc odpowiedni kod i okazuje się, że prywatne dane dla innego obiektu tego samego typu wcale nie są taki prywatne. Dopiero kiedy spróbujemy użyć prywatnej zmiennej w metodzie innej klasy zobaczymy komunikat o tym, iż zmienna jest prywatna.

Pseudokod ułatwi zrozumienia działania przeciążenia operatora przypisania:

Location a, b;
//a.operator=(b);
a = b;

Tak to wygląda po rozwinięciu przez kompilator. Teraz pytanie dlaczego referencje? Aby nie kopiować argumentu formalnego funkcji. Dlaczego nie wskaźnik? Bo operacja przypisania wtedy wyglądałaby tak:

&a = &b;

Prawda, że ładnie?
Jeszcze dlaczego argument jest const? Aby nie zmienić jego zawartości.

Należy pamiętać o tym, że każdy typ, z którego korzystamy w nowym typie, gdzie definiujemy operator przeciążenia również musi posiadać taką funkcji, ponieważ będzie ona w takim przypadku wywoływana rekurencyjnie.

No comments:

Post a Comment