Mamy taki kod:
#include <iostream>
using namespace std;
class SimpleCar
{
public:
SimpleCar(); // konstruktor bezargumentowy
SimpleCar(SimpleCar&); // konstruktor kopiujący
~SimpleCar(); // destruktor
};
SimpleCar::SimpleCar(){cout << "Wywolanie kostruktora bezargumentowego...\n";}
SimpleCar::SimpleCar(SimpleCar&){cout << "Wywolanie kostruktora kopiujacego...\n";}
SimpleCar::~SimpleCar(){cout << "Wywolanie destruktora...\n";}
SimpleCar FunctionOne(SimpleCar theCar); // parametr przekazywany poprzez wartość
SimpleCar* FunctionTwo(SimpleCar *theCar); // parametr przekazywany poprzez wskaźnik
int main()
{
cout << "Tworze obiekt SimpleCar...\n";
SimpleCar Mercedes;
cout << "\n***Uzycie 'FunctionOne'***\n\n";
cout << "Wywoluje FunctionOne...\n";
FunctionOne(Mercedes);
cout << "\n***Uzycie 'FunctionTwo'***\n\n";
cout << "Wywoluje FunctionTwo...\n";
FunctionTwo(&Mercedes);
cout << "\n";
return 0;
}
SimpleCar FunctionOne(SimpleCar theCar)
{
cout << "FunctionOne. Wracam...\n";
return theCar;
}
SimpleCar* FunctionTwo(SimpleCar *theCar)
{
cout << "FunctionTwo. Wracam...\n";
return theCar;
}
Wynik jego działania jest następujący:
Tworze obiekt SimpleCar...
Wywolanie kostruktora bezargumentowego...
***Uzycie 'FunctionOne'***
Wywoluje FunctionOne...
Wywolanie kostruktora kopiujacego...
FunctionOne. Wracam...
Wywolanie kostruktora kopiujacego...
Wywolanie destruktora...
Wywolanie destruktora...
***Uzycie 'FunctionTwo'***
Wywoluje FunctionTwo...
FunctionTwo. Wracam...
Wywolanie destruktora...
Teraz chcę rozwiać moje wątpliwości czy dobrze to wszystko rozumiem :)
Gdy tworzę obiekt, w odniesieniu do powyższego kodu, wywoływany jest konstruktor bezargumentowy (domyślny). Jeśli klasa miałaby jakieś pola, a także posiadała odpowiedni konstruktor tworzenie obiektu mogłoby wyglądać następująco: SimpleCar Mercedes(1994,lpg)
. Oczywiście mógłbym pozostawić deklarację SimpleCar Mercedes
i wówczas wywołany byłby również konstruktor domyślny. Dodatkowo, jeśli sam nie zadeklaruję w klasie konstruktora bezargumentowego, to zostanie on stworzony podczas kompilacji. To chyba dobrze rozumiem.
Dalej mam wywołania dwóch funkcji. Pierwsza, FunctionOne, otrzymuje parametr poprzez wartość, zwraca również wartość. Druga, FunctionTwo, otrzymuje parametr poprzez wskaźnik do obiektu SimpleCar, zwraca również wskaźnik do obiektu.
Przejdę teraz do opisu działania obu funkcji w kodzie. Zacznę od FunctionOne. Najpierw wywołuję funkcję, co wiąże się z uprzednim wyświetleniem komunikatu zawartego w ciele funkcji main. Funkcja ta otrzymuje obiekt poprzez wartość, więc na stosie tworzona jest kopia przekazywanego obiektu, dlatego wywoływany jest konstruktor kopiujący. Następnie wykonanie przechodzi do ciała funkcji FunctioOne, o czym świadczy stosowny komunikat. Następnie wykonanie powraca do ciała funkcji main, jednocześnie FunctionOne zwraca poprzez wartość obiekt typu SimpleCar, co powoduje wywołanie konstruktora kopiującego (ponowne), gdyż na stosie tworzona jest kolejna kopia obiektu (tym razem jest to obiekt zwracany). Wartość ta nie jest przypisywana niczemu, obiekt na stosie jest niszczony, co powoduje wywołanie destruktora. Działanie FunctionOne jest zakończone, utworzona wcześniej kopia przekazanego jej obiektu jest niszczona, więc znowu wywoływany jest destruktor. Sterowanie wraca do ciała funkcji main.
Następnie wywoływana jest funkcja FunctionOne. Funkcja ta otrzymuje parametr przekazywany przez... (!) no i tu mam wątpliwości, nieco zgłupiałem. Wydaje mi się, że jest to przekazywanie parametru przez wskaźnik. Niezależnie czy używam wskaźników czy referencji do przekazywania parametrów, nie jest tworzona kopia obiektu, dlatego nie jest wywoływany konstruktor. Jest przekazywany jego (obiektu) adres. Obiekt zwracany jest również przez wskaźnik, więc nic nie jest niszczone. Sterowanie jest przekazywane do ciała funkcji main.
Na koniec wywoływany jest destruktor, gdyż program kończy działanie, a obiekt Mercedes
jest niszczony.
Użycie funkcji, która otrzymuje parametry i zwraca wynik poprzez wartość wiązało się z dwukrotnym wywołaniem konstruktora kopiującego i dwukrotnym wywołaniem destruktora. Natomiast wykorzystanie wskaźników zredukowało ilość wywołań, ani konstruktor, ani destruktor nie musiał być wywołany. Wniosek jest zatem taki, że użycie wskaźników pozwala na większą wydajność. Czy tak jest zawsze ?
Czy dobrze rozumiem działanie kodu ? Czy można coś dodać ?