Problem ze zrozumieniem idei zastosowania if''a

0

Pytanie: rozumiem o co chodzi w filozofii operatora przypisania "operator=",ale nie bardzo rozumiem ideę linijki "if (this==&rhs) return *this;" Po prostu jakoś mi się to w głowie kiełbasi. Co ta linijka daje oraz dlaczego są przyrównane do siebie this z &rhs'em?

class Cat{
public:
	Cat();
	int GetAge(){ return *itsAge; }
	int GetWeight(){ return *itsWeight; }
	void SetAge(int age){ *itsAge=age; }
	Cat operator=(Cat &);
private:
	int *itsAge;
	int *itsWeight;
};
Cat::Cat()
{
	itsAge=new int;
	itsWeight=new int;

	*itsAge=5;
	*itsWeight=9;
	cout <<"adres thisa: "<<this<<endl;
	
}
Cat Cat::operator=(Cat & rhs)
{
	if (this==&rhs) return *this;
	*itsAge=rhs.GetAge();
	*itsWeight=rhs.GetWeight();
	
	cout <<"adres rhs: "<<&rhs<<endl;
	return *this;


}
int main()
{
	char znak=0;
	Cat Mruczek;
	cout <<"bierzacy wiek mruczka: "<<Mruczek.GetAge()<<endl;
	cout <<"adres Mruczka: "<<&Mruczek<<endl;
	Cat filemon;
	filemon.SetAge(5);
	cout <<"bierzacy wiek filemona: "<<filemon.GetAge()<<endl;
	cout <<"adres filemona: "<<&filemon<<endl;
	filemon.operator=(Mruczek);
//tu może być też oczywiscie filemon=Mruczek;
	cout <<"wiek filemona: "<<filemon.GetAge()<<endl;
	cout <<"adres filemona: "<<&filemon<<endl;

	cout <<"koniec?: "<<endl;
	while(1)
	{
		cin>>znak;
		if (znak!='k') cout <<"to musi byc k: "<<endl;
		else exit(0);
	}

	return 0;
}
0

Zabezpieczenie przed self-assignmentem. Na przykad:

T x;
x = x;

W takim wypadku operator nie powinien nic robić. Jest to sprawdzane waśnie w tym ifie – sprawdzamy, czy obiekt przypisywany (rhs) jest tym samym obiektem, do którego przypisujemy (*this). Robi się to za pomocą porównania wskaźników.

0

Klasy, które same zarządzają swoją pamięcią powinny mieć: destruktor, operator kopiujący i operator przypisania. Ty masz tylko ten ostatni.
Przede wszystkim powinieneś się zastanowić co twój operator przypisania ma robić - wykonywać kopię głęboką czy płytką? Patrząc na właściwości tej klasy, powinien raczej wykonywać kopię głęboką (kopiować nie tylko wskaźniki, a to, co jest pod nimi). W takim razie, ogólny schemat operatora przypisania wygląda tak: zwolnienie pamięci, alokacja nowej i skopiowanie. W tym przypadku można sobie zwolnienie i alokację darować, bo niezależnie od wartości, ilość zaalokowanej pamięci będzie ta sama, a na końcu zwolni ją destruktor (który musisz dopisać).
Także w tym wypadku zabezpieczenie przed skopiowaniem siebie nie jest wymagane, bo nie zwalniasz pamięci. Wyobraź jednak sobie co by się stało, gdybyś ją zwolnił (uruchomiły by się destruktory itd), a rhs wskazywał na nasz obiekt, z którego zaraz chcielibyśmy coś skopiować - wielka kaszana.

Jest kilka metod, schematów, które pomagają zaimplementować "wielką trójcę" - destruktor, konstruktor kopiujący i operator przypisania, np. przez swap.

0
rincewind napisał(a):

Zabezpieczenie przed self-assignmentem. Na przykad:

T x;
x = x;

W takim wypadku operator nie powinien nic robić. Jest to sprawdzane waśnie w tym ifie – sprawdzamy, czy obiekt przypisywany (rhs) jest tym samym obiektem, do którego przypisujemy (*this). Robi się to za pomocą porównania wskaźników.

No tak, ok. To Rozumiem już ideę tego if'a. Ale w takim razie jaką (tak precyzyjniej) w ifie pełni rolę "return *this;" skoro po linijkach "*itsAge=rhs.GetAge();
*itsWeight=rhs.GetWeight();" również jest return *this? Bo te dwie linijki są tak jakby główną maszyną operatora . Zatem jeśli if ma sprawiać, by operator= nic nie robił, to czemu również w drugim miejscu jest return *this?

Cat Cat::operator=(Cat & rhs)
{
        if (this==&rhs) return *this;
        *itsAge=rhs.GetAge();
        *itsWeight=rhs.GetWeight();
 
        cout <<"adres rhs: "<<&rhs<<endl;
        return *this;
 }
0

Bo operator przypisania zawsze powinien zwracać *this. Taka zasada.
Zwracany typ powinien też być Cat& a nie Cat.

Jest to potrzebne do przypisań w stylu

a = b = c;
0
Rev napisał(a):

