Kiedy tego nie robić?
Przeciążenie (overload) operatora ma tylko sens, jeżeli programista nie będzie zaskoczony jego działaniem.Standard nie gwarantuje w jakiej kolejności będą wołane operandy. Np. dla f() + g(), nie wiemy, która z funkcji zostanie zawołana jako pierwsza. Mamy za to pewność że priorytety operatorów będą zachowane (czyli np. mnożenie będzie przed dodawaniem) [C++Prime, s. 138].
Nie należy przeciążać operatora kropki (.), pobrania adresu (&), logicznego AND (&&) oraz logicznego OR (||).
#include <iostream> using namespace std; struct Num { Num(int v) : value(v) { } Num() : Num(0) { } int value; }; Num operator+(const Num& first, const Num& second) { return Num(first.value + second.value); } Num operator*(const Num& first, const Num& second) { return Num(first.value * second.value); } int main() { Num a(2); Num b(2); Num c(2); cout << (a + b).value << endl; cout << (a * b).value << endl; cout << (a * b + c).value << endl; cout << (a + b * c).value << endl; }Wynik:
4 4 6 6
Gdzie tworzyć deklaracje?
Kiedy już decydujemy się na przeciążenie operatora, stajemy przed dylematem, czy stworzyć go jako element składowy klasy, czy jako funkcję poza klasą. Pomocne mogą być poniższe wskazówki:- operatora przypisania (=), nawisu kwadratowego ([]), zawołania (()) oraz strzałki (->) muszą być zdefiniowane jako składniki klasy
- operatory mieszane z przypisaniem (+=, -=, *=, /= itp.) generalnie powinny być składowymi klasy, ale w przeciwieństwie do samego operatora przypisania (=) nie jest to wymagane
- operatory które zmieniają stan obiektu, albo są z nim ściśle powiązane np. inkrementacja (++), dekrementacja (--), dereferencja (*) zazwyczaj powinny być składowymi klasy
- operatory symetryczne (takie które mogą konwertować którykolwiek z operandów) tj. arytmetyczne (+, -), równości (==), relacji (<, >, <=, <=) i bitowe (|, &, ^, <<, >>), zazwyczaj powinny być definiowane jako funkcje poza klasą
Szczegóły dla konkretnych operatorów
Operatory I/O - wejścia, wyjścia - (<<, >>), powinny być funkcjami poza klasą i powinny drukować zawartość z bardzo minimalnym formatowaniem. Jako, że czasami muszą mieć dostęp do niepublicznych danych, powinny być zadeklarowane jako przyjaciele (friend) klasy. Operator wejściowy powinien radzić sobie z sytuacją, gdy "wejście" zawiedzie, operator wyjściowy generalnie się tym nie przejmuje.Dla operatora nawiasu kwadratowego ([]) warto zrobić wersję const oraz non-const
Dla operatorów inkrementacji (++) i dekrementacji (--) istnieją dwie wersje: prefiksowa i postfiksowa. Wersja postfiksowa dla rozróżnienia bierze jako argument dodatkowy nieużywany parametr typu int, kompilator wstawia tam zero.
#include <iostream> using namespace std; struct Counter { double d; Counter() : d(0.12) { } Counter operator++() { d += 1.0; cout << "prefix: ++obj" << endl; return *this; } Counter operator++(int) { Counter tmp = *this; d += 1.0; cout << "postfix: obj++" << endl; return tmp; } }; int main() { Counter c; c++; ++c; }Wynik:
postfix: obj++ prefix: ++obj
Konwersje typów
Konwersje z jednego typu na drugi mogą być bardzo mylące. Czasami lepiej stworzyć odpowiednią metodą, która lepiej poinformuje nas o intencji danej konwersji. Operatory konwersji powinny być składowymi klasy, nie powinny określić typu zwracanego i posiadać pustą listą parametrów. Funkcja zazwyczaj powinna być const-owa.We wcześniejszej wersji języka konwersja na typ bool była problematyczna, ponieważ bool jest typem arytmetycznym. Obiekt klasy która pozwala na konwersję na bool, mógł zostać użyty we wszystkich miejscach, gdzie wymagany jest typ arytmetyczny. Gdyby std::cin dało się konwertować na typ bool, możliwa była by operacja "std::cin << 34" - w rezultacie doszło by do przesunięcia bitowego (bool zostałby jeszcze skonwertowany na int).
Nowy standard wprowadza konwersje explicit. Powinna się ona ograniczyć raczej tylko do bool i zazwyczaj z intencją sprawdzania wyniku w jakimś warunku.
Jeżeli konwersja jest explicit, możemy jej używać ale tylko z rzutowaniem. Wyjątkiem jest niejawne (implicit) korzystanie z konwersji dla:
- warunków w if, while oraz do-while
- warunków w for
- operatorów dla logicznego NOT (!), OR (||) oraz AND (&&)
- warunków dla ?:
#include <iostream> using namespace std; struct SmallInt { double val; SmallInt(double v) : val(v) { } explicit operator int() { return val; } explicit operator bool() { return val > 3.0; } }; int main() { SmallInt si(3.14); // cout << si + 1 << endl; // ERROR wymagana jest niejawna konwersja, // ale operator rzutowania na int jest explicit cout << static_cast<int>(si) + 1 << endl; if (si) cout << "Bigger than PI" << endl; return 0; }Wynik:
4 Bigger than PI
Brak komentarzy:
Prześlij komentarz