czym jest konstruktor kopiujący.
ogólnie zasada jest taka, którą powinnaś zapamiętać i się tego trzymać (przynajmniej jeśli jeszcze nie wszystko robisz świadomie) - gdy twoja klasa przechowuje jakiś wskaźnik "z automatu" powinnaś zdefiniować w klasie "świętą trójcę" tzn.
- konstruktor kopiujący
- operator przypisania
- destruktor
ponadto jeśli klasa ma być klasą bazową dla innych klas destruktor powinien być destruktorem wirtualnym.
Konstruktor kopiujący rodzaj konstruktora, który przyjmuje jako parametr referencję (powinien stałą referencję chociaż są wyjątki, których powinno się unikać) na obiekt klasy, której definiujesz ten konstruktor. Wywoływany jest w różnych przypadkach gdy jest wykonywana kopia twojego oryginalnego obiektu inicjalizowana nim, czyli na przykład w sytuacjach:
- przy jawnym wywołaniu konstruktora kopiującego podczas inicjalizacji [1]
- przy przekazywaniu parametrów do funkcji przez wartość [2]
- przy korzystaniu z kontenerów stl [3]
Zdefiniowanie tego konstruktora w przypadku klasy ze wskaźnikiem jest niezbędne ponieważ domyślnie wygenerowany przez kompilator konstruktor kopiujący, jak sama intuicja podpowiada kopiuje pola jednej klasy do drugiej. Niestety w przypadku wskaźników samo skopiowanie wskaźnika nie jest tym czego ty chcesz ponieważ sama operacja kopiowania powoduje iż powstaje druga zmienna wskaźnikowa (w skopiowanym obiekcie), która wskazuje na to samo miejsce w pamięci, tzn.
Masz obiekt 'obj1' z jakimś wskaźnikiem, oraz kopie 'obj2' obiektu 'obj1', która zawiera wskaźnik na to samo miejsce w pamięci co wskaźnik z 'obj1'. Teraz załóżmy, że któryś z obiektów niszczysz, nie ważne który. Tutaj pojawia się problem ponieważ po zniszczeniu jednego z tych obiektów korzystanie z drugiego obiektu staje się niebezpieczne bo przechowuje on wskaźnik, który wskazuje na zwolnioną pamięć (pamięć ta została zwolniona przez destruktor jednego z tych obiektów - tego, który został usunięty). Masz najzwyczajniej w świecie wyciek pamięci. Żeby się przed tym uchronić nie możesz kopiować wskaźnika, tylko jego wartość to znaczy:
- nowym obiekcie musisz przydzielić nowe miejsce w pamięci
- miejsce to wypełnić kopią wartości wskaźnika obiektu, z którego kopiujesz
To powinien robić konstruktor kopiujący w twoim wypadku. Przykład poniżej.
Operator przypisania
Operator przypisania musi się znaleźć z podobnego względu jak konstruktor kopiujący tylko zostaje on wywołany w innym przypadku - jak intuicja podpowiada podczas przypisania (pokazane w przykładzie poniżej).
Destruktor
Jeśli w konstruktorach przydzielasz pamięć (np przy pomocy operatora new) musisz ją gdzieś zwolnić. Czynisz to w destruktorze. Dlatego powinien się on tam znaleźć. O destruktorze wirtualnym nie będę ci w tej chwili pisał bo pomimo iż jest to równie proste to może jednak na tym etapie ci troszkę namieszać. Przyjdzie to z czasem. Na razie musisz wiedzieć to co napisałem.
przykład:
class A {
int *x;
public:
A(int x_) : x(new int(x_)) { cout << "konstruktor A()\r\n"; }
A(const A &a) : x(new int(*a.x))
{
cout << "konstruktor kopiujacy A(const A&) " << "\r\n";
}
~A()
{
if(x)
delete x;
cout << "destruktor ~A()\r\n";
}
A& operator= (const A &a)
{
if(&a != this) //na wypadek gdyby: obj = obj
{
if(x)
delete x;
x = new int(*a.x);
}
cout << "Operator przypisania operator=(const A&)\r\n";
return *this;
}
void wypiszX() const { cout << "x: " << *x << "\r\n"; }
};
void foo(A obj) {}
int main()
{
A obj1(20);
obj1.wypiszX();
A obj2(obj1); //"jawne" wywolanie konstruktora kopiujacego [1]
obj2.wypiszX();
cout << "przy wejsciu do foo: ";
foo(obj1); //przekazywanie przez wartosc [2]
A obj3(30);
obj3.wypiszX();
obj3 = obj1; // przypisanie
obj3.wypiszX();
vector<A> v;
v.push_back(obj1); //przy kontenerach stl [3]
}
Teraz nasuwają się pytania:
- dlaczego konstruktor kopiujący przyjmuje referencje (stałą) do obiektu, a nie przekazuje parametru przez wartość? - ponieważ przy przekazaniu przez wartość wywoływany jest konstruktor kopiujący czyli przy definiowaniu konstruktora kopiującego musiałabyś wykorzystać ... konstruktor kopiujący - głupie nie? I bezsensowne, dlatego jest tam referencja.
Dlaczego referencja znajduje się przy twoich operatorach? Pomijając już to, że wewnątrz tych operatorów wykonujesz operacje wpływające na stan obiektu, który przekazujesz (fuj :/ - wywołując ten operator nie do końca liczę na to iż mój argument ulegnie jakiejkolwiek zmianie) to spójrz na to z tej strony - operacje te mogą być wywoływane wielokrotnie podczas korzystania z twojej klasy. Tworzenie za każdym razem kopii obiektu, który się przekazuje nie jest dobrym rozwiązaniem - jest pamięciożerne i czasochłonne - po co to robić skoro można przekazać oryginał ?
Jeszcze mała uwaga:
Witam, mam pewne pytanie związane z konstrukcją klasy macierzy rzeczywistych, którą napisaliśmy na zajęciach z programowania - dotyczy to głównie składni języka C++.
Fragmenty kodu:
macierz::macierz()
{
tymcz==0;
n=3;
m=3;
t=new double*[n];
for(int i =0 ; i<n;i++)
t[i]=new double[m];
for (int i=0;i<n;i++)
for(int j=0; j<m;j++)
t[i][j]=0;
}
tymcz == 0 ?
oraz to zerowanie - możesz wykorzystać to iż w pierwszej pętli (tej z new) przelatujesz bo wierszach.
Dodatkowo:
macierz ::~macierz()
{
if(tymcz==0)
{
for(int i=0;i<n;i++)
delete t[i];
delete t;
}
}
tutaj jest wywoływany zła wersja operatora delete (delete []).
Na koniec - nie powinnaś się unosić z powodu odpowiedzi, które ci udzielono ponieważ userzy mają rację - pytasz o totalne podstawy, nie stosujesz się do regulaminu działu (o netykiecie nie wspomnę) jednak kod źródłowy oraz staż Cie ratują. Przyjmij krytykę z pokorą i wyciągnij naukę z tego, nikt tutaj ci nie złorzeczy ani nie chce zaszkodzić. Więcej własnego wkładu oraz samodzielnych zmagań z poszukiwaniami, przeglądanie forów, czytanie dokumentacji rozwiną nie tylko twoje umiejętności programistyczne ale również nauczą skutecznego wyszukiwania informacji. Zadanie pytania na forum jest najłatwiejszym rozwiązaniem - najmniej uczącym i najbardziej czasochłonnym - uwierz mi, z mojego postu nawet jeśli coś wyniesiesz to zapomnisz większość bo sama do tego nie doszłaś.
pozdrawiam.