Documente Academic
Documente Profesional
Documente Cultură
Curs 4
Curs 4
Curs-4
Gavrilut Dragos
Mostenirea
Ce este o funcție virtuala ?
Utilitatea funcțiilor virtuale
Cum modelează compilatorul comportamentul funcțiilor
virtuale
Covarianti
Clase abstracte
Alinieri in memorie pentru clasele derivate
Cast-uri intre clasele derivate
Mostenirea
Mostenirea este procesul prin care o serie de proprietati (metode si membri)
se adauga la una sau mai multe clase rezultand o clasa nou (care are toate
proprietatile claselor din care a fost derivata + o serie de proprietati noi).
Un exemplu ar putea fi clasa automobil cu urmatoarele proprietati:
Numar de usi
Numar de roti
Dimensiune
Mostenire multipla
Multipla
class <nume_clasa>: <modificator de access> <clasa de baza 1> ,
<modificator de access> <clasa de baza 2> ,
<modificator de access> <clasa de baza 3> ,
...
<modificator de access> <clasa de baza n> ,
{ ... }
Modificatorul de access este optional (si are una din valorile public / private
sau protected). Daca nu se specifica se considera ca mostenirea e de tipul
private.
Mostenirea
Exemplu. Clasa Derived mosteneste membri si metodele clasei Base. Din acest
motiv poate apela metoda SetX sau accesa membrul x.
App.cpp
class Base
{
public:
int x;
void SetX(int value);
};
class Derived : public Base
{
int y;
public:
void SetY(int value);
};
void main()
{
Derived d;
d.SetX(100);
d.x = 10;
d.SetY(200);
}
Mostenirea
Codul de mai jos nu compileaza. Clasa Derived mosteneste clasa Base, dar
variabila x din Base este declarata ca si private. Asta inseamna ca poate fi
accesata doar din clasa Base si NU si din clase derivate din Base
App.cpp
class Base
{
private:
int x;
};
class Derived : public Base
{
int y;
public:
void SetY(int value);
void SetX(int value);
};
void Derived::SetX(int value)
{
x = value;
}
void main()
{
Derived d;
d.SetX(100);
d.SetY(200);
}
Mostenirea
Solutia in acest caz este utilizarea unui al treilea tip de modificator de access
(protected). Protected specifica ca variabila nu poate fi accesata din afara,
dar poate fi accesata dintr-o clasa derivata.
App.cpp
class Base
{
protected:
int x;
};
class Derived : public Base
{
int y;
public:
void SetY(int value);
void SetX(int value);
};
void Derived::SetX(int value)
{
x = value;
}
void main()
{
Derived d;
d.SetX(100);
d.SetY(200);
}
Mostenirea
Codul de mai jos nu compileaza. “x” este protected in clasa Base – poate fi
accesat de o clasa derivata, dar nu poate fi accesat din afara clasei.
App.cpp
class Base
{
protected:
int x;
};
class Derived : public Base
{
int y;
public:
void SetY(int value);
void SetX(int value);
};
void Derived::SetX(int value)
{
x = value;
}
void main()
{
Derived d;
d.SetX(100);
d.x = 100;
}
Mostenirea
App.cpp
class Base
{
private:
int x;
};
class Derived : public Base
{
int y;
public:
void SetY(int value);
void friend SetX(Derived &d);
};
void SetX(Derived &d)
{
d.x = 100;
}
void main()
{
Derived d;
SetX(d);
}
Mostenirea
Solutia este sa transformam x din private in protected sau public sau sa
punem o functie friend in clasa Base
App.cpp
class Base
{
protected:
int x;
};
class Derived : public Base
{
int y;
public:
void SetY(int value);
void friend SetX(Derived &d);
};
void SetX(Derived &d)
{
d.x = 100;
}
void main()
{
Derived d;
SetX(d);
}
Mostenirea
Atentie unde definiti functia friend. Codul de mai jos NU compileaza pentru
ca functia friend e definita in clasa Derived – chiar daca are un parametru de
tip Base, ea poate accesa doar membri private din clasa Derive.
App.cpp
class Base
{
private:
int x;
};
class Derived : public Base
{
int y;
public:
void SetY(int value);
void friend SetX(Base &d);
};
void SetX(Base &d)
{
d.x = 100;
}
void main()
{
Derived d;
}
Mostenirea
Codul current insa functioneaza corect – pentru ca functia friend SetX este
definite in clasa Base (deci poate accesa membri privati ai acestei clase).
App.cpp
class Base
{
private:
int x;
public:
void friend SetX(Base &d);
};
class Derived : public Base
{
int y;
public:
void SetY(int value);
};
void SetX(Base &d)
{
d.x = 100;
}
void main()
{
Derived d;
}
Mostenirea
Polimorfismul este modalitatea prin care mai multe obiecte care au o interfața
comuna (in cazul C++ derivate sunt din aceeași clasa) pot fi convertite (cast-ate)
la clasa de baza păstrând-si funcționalitatea specifica funcțiilor lor.
App-1.cpp După execuția codului pe ecran se
class Figura { va afișa “Cerc” si “Patrat”.
public: virtual void Draw() { printf("Figura"); }
};
class Cerc: public Figura {
public: void Draw() { printf(“Cerc"); }
};
class Patrat: public Figura {
public: void Draw() { printf(“Patrat"); }
};
void main()
{
Figura *f[2];
f[0] = new Cerc();
f[1] = new Patrat();
for (int index = 0;index<2;index++)
f[index]->Draw();
}
Utilitatea funcțiilor virtuale
Implementeaza
Functie 1
In practica, conceptual de polimorfism si
Functie 2 Plugin 1
funcțiile virtuale sunt folosite in special
….
pentru a declara un plugin sau add-on.
Functie n
Interfata Implementeaza
Functie 1 Functie 1
Functie 2
Functie 2 Plugin 2
Aplicatie ….
Functie 3 Functie n
….
Functie n Implementeaza
Functie 1
Functie 2 Plugin n
….
Functie n
Utilitatea funcțiilor virtuale
App-1.cpp App-2.cpp
class A class A
{ {
public: public:
int a1, a2, a3; int a1, a2, a3;
void Set() { printf("A"); } virtual void Set() { printf("A"); }
}; };
void main() void main()
{ {
printf("%d",sizeof(A)); printf("%d",sizeof(A));
} }
In urma execuției App-1 va afișa “12” iar App-2 va afișa “16” (pe o
arhitectura pe 32 de biți). Pe o arhitectura pe 64 de biți App-2 va afișa “24”
DE CE ?
Cum modelează compilatorul
comportamentul functiilor virtuale
Utilizarea cuvântului “virtual” la măcar una dintre metodele unei clase
adaugă la compilare încă un membru (la începutul clasei) care este un pointer
(vfptr) către o lista cu toate funcțiile virtuale ale acelei clase.
App-1.cpp Memorie
class A
{
public:
int a1, a2, a3;
void Set() { printf("A"); }
};
void main()
{
A a;
}
Cum modelează compilatorul
comportamentul functiilor virtuale
Utilizarea cuvântului “virtual” la măcar una dintre metodele unei clase
adaugă la compilare încă un membru (la începutul clasei) care este un pointer
(vfptr) către o lista cu toate funcțiile virtuale ale acelei clase.
App-1.cpp Memorie
class A
{
…
public:
int a1, a2, a3;
A::Set()
void Set() { printf("A"); }
};
void main()
{
A a;
}
Cum modelează compilatorul
comportamentul functiilor virtuale
Utilizarea cuvântului “virtual” la măcar una dintre metodele unei clase
adaugă la compilare încă un membru (la începutul clasei) care este un pointer
(vfptr) către o lista cu toate funcțiile virtuale ale acelei clase.
App-1.cpp Memorie
class A
{
…
public:
int a1, a2, a3;
A::Set()
void Set() { printf("A"); }
}; …
void main()
{ main()
A a;
}
Cum modelează compilatorul
comportamentul functiilor virtuale
Utilizarea cuvântului “virtual” la măcar una dintre metodele unei clase
adaugă la compilare încă un membru (la începutul clasei) care este un pointer
(vfptr) către o lista cu toate funcțiile virtuale ale acelei clase.
App-1.cpp Memorie
class A
{
…
public:
int a1, a2, a3;
A::Set()
void Set() { printf("A"); }
}; …
void main()
{ main()
A a;
} …
<Stiva>
…
Cum modelează compilatorul
comportamentul functiilor virtuale
Utilizarea cuvântului “virtual” la măcar una dintre metodele unei clase
adaugă la compilare încă un membru (la începutul clasei) care este un pointer
(vfptr) către o lista cu toate funcțiile virtuale ale acelei clase.
App-2.cpp Memorie
class A …
{ Stiva
public:
A::Set()
int a1, a2, a3;
virtual void Set() { printf("A"); }
…
…
};
void main() a.a3
{ main()
A a; a.a2
} …
a.a1
<Stiva>
a.vfptr
…
…
ESP/RSP
Cum modelează compilatorul
comportamentul functiilor virtuale
Utilizarea cuvântului “virtual” la măcar una dintre metodele unei clase
adaugă la compilare încă un membru (la începutul clasei) care este un pointer
(vfptr) către o lista cu toate funcțiile virtuale ale acelei clase.
classA virtual
App-2.cpp Memorie
functions
class A …
{ Stiva ptr to A::Set
public:
A::Set()
int a1, a2, a3;
virtual void Set() { printf("A"); }
… RTTI
…
};
void main() a.a3
{ main()
A a; a.a2
} …
a.a1
<Stiva>
a.vfptr
…
…
ESP/RSP
Cum modelează compilatorul
comportamentul functiilor virtuale
Utilizarea cuvântului “virtual” la măcar una dintre metodele unei clase
adaugă la compilare inca un membru (la începutul clasei) care este un pointer
(vfptr) către o lista cu toate funcțiile virtuale ale acelei clase.
classA virtual
App-2.cpp Memorie
functions
class A …
{ Stiva ptr to A::Set
public:
A::Set()
int a1, a2, a3;
virtual void Set() { printf("A"); }
… RTTI
…
};
void main() a.a3
{ main()
A a; a.a2
} …
a.a1
<Stiva>
a.vfptr
…
…
ESP/RSP
Cum modelează compilatorul
comportamentul functiilor virtuale
De asemenea compilatorul fie creează un constructor implicit (daca nu este
ceva definit) fie modifica constructorul/constructorii existenți ca sa seteze
pointerul vfptr la lista de funcții virtuale.
App.cpp Disasm
class A A a;
{
a.x = 1;
public:
int x, y; mov dword ptr [ebp-12],1
int Calcul() { return x+y; } a.y = 2;
}; mov dword ptr [ebp-8],2
void main()
{
A a;
a.x = 1;
a.y = 2;
}
App.cpp Disasm
class A A a;
{
lea ecx,[ebp-16]
public:
int x, y; call A::A
int Calcul() { return x+y; } a.x = 1;
A() { x = y = 0; } mov dword ptr [ebp-16],1
};
void main()
a.y = 2;
{ mov dword ptr [ebp-12],2
A a;
a.x = 1;
a.y = 2;
}
In cazul de fata, este apelat constructorul default creat in clasa A. Codul din
constructorul default nu este modificat.
Cum modelează compilatorul
comportamentul functiilor virtuale
De asemenea compilatorul fie creează un constructor implicit (daca nu este
ceva definit) fie modifica constructorul/constructorii existenți ca sa seteze
pointerul vfptr la lista de funcții virtuale.
App.cpp Disasm
class A A a;
{
lea ecx,[ebp-20]
public:
int x, y; call A::A
virtual int Calcul() {return x+y;} a.x = 1;
}; mov dword ptr [ebp-16],1
void main()
{
a.y = 2;
A a; mov dword ptr [ebp-12],2
a.x = 1;
a.y = 2;
}
App.cpp Disasm
class A A a;
{
lea ecx,[ebp-20]
public:
int x, y; call A::A
virtual int Calcul() {return x+y;} a.x = 1;
}; mov dword ptr [ebp-16],1
void main() Disasm
{
a.y = 2;
A a; push ebp mov dword ptr [ebp-12],2
a.x = 1;mov ebp,esp
a.y = 2;mov dword ptr[ebp-8],ecx
}
mov eax,dword ptr [ebp-8]
mov dword ptr [eax], A-virtual-fnc-list
mov eax,dword ptr [ebp-8]
mov esp,ebp
pop ebp
ret
Cum modelează compilatorul
comportamentul functiilor virtuale
De asemenea compilatorul fie creează un constructor implicit (daca nu este
ceva definit) fie modifica constructorul/constructorii existenți ca sa seteze
pointerul vfptr la lista de funcții virtuale.
App.cpp Disasm
class A A a;
{
lea ecx,[ebp-20]
public:
int x, y; call A::A
virtual int Calcul() {return x+y;} a.x = 1;
A() { x = y = 0; } mov dword ptr [ebp-16],1
};
void main()
a.y = 2;
{ mov dword ptr [ebp-12],2
A a;
a.x = 1;
a.y = 2;
}
Chiar daca funcția Calcul este virtuala, nu are sens pentru compilator sa facă
apelul folosind vfptr in acest caz, pentru ca nefiind un pointer a.Calcul nu
poate puncta decât la o singura funcție.
Cum modelează compilatorul
comportamentul functiilor virtuale
Apelul funcției virtuale prin referința din vfptr este făcut doar daca instanța
obiectului care face apelul e un pointer sau o referință.
App.cpp Disasm
class A A a;
{
public:
lea ecx,[a]
int x, y; call A::A
virtual int Calcul() {return x+y;} a.x = 1;
A() { x = y = 0; } mov dword ptr [ebp-10h],1
};
void main() a.y = 2;
{ mov dword ptr [ebp-0Ch],2
A a; A* a2 = &a;
a.x = 1; lea eax,[a]
a.y = 2;
A* a2 = &a; mov dword ptr [a2],eax
a2->Calcul(); a2->Calcul();
} mov eax,dword ptr [a2]
mov edx,dword ptr [eax]
mov ecx,dword ptr [a2]
In cazul de fata apelul se face mov eax,dword ptr [edx]
folosind vfptr call eax
Cum modelează compilatorul
comportamentul functiilor virtuale
App.cpp Pseudo C/C++ Code
class A struct A_VirtualFunctions {
{
public: };
int x;
virtual int Calcul() {return 0;}
A() { x = 0; }
};
void main()
{
A a;
a.x = 1;
a.y = 2;
A* a2 = &a;
a2->Calcul();
}
Cum modelează compilatorul
comportamentul functiilor virtuale
App.cpp Pseudo C/C++ Code
class A struct A_VirtualFunctions {
{ int (*Calcul) ();
public: };
int x;
virtual int Calcul() {return 0;}
A() { x = 0; }
};
void main()
{
A a;
a.x = 1;
a.y = 2;
A* a2 = &a;
a2->Calcul();
}
Cum modelează compilatorul
comportamentul functiilor virtuale
App.cpp Pseudo C/C++ Code
class A struct A_VirtualFunctions {
{ int (*Calcul) ();
public: };
int x; class A {
virtual int Calcul() {return 0;} public:
A() { x = 0; }
}; int x;
void main()
{
A a;
a.x = 1;
a.y = 2;
A* a2 = &a;
a2->Calcul();
}
Cum modelează compilatorul
comportamentul functiilor virtuale
App.cpp Pseudo C/C++ Code
class A struct A_VirtualFunctions {
{ int (*Calcul) ();
public: };
int x; class A {
virtual int Calcul() {return 0;} public:
A() { x = 0; } A_VirtualFunctions *vfPtr;
}; int x;
void main()
{
A a;
a.x = 1;
a.y = 2;
A* a2 = &a;
a2->Calcul();
}
Cum modelează compilatorul
comportamentul functiilor virtuale
App.cpp Pseudo C/C++ Code
class A struct A_VirtualFunctions {
{ int (*Calcul) ();
public: };
int x; class A {
virtual int Calcul() {return 0;} public:
A() { x = 0; } A_VirtualFunctions *vfPtr;
}; int x;
void main() int A_Calcul() { return 0; }
{
A a;
a.x = 1;
a.y = 2;
A* a2 = &a;
a2->Calcul();
}
Cum modelează compilatorul
comportamentul functiilor virtuale
App.cpp Pseudo C/C++ Code
class A struct A_VirtualFunctions {
{ int (*Calcul) ();
public: };
int x; class A {
virtual int Calcul() {return 0;} public:
A() { x = 0; } A_VirtualFunctions *vfPtr;
}; int x;
void main() int A_Calcul() { return 0; }
{
A a;
a.x = 1;
a.y = 2;
A* a2 = &a;
a2->Calcul(); A_VirtualFunctions Global_A_vfPtr;
} Global_A_vfPtr.Calcul = &A::A_Calcul;
Cum modelează compilatorul
comportamentul functiilor virtuale
App.cpp Pseudo C/C++ Code
class A struct A_VirtualFunctions {
{ int (*Calcul) ();
public: };
int x; class A {
virtual int Calcul() {return 0;} public:
A() { x = 0; } A_VirtualFunctions *vfPtr;
}; int x;
void main() int A_Calcul() { return 0; }
{ A() {
A a;
a.x = 1; x = 0;
a.y = 2; }
A* a2 = &a; };
a2->Calcul(); A_VirtualFunctions Global_A_vfPtr;
} Global_A_vfPtr.Calcul = &A::A_Calcul;
Cum modelează compilatorul
comportamentul functiilor virtuale
App.cpp Pseudo C/C++ Code
class A struct A_VirtualFunctions {
{ int (*Calcul) ();
public: };
int x; class A {
virtual int Calcul() {return 0;} public:
A() { x = 0; } A_VirtualFunctions *vfPtr;
}; int x;
void main() int A_Calcul() { return 0; }
{ A() {
A a; vfPtr = &Global_A_vfPtr;
a.x = 1; x = 0;
a.y = 2; }
A* a2 = &a; };
a2->Calcul(); A_VirtualFunctions Global_A_vfPtr;
} Global_A_vfPtr.Calcul = &A::A_Calcul;
Cum modelează compilatorul
comportamentul functiilor virtuale
App.cpp Pseudo C/C++ Code
class A struct A_VirtualFunctions {
{ int (*Calcul) ();
public: };
int x; class A {
virtual int Calcul() {return 0;} public:
A() { x = 0; } A_VirtualFunctions *vfPtr;
}; int x;
void main() int A_Calcul() { return 0; }
{ A() {
A a; vfPtr = &Global_A_vfPtr;
a.x = 1; x = 0;
a.y = 2; }
A* a2 = &a; };
a2->Calcul(); A_VirtualFunctions Global_A_vfPtr;
} Global_A_vfPtr.Calcul = &A::A_Calcul;
void main()
{
A a;
a.x = 1;
a.y = 2;
A* a2 = &a;
}
Cum modelează compilatorul
comportamentul functiilor virtuale
App.cpp Pseudo C/C++ Code
class A struct A_VirtualFunctions {
{ int (*Calcul) ();
public: };
int x; class A {
virtual int Calcul() {return 0;} public:
A() { x = 0; } A_VirtualFunctions *vfPtr;
}; int x;
void main() int A_Calcul() { return 0; }
{ A() {
A a; vfPtr = &Global_A_vfPtr;
a.x = 1; x = 0;
a.y = 2; }
A* a2 = &a; };
a2->Calcul(); A_VirtualFunctions Global_A_vfPtr;
} Global_A_vfPtr.Calcul = &A::A_Calcul;
void main()
{
A a;
a.x = 1;
a.y = 2;
A* a2 = &a;
a2->vfPtr->Calcul();
}
Cum modelează compilatorul
comportamentul functiilor virtuale
vfptr-ul e doar un pointer. Prin urmare, poate fi schimbat in timpul execuției.
App.cpp Codul alăturat afișează “AA”.
class A
{
Prima data prin apelul funcției
public: Print direct a doua oara prin
int x;
virtual void Print() { printf("A"); } apelul funcției Print din vfptr.
};
class B
{
public:
int x;
virtual void Print() { printf(“B"); }
};
void main()
{
A a;
B b;
A* a2 = &a;
a.Print();
a2->Print();
}
Cum modelează compilatorul
comportamentul functiilor virtuale
vfptr-ul e doar un pointer. Prin urmare, poate fi schimbat in timpul execuției.
App.cpp Codul alăturat afișează “AB”. Prin
class A
{
utilizarea lui memcpy am
public: suprascris vfptr-ul pentru
int x;
virtual void Print() { printf("A"); } obiectului “a” cu cel a lui “b”.
};
class B
Cum funcția Print are aceeași
{ semănătura in ambele clase, codul
public:
int x; va funcționa corect in timpul
virtual void Print() { printf(“B"); } execuției.
};
void main()
{
A a;
B b;
memcpy(&a, &b, sizeof(void*));
A* a2 = &a;
a.Print();
a2->Print();
}
Cum modelează compilatorul
comportamentul functiilor virtuale
vfptr-ul e doar un pointer. Prin urmare, poate fi schimbat in timpul execuției.
App.cpp Constructorul de copiere nu copie
class A
{
si vfptr-ul ci doar îl setează la
public: valoarea implicit pentru clasa in
int x;
virtual void Print() { printf("A"); } cauza. Chiar daca “a2” are un alt
};
class B
vfptr, a3 va avea vfptr-ul specific
{ clasei A
public:
int x;
virtual void Print() { printf(“B"); }
Codul alăturat va afișa “A”
};
void main()
{
A a;
B b;
memcpy(&a, &b, sizeof(void*));
A* a2 = &a;
A a3 = (*a2);
A *a4 = &a3;
a4->Print();
}
Cum modelează compilatorul
comportamentul functiilor virtuale
Daca o funcție virtuala nu e suprascrisa se păstrează si la fel in clasa derivata.
App.cpp
După execuția codului, valoarea
class A
{ lui “x” va fi 1.
public:
int x, y; Chiar daca se apelează funcția
virtual int Suma() { return x + y; }
virtual int Diferenta() { return x - y; } Suma, pe un pointer de tipul A*,
};
virtual int Produs() { return x*y; } obiectul in cauza este in realitate
class B : public A un B, si cum funcția Suma este
{
public:
virtuala, se apelează funcția Suma
int Suma() { return 1; } din B si nu din A.
};
void main()
{
B b;
b.x = 1;
b.y = 2;
A* a;
a = &b;
int x = a->Suma();
}
Cum modelează compilatorul
comportamentul funcțiilor virtuale
void main()
{
}
Cum modelează compilatorul
comportamentul funcțiilor virtuale
sizeof(B) = 20
Alinieri in memorie pentru clasele
derivate
class A
{
public:
int a1,a2,a3; Offset Variabila C1 C2
};
+ 0 A::a1
sizeof(A) = 12 + 4 A::a2 A
class B: public A
{
+ 8 A::a3
+ 12 B::b1
B
public: + 16 B::b2
int b1,b2
};
sizeof(B) = 20
Alinieri in memorie pentru clasele
derivate
class A
{
public:
int a1,a2,a3; Offset Variabila C1 C2
};
+ 0 A::a1
sizeof(A) = 12 + 4 A::a2 A
class B: public A
{
+ 8 A::a3
+ 12 B::b1
B
public: + 16 B::b2
int b1,b2
};
sizeof(B) = 20
Alinieri in memorie pentru clasele
derivate
class A class B:
{ {
public: public:
int a1,a2,a3; int b1,b2; Offset Variabila C1 C2 C3
}; };
+ 0 A::a1
sizeof(A) = 12 sizeof(B) = 8 + 4 A::a2 A
+ 8 A::a3
B
class C:public A,B
{
+ 12 B::b1
+ 16 B::b2
C
public: +20 C::c1
int c1,c2;
}; +24 C::c2
sizeof(C) = 28
Alinieri in memorie pentru clasele
derivate class C:public B,A
{
class A class B: public:
{ { int c1,c2;
public: public: };
int a1,a2,a3; int b1,b2;
}; }; sizeof(C) = 28
sizeof(A) = 12 sizeof(B) = 8 Offset Variabila C1 C2 C3
+ 0 B::b1
Alinierea in memorie in cazul claselor
derivate se face in ordinea in care se + 4 B::b2 B
face derivarea. + 8 A::a1
A
+ 12 A::a2
+ 16 A::a3
C
+20 C::c1
+24 C::c2
Alinieri in memorie pentru clasele
derivate warning C4584: 'C' : base-class 'A' is already a base-
class of 'B’.
Moștenirea multipla poate produce situații ambigui. De exemplu in acest caz,
clasa A are doua instanțe in cadrul
clase C. Offset Variabila C1 C2 C3
App.cpp +0 A::a1
class A
{
+4 A::a2 A
public: +8 A::a3
int a1, a2, a3;
};
class B: public A
+12 B::A::a1
BC
{
public: +16 B::A::a2 B::A
int b1, b2;
}; +20 B::A::a3
class C : public A, public B
{ +24 B::b1
public:
};
int c1, c2; +28 B::b2
void main()
{ +32 C::c1
}
+36 C::c2
Alinieri in memorie pentru clasele
derivate
Moștenirea multipla poate produce situații ambigui. De exemplu in acest caz,
clasa A are doua instanțe in cadrul clase C.
App.cpp
class A {
public:
int a1, a2, a3;
}; Compilatorul nu știe cum sa interpreteze apelul
class B: public A {
public: c.a1 (nu știe daca trebuie sa se refere la membrul
};
int b1, b2;
a1 din cadrul derivării din clasa A sau la membrul
class C : public A, public B { a1 obținut din derivarea din clasa B (care e la
public:
int c1, c2; rândul lui derivate din clasa A).
};
void main()
{ Codul nu compilează !!!
C c;
c.a1 = 10;
}
Alinieri in memorie pentru clasele
derivate
Moștenirea multipla poate produce situații ambigui. De exemplu in acest caz,
clasa A are doua instanțe in cadrul clase C.
App.cpp Soluția este să descriem un membru prin
class A {
public:
calea lui întreaga (lista de clase din care
int a1, a2, a3; este derivate). In exemplul alăturat,
};
class B: public A { c.A::a1 se refera la membrul a1 datorat
public: derivării directe din A, iar c.B::A::a1 se
int b1, b2;
}; refera la membrul a1 datorat derivării
class C : public A, public B {
public:
directe a lui C din B, care la rândul lui
int c1, c2; este derivate din A.
};
void main()
{
Dar daca dorim sa ca un obiect de tipul A
C c; sa fie instantiate o singura data ?
c.A::a1 = 10;
}
c.B::A::a1 = 20;
Aceasta problema mai poarta numele si de
“problema carourilor”
Alinieri in memorie pentru clasele
derivate
Moștenirea multipla poate produce situații ambigui. De exemplu in acest caz,
clasa A are doua instanțe in cadrul clase C.
App.cpp O altă soluție este utilizarea cuvântului
class A {
public:
cheie “virtual” atunci când se face
int a1, a2, a3; derivarea dintr-o clasa. In cazul alăturat
};
class B: public virtual A { trebuie daca orice derivare din A se face
public: folosind virtual, A va avea o singura
int b1, b2;
}; instanța intr-un obiect C.
class C : public virtual A, public B {
public: Pentru ca acest cod sa funcționeze,
int c1, c2;
}; trebuie ca atât C cat si B sa deriveze
void main()
{
folosind virtual din A.
C c;
c.a1 = 10;
c.a2 = 20;
}
Alinieri in memorie pentru clasele
derivate
Exact ca si in cazul funcțiilor virtuale si in acest caz se creează un constructor
virtual sau se modifica cele existente.
App.cpp Disasm
class A { C c;
public:
push 1
int a1, a2, a3;
}; lea ecx,[c]
class B: public virtual A { call C::C
public: c.a1 = 10;
int b1, b2;
};
mov eax,dword ptr [c]
class C : public virtual A, public B { mov ecx,dword ptr [eax+4]
public: mov dword ptr [c+ecx],10
int c1, c2; c.b1 = 20;
};
void main() mov dword ptr [c+20],20
{
C c;
c.a1 = 10;
c.b1 = 20;
}
Alinieri in memorie pentru clasele
derivate
Codul generat in constructor primește un parametru (True(1) sau False(0)).
Acest parametru ii spune constructorului daca trebuie sa seteze sau nu tabela
cu indexi a clasei.
+ 4 B::b1
B
C
+ 8 B::b2 Offset Offset relative la C
+ 12 C::c1 +0 0
+ 16 C::c2 +4 Virtual A 20
+ 20 A::a1
+ 24 A::a2
+ 28 A::a3
A
Alinieri in memorie pentru clasele
derivate
Asignarea unei valori care aparține unei clase din care s-a declarant folosind
cuvântul cheie virtual se face in 3 pași.
App.cpp
Disasm
class A { … }
class B: public virtual A { … } c.a1 = 10;
class C : public virtual A, public B { … } mov eax,dword ptr [c]
void main()
{
mov ecx,dword ptr [eax+4]
C c; mov dword ptr [c+ecx],10
c.a1 = 10;
}
Alinieri in memorie pentru clasele
derivate
In primul pas, EAX primește primul dword care se găsește la începutul lui c
(adică pointerul către tabela cu indexi pentru variabilele virtuale din C).
App.cpp
Disasm
class A { … }
class B: public virtual A { … } c.a1 = 10;
class C : public virtual A, public B { … } mov eax,dword ptr [c]
Offset Variabila
void main()
{ C1 C2 mov ecx,dword ptr [eax+4]
mov dword ptr [c+ecx],10
+ 0Cc.a1
c;
Ptr Class C Variable Offssets Table
= 10;
} + 4 B::b1
B
C
+8 B::b2
+ 12 C::c1
+ 16 C::c2
+ 20 A::a1
+ 24
+ 28
A::a2
A::a3
A
Alinieri in memorie pentru clasele
derivate
In al doilea pas, ECX primește valoarea de la a doua poziție (+4) din tabela cu
indexi la care punctează EAX (adică valoarea 20 offset-ul lui A::a1 din C)
App.cpp
Disasm
class A { … }
class B: public virtual A { … } c.a1 = 10;
class C : public virtual A, public B { … } mov eax,dword ptr [c]
Offset Variabila
void main()
{ C1 C2 mov ecx,dword ptr [eax+4]
mov dword ptr [c+ecx],10
+ 0Cc.a1
c;
Ptr Class C Variable Offssets Table
= 10;
} + 4 B::b1
B
C
+8 B::b2
+ 12 C::c1
+ 16 C::c2
Offset Offset relativ la C
+ 20 A::a1
+ 24
+ 28
A::a2
A::a3
A +0
+4 Virtual A 20
0
Alinieri in memorie pentru clasele
derivate
In ultimul pas, la offset-ul date de ECX (adică 20) fata de adresa lui “c” se
stochează valoarea 10
App.cpp
Disasm
class A { … }
class B: public virtual A { … } c.a1 = 10;
class C : public virtual A, public B { … } mov eax,dword ptr [c]
Offset Variabila
void main()
{ C1 C2 mov ecx,dword ptr [eax+4]
mov dword ptr [c+ecx],10
+ 0Cc.a1
c;
Ptr Class C Variable Offssts Table
= 10;
} + 4 B::b1
B
C
+8 B::b2
+ 12 C::c1
+ 16 C::c2
Offset Offset relative la C
+ 20 A::a1
+ 24
+ 28
A::a2
A::a3
A +0
+4 Virtual A
0
20
Alinieri in memorie pentru clasele
derivate
Membri din derivările virtuale sunt puși ultimii in structura. In cazul de mai
jos, cum doar A este derivate virtual, el va fi adăugat ultimul intr-un obiect C
App.cpp Offset Camp
class A
{ … }
+ 0 ptr class C virtual members offsets
class B: public virtual A
{ … }
+ 4 C::B::b1
class C : public virtual A,
public B + 8 C::B::b2
{ … }
+ 12 C::c1
+ 16 C::c2
+ 20 A::a1 (virtual A from C)
Offset Offset relative la C
+ 24 A::a2 (virtual A from C)
+0 0
+ 28 A::a3 (virtual A from C)
+4 Virtual A 20
Alinieri in memorie pentru clasele
derivate
Daca in C derivam si pe B tot cu virtual, B va fi adăugat după A (A este primul
derivat virtual)
App.cpp Offset Camp
class A
{ … }
+ 0 ptr class C virtual members offsets
class B: public virtual A
{ … }
+ 4 C::c1
class C : public virtual A,
public virtual B + 8 C::c2
{ … }
+ 12 A::a1 (virtual A from C)
+ 16 A::a2 (virtual A from C)
+ 20 A::a3 (virtual A from C)
Offset Offset relative la C
+ 24 ptr class B virtual members offsets
+0 0
+ 28 B::b1 (virtual B from C)
+4 Virtual A 12
+ 32 B::b2 (virtual B from C)
+8 Virtual B 24
Alinieri in memorie pentru clasele
derivate
In cazul tabelei de indexi din B, offset-ul “-12” se refera la poziția lui A
(membru virtual in B) relative la poziția lui B (deci 24-12 = 12 – poziție in C)
App.cpp Offset Camp
class A
{ … }
+ 0 ptr class C virtual members offsets
class B: public virtual A
{ … }
+ 4 C::c1
class C : public virtual A,
public virtual B + 8 C::c2
{ … }
+ 12 A::a1 (virtual A from C)
+ 16 A::a2 (virtual A from C)
+ 20 A::a3 (virtual A from C)
Offset Offset relative la B
+ 24 ptr class B virtual members offsets
+0 0
+ 28 B::b1 (virtual B from C)
+4 Virtual A -12
+ 32 B::b2 (virtual B from C)
Alinieri in memorie pentru clasele
derivate
Daca doar din B se face derivare virtuala, atunci câmpurile se organizează in
felul următor:
Offset Camp
App.cpp
class A
+ 0 A::a1
{ … }
class B: public A
+ 4 A::a1
{ … }
class C : public A,
+ 8 A::a3
{ … }
public virtual B
+ 12 ptr class C virtual members offsets
+ 16 C::c1
+ 20 C::c2
+ 24 B::A::a1
Offset Offset relative la C
+ 28 B::A::a2
+0 -12
+ 32 B::A::a3
+4 Virtual B 12
+ 36 B::b1
+ 40 B::b2
Alinieri in memorie pentru clasele
derivate
Daca doar din B se face derivare virtuala, atunci câmpurile se organizează in
felul următor:
Offset Camp
App.cpp
class A
+ 0 A::a1
{ … }
class B: public A
+ 4 A::a1
{ … }
class C : public A,
+ 8 A::a3
{ … }
public virtual B
+ 12 ptr class C virtual members offsets
+ 16 C::c1
+Primul index (+0) reprezintă la ce offset se afla
20 C::c2
începutul unui obiect de tipul C fata de offsetul in
+ 24 B::A::a1
Offset Offset relative la C care se găsește pointerul către tabela cu offseturi
+ 28 B::A::a2
+0 -12 virtuale.
+ 32 B::A::a3
+4 Virtual B 12
+ 36 B::b1
+ 40 B::b2
Alinieri in memorie pentru clasele
derivate
Daca doar din B se face derivare virtuala, atunci câmpurile se organizează in
felul următor:
Offset Camp
App.cpp
class A
+ 0 A::a1
{ … }
class B: public A
+ 4 A::a1
{ … }
class C : public A,
+ 8 A::a3
{ … }
public virtual B
+ 12 ptr class C virtual members offsets
+ 16 C::c1
+ 20 C::c2
class B:public A
{ ;B b;
lea ecx,[b]
public:
call B::B
int b1,b2;
;A* a = &b;
};
lea eax,[b]
mov dword ptr [a],eax
Cast-uri intre clasele derivate
class A void main(void) Disasm
{ { a = &c;
public: C c; lea
mov
eax,[c]
dword ptr [a],eax
int a1,a2,a3; A* a; b = &c;
}; B* b; lea
test
eax,[c]
eax,eax
je NULL_CAST
class B a = &c; lea ecx,[c]
add ecx,0Ch
{ b = &c; mov dword ptr [ebp-104h],ecx
public: } jmp GOOD_CAST
NULL_CAST:
int b1,b2; mov dword ptr [ebp-104h],0
}; GOOD_CAST:
mov edx,dword ptr [ebp-104h]
mov dword ptr [b],edx
class C:public A,B _asm nop;
{ nop
public:
int c1,c2;
};
Cast-uri intre clasele derivate
class A void main(void) Disasm
{ { c2 = (C*)b;
public: C c; cmp
je
dword ptr [b],0
NULL_CAST
int a1,a2,a3; A* a; mov eax,dword ptr [b]
}; B* b; sub
mov
eax,0Ch
dword ptr [ebp-110h],eax
C* c2; jmp GOOD_CAST
class B a = &c; NULL_CAST:
mov dword ptr [ebp-110h],0
{ b = &c; GOOD_CAST:
public: c2 = (C*)b; mov ecx,dword ptr [ebp-110h]
mov dword ptr [c2],ecx
int b1,b2; }
};