12 czerwca 2008

konstruktory obliczeniowe

Problem optymalizacji z pewnością nie należy do najprostszych, jednak pewne elementy C++ pozwalają przyśpieszyć działanie programów. Jednym z nich jest referencja, czyli znaczek '&' ;) Każdy wie, że przekazywanie parametrów do funkcji, przez wartość jest dość nieoptymalne, szczególnie dla większych obiektów. W tym przypadku możemy przekazać do funkcji referencje na dany obiekt, w celu uniknięcia wywołań konstruktora kopiującego i destruktora, podczas opuszczania funkcji. Jednak co ze zwracaniem wartości? Jeśli chcemy uniknąć kosztów konstrukcji obiektów podczas ich zwracania, możemy wykonać te operacje także stosując referencje. Jednak zwracanie referencji nie ma sensu, jeśli próbujemy zwrócić obiekt lokalny - jak sama nazwa wskazuje, istnieje on tyko w zakresie naszej funkcji. Dlatego na pierwszy rzut oka, standardowa implementacja operatorów arytmetycznych, wydaje sie być w pełni zoptymalizowana:

CMatrix CMatrix::operator+(CMatrix &rhs){
   CMatrix temp;
return temp;
}

Istnieje jednak inny, mniej znany i już raczej niepotrzebny sposób - opisuję go w ramach ciekawostki, bo sam na wykładzie z C++ byłem dość zaskoczony gdy go zobaczyłem. Na czym on polega? Otóż w powyższym kodzie wywoływane są dwa konstruktory: domyślny podczas tworzenia obiektu temp i kopiujący podczas jego zwracania. Wywołania tych dwóch różnych konstruktorów zastąpimy jednym: konstruktorem obliczeniowym. Na początku tworzymy ów konstruktor.
CMatrix(CMatrix &v1,CMatrix &v2){
    //operacje dodawania...
}
Konstruktor ten przyjmuje 2 macierze, które można ze sobą dodać. Ważne, żeby wynik dodawania nie był zapisywany w żadnym z obiektów, ale bezpośrednio w polach klasy. Potem cały kod operatora dodawania, zamieniamy na wywołanie tego konstruktora: CMatrix CMatrix::operator+(CMatrix &rhs){
   return CMatrix(*this,rhs);
} I tak oto tym sposobem, zaoszczędziliśmy wywołanie jednego konstruktora. Wcześniej wspomniałem, że sposób ten dzisiaj jest raczej bezużyteczny. Chodzi oczywiście o optymalizacje, które przeprowadzają za nas kompilatory, a w tym przypadku "optymalizacje wartości zwracanej". Te czynności wykonywane są jednak zazwyczaj dla prostych funkcji, łatwych do zinterpretowania dla maszyny.

2 komentarze:

  1. A to ci dobre ;). Tylko dodając następne operacje np. mnożenie macierzy trzeba by pokombinować.

    OdpowiedzUsuń
  2. Niekoniecznie.
    Można zrobić to na dwa sposoby:
    przekazać do konstruktora enuma i potem tylko sprawdzać jakie operacje chcemy wykonać lub zrobić oddzielne konstruktory dla każdej operacji, tylko dodać do każdego z nich dodatkowy, nieużywany argument. Korzystamyw ten sposób z przeciążania funkcji ;)

    OdpowiedzUsuń