Klasy, które same zarządzają swoją pamięcią powinny mieć: destruktor, operator kopiujący i operator przypisania. Ty masz tylko ten ostatni.
Przede wszystkim powinieneś się zastanowić co twój operator przypisania ma robić - wykonywać kopię głęboką czy płytką? Patrząc na właściwości tej klasy, powinien raczej wykonywać kopię głęboką (kopiować nie tylko wskaźniki, a to, co jest pod nimi). W takim razie, ogólny schemat operatora przypisania wygląda tak: zwolnienie pamięci, alokacja nowej i skopiowanie. W tym przypadku można sobie zwolnienie i alokację darować, bo niezależnie od wartości, ilość zaalokowanej pamięci będzie ta sama, a na końcu zwolni ją destruktor (który musisz dopisać).
Także w tym wypadku zabezpieczenie przed skopiowaniem siebie nie jest wymagane, bo nie zwalniasz pamięci. Wyobraź jednak sobie co by się stało, gdybyś ją zwolnił (uruchomiły by się destruktory itd), a rhs wskazywał na nasz obiekt, z którego zaraz chcielibyśmy coś skopiować - wielka kaszana.

Jest kilka metod, schematów, które pomagają zaimplementować "wielką trójcę" - destruktor, konstruktor kopiujący i operator przypisania, np. przez swap.

W tej chwili, wydaje mi się, że potrzebuję krótkich definicji czym różni się kopiowanie głębokie od płytkiego. Czytałem o tym w c++dla każdego, ale jakoś nie mogę sobie tego w głowie po układać. Choć mam wrażenie jestem już bliski załapania tego wszystkiego.

Zaraz, czy kopiowanie płytkie to kopiowanie samych zmiennych wskaźnikowych - wskaźników? Kopiowanie głębokie zaś to kopiowanie ich razem z ich zawartością? W c++dla każdego nie opisali tego w jednym akapicie i dlatego trochę mi się to kiełbasi.

0

Płytkie kopiowanie to przypisanie zmiennym tych samych adresów, czyli kopia posiada te same wartości jak również wskaźniki. Kopiowanie owszem jest szybsze, ale może być niebezpieczne. Kopiowanie głębokie zaś alokuje nową pamięć i dopiero przypisuje zmienne. Jest wolniejsze, ale bezpieczniejsze, że nowy obiekt jest wyodrębniony.

0
xeo545x39 napisał(a):

Płytkie kopiowanie to przypisanie zmiennym tych samych adresów, czyli kopia posiada te same wartości jak również wskaźniki. Kopiowanie owszem jest szybsze, ale niebezpieczne. Kopiowanie głębokie zaś alokuje nową pamięć i dopiero przypisuje zmienne. Jest wolniejsze, ale bezpieczniejsze, że nowy obiekt jest wyodrębniony.

Chyba już łapię. To uprośćmy:
kopiowanie płytkie - korzystam z tych samych bloków pamięci- jest to trochę ryzykowne,bo muszę pamiętać,by nie chcący tych bloków pamięci nie utracić.

kopiowanie głębokie - korzystam z nowych bloków pamięci. Cholera, czuję że jeszcze raz dla rozjaśnienia muszę te wszystkie rozdziały przeczytać. Jak to mówią: "Do trzech razy sztuka";-))

0

U ciebie jest głębokie, bo przepisujesz dane, a nie wskaźniki:

*itsAge=rhs.GetAge();

Ponieważ pamięć jest przydzielana w konstruktorze bezparametrowym, to w operatorze przypisania masz gwarantowane że jest już przydzielona (stąd tylko kopiowanie danych).

Brakuje u ciebie jednak konstruktora kopiującego, tam byłyby te drugie new.

PS. wydaje mi się jednak, że przy kopiowaniu nie powinieneś wywoływać funkcji, a jedynie odwoływać się do pól:

*itsAge=*rhs.itsAge;
0
Azarien napisał(a):

U ciebie jest głębokie, bo przepisujesz dane, a nie wskaźniki:

*itsAge=rhs.GetAge();

Ponieważ pamięć jest przydzielana w konstruktorze bezparametrowym, to w operatorze przypisania masz gwarantowane że jest już przydzielona (stąd tylko kopiowanie danych).

Brakuje u ciebie jednak konstruktora kopiującego, tam byłyby te drugie new.

PS. wydaje mi się jednak, że przy kopiowaniu nie powinieneś wywoływać funkcji, a jedynie odwoływać się do pól:

*itsAge=*rhs.itsAge;

O widzisz;-) A ja przed chwilą zamiast w konstruktorze kopiującym, przydzieliłem nowe bloki w implementacji operatora przypisania. Po prostu zamiast w konstruktorze kopiującym, przydzieliłem je w definicji tego operatora. Nie wiem. Można tak?

Dla sprawdzenia, wypisałem sobie na ekranie w dwóch miejscach: &itsAge, żeby sprawdzić czy faktycznie są różne adresy. Wyszło, że faktycznie są różne.

1 użytkowników online, w tym zalogowanych: 0, gości: 1