Pozwala ci na dostarczenie intuicyjnego interfejsu dla użytkowników twojej klasy, oraz umożliwia wzorcom klas prawidłową współpracę w takim samym stopniu z klasami i wbudowanymi/wewnętrznymi typami.
Przeładowanie operatora pozwala operatorom C/C++ na posiadanie zdefiniowanych przez użytkownika znaczeń dla zdefioniwanych przez użytkownika typów (klas). Przeładowane operatory są synktatycznym cukrem dla wywołań funkcji:
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
Przez przeciążanie standardowych operatorów na klasie, możesz wykorzystać intuicję użytkownika tej klasy. To z kolei pozwala użytkownikom programować w języku z którego pochodzi problem, zamiast w języku maszyny na której pracują.
Najważniejszymi celem jest na równi redukcja krzywej uczenia się oraz częstości występowania defektów.
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
Oto kilka z wielu przykładów:
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
Przeciążanie operatora czyni życie łatwiejszym dla użytkowników klasy, nie programisty klasy!
Rozważ poniższy przykład.
Niektórzy ludznie nie lubią słowa kluczowego operator albo na swój sposób zabawnej składni, jaka ląduje wewnątrz z nim wewnątrz klasy. Ale przeciążanie operatora nie zostało pomyślane jako ułatwienie życia dla programisty klasy. Ma ułatwić życie użytkownikom klasy:
Pamiętaj: w "świecie ponownego użycia" będzie zwykle wiele osób, które użyją twojej klasy, ale tylko jedna, która ją zbuduje (ty); dlatego właśnie powieneś robić rzeczy z korzyścią dla większości zamiast jednostki.
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
Większość może być przeciążana. Jedyne operatory C, które nie mogą to . i ?: (i sizeof, który technicznie jest operatorem). C++ dodaje kilka swoich własnych operatorów, z których przeciążane mogą być wszystkie z wyjątkiem :: i .*.
Oto przykład z operatorem indeksu (zwraca referencję). Pierwszy bez przeciążania operatora:
A teraz to samo z przeciążeniem operatora:
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
Nie: przynajmniej jeden z operandów przeciążaneg operatora musi być typu zdefioniowanego przez użytkownika (przez większość czasu oznacza to class).
Ale nawet jeśli C++ pozwoliłby ci na to, nie chciałbyś tego robić, ponieważ powinieneś używać std::string-opodobnych klas w pierwszej kolejności zamiast tablic znaków gdyż tablice należą do złych rozwiązań.
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
Nope.
The names of, precedence of, associativity of, and arity of operators is fixed by the language. There is no operator** in C++, so you cannot create one for a class type.
If you're in doubt, consider that x ** y is the same as x * (*y) (in other words, the compiler assumes y is a pointer). Besides, operator overloading is just syntactic sugar for function calls. Although this particular syntactic sugar can be very sweet, it doesn't add anything fundamental. I suggest you overload pow(base,exponent) (a double precision version is in <cmath>).
By the way, operator^ can work for to-the-power-of, except it has the wrong precedence and associativity.
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
Używajoperator() zamiast operator[].
Kiedy masz do czynienia z wiekszą ilością indeksów, najlepszym rozwiązaniem jest użycie właśnie operator() zamiast operator[]. Powodem taiego właśnie działania jest to, iż operator[] zawsze przyjmuje dokładnie jeden parametr, a operator() może przyjąć dowolną ich ilość (in the case of a rectangular matrix, two paramters are needed).
Dla przykładu:
Wtedy możesz uzyskać dostęp do elementu Matrix m poprzez m(i,j) zamiast m[i][j]:
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
Oto o co chodzi w tym faq: niektórzy ludzie budują taką klasę tak, że posiada ona operator[], który zwraca referencję do obiektu Array. Z kolei ten obiekt posiada operator[], który zwraca elemnt klasy (np. referncję do double)
Here's what this FAQ is really all about: Some people build a Matrix class that has an operator[] that returns a reference to an Array object, and that Array object has an operator[] that returns an element of the Matrix (e.g., a reference to a double). Thus they access elements of the matrix using syntax like m[i][j] rather than syntax like m(i,j).
The array-of-array solution obviously works, but it is less flexible than the operator() approach. Specifically, there are easy performance tuning tricks that can be done with the operator() approach that are more difficult in the [][] approach, and therefore the [][] approach is more likely to lead to bad performance, at least in some cases.
For example, the easiest way to implement the [][] approach is to use a physical layout of the matrix as a dense matrix that is stored in row-major form (or is it column-major; I can't ever remember). In contrast, the operator() approach totally hides the physical layout of the matrix, and that can lead to better performance in some cases.
Put it this way: the operator() approach is never worse than, and sometimes better than, the [][] approach.
As an example of when a physical layout makes a significant difference, a recent project happened to access the matrix elements in columns (that is, the algorithm accesses all the elements in one column, then the elements in another, etc.), and if the physical layout is row-major, the accesses can "stride the cache". For example, if the rows happen to be almost as big as the processor's cache size, the machine can end up with a "cache miss" for almost every element access. In this particular project, we got a 20% improvement in performance by changing the mapping from the logical layout (row,column) to the physical layout (column,row).
Of course there are many examples of this sort of thing from numerical methods, and sparse matrices are a whole other dimension on this issue. Since it is, in general, easier to implement a sparse matrix or swap row/column ordering using the operator() approach, the operator() approach loses nothing and may gain something it has no down-side and a potential up-side.
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
>From the outside!
A good interface provides a simplified view that is expressed in the vocabulary of a user. In the case of OO software, the interface is normally the set of public methods of either a single class or a tight group of classes.
First think about what the object logically represents, not how you intend to physically build it. For example, suppose you have a Stack class that will be built by containing a LinkedList:
Should the Stack have a get() method that returns the LinkedList? Or a set() method that takes a LinkedList? Or a constructor that takes a LinkedList? Obviously the answer is No, since you should design your interfaces from the outside-in. I.e., users of Stack objects don't care about LinkedLists; they care about pushing and popping.
Now for another example that is a bit more subtle. Suppose class LinkedList is built using a linked list of Node objects, where each Node object has a pointer to the next Node:
Should the LinkedList class have a get() method that will let users access the first Node? Should the Node object have a get() method that will let users follow that Node to the next Node in the chain? In other words, what should a LinkedList look like from the outside? Is a LinkedList really a chain of Node objects? Or is that just an implementation detail? And if it is just an implementation detail, how will the LinkedList let users access each of the elements in the LinkedList one at a time?
The key insight is the realization that a LinkedList is not a chain of Nodes. That may be how it is built, but that is not what it is. What it is is a sequence of elements. Therefore the LinkedList abstraction should provide a "LinkedListIterator" class as well, and that "LinkedListIterator" might have an operator++ to go to the next element, and it might have a get()/set() pair to access its value stored in the Node (the value in the Node element is solely the responsibility of the LinkedList user, which is why there is a get()/set() pair that allows the user to freely manipulate that value).
Starting from the user's perspective, we might want our LinkedList class to support operations that look similar to accessing an array using pointer arithmetic:
To implement this interface, LinkedList will need a begin() method and an end() method. These return a "LinkedListIterator" object. The "LinkedListIterator" will need a method to go forward, ++p; a method to access the current element, *p; and a comparison operator, p != a.end().
The code follows. The important thing to notice is that LinkedList does not have any methods that let users access Nodes. Nodes are an implementation technique that is completely buried. This makes the LinkedList class safer (no chance a user will mess up the invariants and linkages between the various nodes), easier to use (users don't need to expend extra effort keeping the node-count equal to the actual number of nodes, or any other infrastructure stuff), and more flexible (by changing a single typedef, users could change their code from using LinkedList to some other list-like class and the bulk of their code would compile cleanly and hopefully with improved performance characteristics).
Here are the methods that are obviously inlinable (probably in the same header file):
Conclusion: The linked list had two different kinds of data. The values of the elements stored in the linked list are the responsibility of the user of the linked list (and only the user; the linked list itself makes no attempt to prohibit users from changing the third element to 5), and the linked list's infrastructure data (next pointers, etc.), whose values are the responsibility of the linked list (and only the linked list; e.g., the linked list does not let users change (or even look at!) the various next pointers).
Thus the only get()/set() methods were to get and set the elements of the linked list, but not the infrastructure of the linked list. Since the linked list hides the infrastructure pointers/etc., it is able to make very strong promises regarding that infrastructure (e.g., if it was a doubly linked list, it might guarantee that every forward pointer was matched by a backwards pointer from the next Node).
So, we see here an example of where the values of some of a class's data is the responsibility of users (in which case the class needs to have get()/set() methods for that data) but the data that the class wants to control does not necessarily have get()/set() methods.
Note: the purpose of this example is not to show you how to write a linked-list class. In fact you should not "roll your own" linked-list class since you should use one of the "container classes" provided with your compiler. Ideally you'll use one of the standard container classes such as the std::list<T> template.
[ Góra | Dół | Poprzednia sekcja | Następna sekcja | Szukaj w FAQ ]
E-mail the author
[ C++ FAQ Lite
| Spis treści
| Skorowidz
| O autorze
| ©
| Pobierz swoją własną kopię ]
Ostatnia aktualizacja Jun 17, 2002
Wersja polska: 0.1h Jul 9, 2004