Jawne wykorzystanie wskaźnika this. Samoodwołania. Konstruktor

Transkrypt

Jawne wykorzystanie wskaźnika this. Samoodwołania. Konstruktor
Jawne wykorzystanie wskaźnika this. Samoodwołania.
class X
{
private:
int pole;
public:
X ( ) : pole (0) { }
void zmien (int);
void wypisz ( );
void dodaj (int, int);
};
int main ( )
{
X objx;
objx.zmien (10);
objx.wypisz ( );
objx.dodaj (-2, 11);
objx.wypisz ( );
return 0;
}
void X::zmien (int arg)
{
pole=arg;
Na wykładzie zostaną
omówione slajdy
}
void X::wypisz ( )
{
cout<<pole;
}
od 4 do 15 z pliku
wyklad5.pdf
class X
{
private:
int pole;
public:
X ( ) : pole (0) { }
X& zmien (int);
const X& wypisz ( ) const;
X& dodaj (int, int);
};
X& X::zmien (int arg)
{
pole=arg;
return *this;
}
const X& X::wypisz ( ) const
{
cout<<pole<<endl;
return *this;
}
X& X::dodaj (int arg1, int arg2)
{
pole+=arg1*arg2;
return *this;
}
void X::dodaj (int arg1, int arg2)
{
pole+=arg1*arg2;
}
1
int main ( )
{
X objx;
objx.zmien (10).wypisz ( ).dodaj (-2, 11).wypisz ( );
return 0;
}
2
[C++ Warning] Non-const function X::dodaj(int,int) called for const object
Konstruktor kopiujący
Inicjowanie jednego obiektu klasy wartością innego obiektu nazywamy domyślnym
inicjowaniem składowa po składowej, które polega na kolejnym kopiowaniu
poszczególnych pól niestatycznych klasy do pól klasy inicjowanej w ten sposób.
class X
{
private:
int a, b;
Projektant klasy może zastąpić ten domyślny sposób wykonania operacji
kopiowania, definiując dla klasy specjalny konstruktor nazywany konstruktorem
kopiującym.
public:
X ( ) : a(2), b(10) { }
void wyswietl ( ) const { cout<<a<<" "<<b<<endl; }
};
int main ( )
{
X x1;
cout<<"x1: ";
x1.wyswietl( ); // X x2(x1); lub X x2=X(x1);
X x2=x1;
cout<<"x2: ";
x1: 2 10
x2.wyswietl ( );
x2: 2 10
return 0;
}
Jeżeli klasa zawiera konstruktor kopiujący, to każde inicjowanie jednego obiektu tej
klasy wartością innego jej obiektu spowoduje wywołanie tego konstruktora.
Konstruktorem kopiującym danej klasy nazywamy konstruktor, który można
wywołać z jednym argumentem, którym jest referencja do obiektu danej klasy.
Konstruktor kopiujący może mieć więcej niż jeden argument, ale wtedy pozostałe
argumenty muszą być domyślne.
W praktyce konstruktor kopiujący powinien zrobić wierną kopię oryginału.
3
4
Deklaracja i definicja konstruktora kopiującego.
class X
{
private:
int a, b;
public:
X ( ) : a(2), b(10) { }
void wyswietl ( ) const { cout<<a<<" "<<b<<endl; }
X (X& wzor); //deklaracja konstruktora kopiującego
};
X::X (X& wzor) //definicja konstruktora kopiującego
{
a=wzor.a*2;
b=wzor.b+17;
}
Konstruktor kopiujący jest wywoływany:
1. gdy definiujemy obiekt klasy, który inicjujemy innym obiektem tej samej klasy,
2. podczas przesyłania argumentów do funkcji – jeśli argumentem funkcji jest
int main ( )
{
X x1;
cout<<"x1: ";
x1.wyswietl();
X x2=x1;
cout<<"x2: ";
x2.wyswietl ( );
return 0;
}
3.
obiekt klasy, który jest przesyłany przez wartość,
podczas gdy funkcja jako swój rezultat zwraca przez wartość obiekt klasy.
Ze względu na 2. argument konstruktora kopiującego nie może być przesyłany
przez wartość.
W przypadku klasy X, do tworzenia wiernych kopii wystarczy domyślny sposób
inicjowania składowa po składowej.
x1: 2 10
x2: 4 27
Kiedy jawny konstruktor kopiujący jest niezbędny?
Konstruktor kopiujący definiujemy, w przypadku gdy:
//definicja alternatywna
X::X (X& wzor)
: a(wzor.a*2), b(wzor.b+17)
{}
class Produkt
{
private:
string nazwa;
double *cena;
pole klasy jest wskaźnikiem,
pole musi być unikatowe, np. numer konta, numer karty bibliotecznej, numer
5
Przykład klasy w której konstruktor kopiujący powinien się pojawić
6
void Produkt::zmien (const string& _nazwa, double _cena)
{
nazwa=_nazwa;
*cena=_cena;
}
void Produkt::wypisz ( ) const
{
cout<<"Produkt: "<<nazwa
<<" Cena: "<< *cena<<endl;
}
public:
Produkt (const string& _nazwa, double _cena=0.0);
~Produkt ( );
void zmien (const string& _nazwa, double _cena);
void wypisz ( ) const;
};
Produkt::Produkt (const string& _nazwa, double _cena)
: nazwa( _nazwa), cena (new double(_cena))
{}
Produkt::~Produkt ( )
{
delete cena;
}
legitymacji.
7
int main ( )
{
Produkt p1("kawa", 12);
Produkt p2=p1;
p1.wypisz ( );
p2.wypisz ( );
p2.zmien ("herbata", 5);
p1.wypisz ( );
p2.wypisz ( );
return 0;
}
W przypadku składowych, które są
wskaźnikami domyślne
inicjowanie składowa po
składowej lub niejawny
konstruktor kopiujący, nie działają
tak jakbyśmy chcieli.
Produkt: kawa Cena: 12
Produkt: kawa Cena: 12
Produkt: kawa Cena: 5
Produkt: herbata Cena: 5
8
Po dodaniu do programu wyróżnionych fragmentów program będzie działał poprawnie.
class Produkt
{
private:
string nazwa;
double *cena;
public:
Produkt (const string& _nazwa, double _cena=0.0);
Produkt (Produkt& wzor);
~Produkt ( );
void zmien (const string& _nazwa, double _cena);
void wypisz ( ) const;
};
Konstruktor kopiujący gwarantujący nietykalność.
Jeśli
const Produkt p1 ("kawa", 12);
Produkt p2=p1;
int main ( )
{
Produkt p1("kawa", 12);
Produkt p2=p1;
p1.wypisz ( );
p2.wypisz ( );
p2.zmien ("herbata", 5);
p1.wypisz ( );
p2.wypisz ( );
return 0;
}
[C++ Error] Could not find a match for 'Produkt::Produkt(const Produkt)'
Zdefiniowany dla klasy Produkt konstruktor kopiujący nie gwarantuje, że nie uszkodzi
wzorca, który w tym przypadku jest obiektem stałym.
Nietykalność wzorca zostanie zapewniona, jeśli argumentem konstruktora kopiującego
będzie referencja do obiektu stałego.
Linia skompiluje się jeśli deklaracja konstruktora kopiującego będzie następująca
Produkt (const Produkt&);
natomiast definicja
Produkt::Produkt (Produkt& wzor)
Produkt: kawa Cena: 12
:nazwa(wzor.nazwa), cena (new double(*wzor.cena)) Produkt: kawa Cena: 12
Produkt: kawa Cena: 12
{}
Produkt: herbata Cena: 5
Produkt::Produkt (const Produkt& wzor)
: nazwa(wzor.nazwa), cena (new double(*wzor.cena))
{}
9
Jednak nie zawsze jest tak prosto, jeśli klasa Produkt miałaby pola będące obiektami
innych klas, to również w konstruktorach kopiujących tych klas argumentem
formalnym musiałaby być referencja do obiektu stałego.
10
Operator przypisania
Klasa Produkt zdefiniowana w poprzednim rozdziale nie jest kompletna.
Dlatego też, jeśli piszemy program, to od razu należy stawiać const w takich miejscach
jak konstruktory kopiujące.
Prywatny konstruktor kopiujący.
Zamiast definiować konstruktor kopiujący, można w ogóle zabronić inicjowania
obiektów składowa po składowej.
To rozwiązanie składa się z dwóch części:
(a) należy zadeklarować konstruktor kopiujący jako metodę prywatną klasy co
zapobiegnie inicjowaniu składowa po składowej we wszystkich miejscach
programu, oprócz treści metod i funkcji zaprzyjaźnionych,
(b) należy zrezygnować z definiowania konstruktora kopiującego (zachowując jednak
jego deklarację zgodnie z punktem (a)), aby uniemożliwić inicjowanie składowa po
składowej w treści metod klasy i funkcjach zaprzyjaźnionych z klasą.
Wtedy program można skompilować, ale program łączący nie będzie mógł
rozstrzygnąć wywołania konstruktora kopiującego, bo nie został on zdefiniowany.
11
class Produkt
{
private:
string nazwa;
double *cena;
Produkt: kawa Cena: 30
Produkt: kawa Cena: 30
Produkt: kawa Cena: 4
Produkt: cukier Cena: 4
public:
Produkt (const string& _nazwa, double _cena=0.0);
Produkt (const Produkt& wzor);
~Produkt ( );
void zmien (const string& _nazwa, double _cena);
void wypisz ( ) const;
};
int main ( )
{
Produkt p1("kawa", 30);
Produkt p2("nic");
p2=p1; //☺
p1.wypisz ( );
p2.wypisz ( );
p2.zmien ("cukier", 4);
p1.wypisz ( );
p2.wypisz ( );
return 0;
}
12
Deklaracja i definicja operatora przypisania (tzn. metody operator=).
operator=).
W linii ☺ zadziałał mechanizm przypisania jednemu obiektowi klasy wartości
drugiego obiektu tej samej klasy, który nazywany jest domyślnym przypisaniem
składowa po składowej.
W zasadzie jeśli domyślne inicjowanie składowa po składowej nie nadaje się dla
jakieś klasy, to również nie nadaje się dla niej domyślne przypisanie składowa po
składowej.
Projektant klasy może sam zaprojektować operator przypisania. W tym celu
należy zadeklarować i zdefiniować metodę operator=.
class Produkt
{
private:
string nazwa;
double *cena;
public:
Produkt (const string& _nazwa, double _cena=0.0);
Produkt (const Produkt& wzor);
~Produkt ( );
void zmien (const string& _nazwa, double _cena);
void wypisz ( ) const;
Produkt& operator= (const Produkt& wzor); //deklaracja operatora przypisania
};
13
Produkt& Produkt::operator= (const Produkt& wzor) //
{
if (this!=&wzor) //
{
Definicja operatora przypisania
nazwa=wzor.nazwa;
*cena=*wzor.cena;
}
return *this; //
}
Metoda operator= gwarantuje nietykalność obiektom przypisywanym (const w ),
tzn. poprawne będą następujące linie kodu:
const Produkt p1("cos", 34);
Produkt p2("COS");
p2=p1;
Zapytanie w linii wyklucza przypisanie
obiektu na ten sam obiekt tzn. przypisanie
p2=p2;
nie zostanie wykonane.
Metoda operator= jest typu Produkt&
linia (w linii zwracana jest
wartość tego typu), dzięki czemu w
programie można napisać
p2=p1=p3;
o ile p1, p2, p3 są obiektami klasy
Produkt.
15
14
Po tych uzupełnieniach program będzie działał poprawnie.
int main ( )
{
Produkt p1("kawa", 30);
Produkt p2("nic");
p2=p1; // p2.operator= (p1);
p1.wypisz ( );
p2.wypisz ( );
p2.zmien ("cukier", 4);
p1.wypisz ( );
Produkt: kawa Cena: 30
p2.wypisz ( );
Produkt: kawa Cena: 30
return 0;
Produkt: kawa Cena: 30
}
Produkt: cukier Cena: 4
16