14-bo‘lim
Vorislik va polimorfizm
Virtual funksiyalar, abstrakt interfeyslar, kesilish muammosi va vorislik qachon kerak emasligi.
Ushbu bo‘lim mundarijasi
Vorislik - C++ ning eng ko'p suiiste'mol qilinadigan imkoniyati. Bu bo'limda uni qachon ishlatish va - eng muhimi - qachon ishlatmaslik kerakligini ko'ramiz.
Virtual funksiya #
#include <print>
#include <string>
#include <vector>
#include <memory>
#include <functional>
#include <algorithm>
class Xodim
{
std::string ism_;
long long oylik_;
public:
Xodim(std::string ism, long long oylik)
: ism_(std::move(ism)), oylik_(oylik) {}
virtual ~Xodim() = default; // MAJBURIY
const std::string& ism() const { return ism_; }
long long oylik() const { return oylik_; }
virtual long long oylikTolov() const { return oylik_; }
virtual std::string lavozim() const { return "xodim"; }
};
class Dasturchi : public Xodim
{
int loyihalar_;
public:
Dasturchi(std::string ism, long long oylik, int loyihalar)
: Xodim(std::move(ism), oylik), loyihalar_(loyihalar) {}
long long oylikTolov() const override
{
return oylik() + loyihalar_ * 500'000;
}
std::string lavozim() const override { return "dasturchi"; }
};
class Menejer : public Xodim
{
int qol_ostidagilar_;
public:
Menejer(std::string ism, long long oylik, int qol)
: Xodim(std::move(ism), oylik), qol_ostidagilar_(qol) {}
long long oylikTolov() const override
{
return oylik() + qol_ostidagilar_ * 200'000;
}
std::string lavozim() const override { return "menejer"; }
};
int main()
{
std::vector<std::unique_ptr<Xodim>> jamoa;
jamoa.push_back(std::make_unique<Dasturchi>("Husanboy", 8'000'000, 3));
jamoa.push_back(std::make_unique<Menejer>("Nodira", 9'000'000, 6));
jamoa.push_back(std::make_unique<Xodim>("Kamola", 5'000'000));
long long jami = 0;
for (const auto& x : jamoa) {
std::println("{:<10} {:<11} {:>10}", x->ism(), x->lavozim(), x->oylikTolov());
jami += x->oylikTolov();
}
std::println("{}", std::string(33, '-'));
std::println("{:<22} {:>10}", "JAMI", jami);
return 0;
}
Husanboy dasturchi 9500000
Nodira menejer 10200000
Kamola xodim 5000000
---------------------------------
JAMI 24700000
Xodim* x = new Dasturchi{"...", 1, 1};
delete x; // virtual ~Xodim() bo'lmasa - UB
Virtual destruktorsiz faqat ~Xodim() chaqiriladi.
Dasturchi ning a'zolari yo'q qilinmaydi - va agar
ular resurs boshqarsa, u sizib chiqadi.
Qoida oddiy:
| Sinf | Destruktor |
|---|---|
| Vorislik uchun mo'ljallangan | virtual ~T() = default; |
| Vorislik uchun mo'ljallanmagan | final qiling |
| Faqat interfeys, o'chirilmaydi | protected: ~T() = default; |
Uchinchi variant kamroq ma'lum, lekin foydali: u bazaviy ko'rsatkich orqali o'chirishni kompilyatsiya paytida taqiqlaydi va virtual jadval xarajatini keltirmaydi.
override - ixtiyoriy emas #
class Asos
{
public:
virtual ~Asos() = default;
virtual void ishla(int n) const { std::println(" Asos::ishla({})", n); }
virtual void tekshir() const { std::println(" Asos::tekshir()"); }
};
class Voris : public Asos
{
public:
// override YOZILGAN - kompilyator imzoni tekshiradi
void ishla(int n) const override { std::println(" Voris::ishla({})", n); }
// override YOZILMAGAN - va imzo mos emas (const yo'q)
// Bu YANGI funksiya, qayta aniqlash EMAS
void tekshir() { std::println(" Voris::tekshir() - const YO'Q"); }
};
int main()
{
Voris v;
Asos& a = v;
std::println("Asos havolasi orqali:");
a.ishla(5);
a.tekshir(); // Asos ning versiyasi - kutilmagan!
std::println("");
std::println("To'g'ridan-to'g'ri:");
v.ishla(5);
v.tekshir();
std::println("");
std::println("Ikkinchi holatda ikki xil funksiya chaqirildi.");
std::println("override yozilganida kompilyator xato berardi.");
return 0;
}
Asos havolasi orqali:
Voris::ishla(5)
Asos::tekshir()
To'g'ridan-to'g'ri:
Voris::ishla(5)
Voris::tekshir() - const YO'Q
Ikkinchi holatda ikki xil funksiya chaqirildi.
override yozilganida kompilyator xato berardi.
override yozingoverride funksiyani virtual qilmaydi - u
kompilyatordan tekshirishni so'raydi: "bu
haqiqatan ham bazaviy sinfdagi virtual funksiyanimi?"
Mos kelmaydigan holatlar:
| Farq | Natija override siz |
|---|---|
const qo'shildi/tushdi | Yangi funksiya |
| Parametr turi boshqa | Ortiqcha yuk |
| Qaytish turi boshqa | Kompilyatsiya xatosi |
| Nom xato yozilgan | Yangi funksiya |
override bilan bularning hammasi kompilyatsiya
xatosi bo'ladi.
final ham bor: void ishla(int) const override final;
bu funksiyani boshqa qayta aniqlab bo'lmasligini
bildiradi va kompilyatorga virtual chaqiruvni
to'g'ridan-to'g'ri chaqiruvga aylantirish imkonini
beradi.
Kesilish (slicing) #
struct Shakl
{
virtual ~Shakl() = default;
virtual std::string nomi() const { return "shakl"; }
virtual double yuza() const { return 0.0; }
};
struct Doira : Shakl
{
double r{};
explicit Doira(double radius) : r(radius) {}
std::string nomi() const override { return "doira"; }
double yuza() const override { return 3.14159265 * r * r; }
};
void qiymatBoyicha(Shakl s) // KESILADI
{
std::println(" qiymat bo'yicha: {} yuza {:.3f}", s.nomi(), s.yuza());
}
void havolaBoyicha(const Shakl& s) // to'g'ri
{
std::println(" havola bo'yicha: {} yuza {:.3f}", s.nomi(), s.yuza());
}
int main()
{
Doira d{2.0};
std::println("Doira{{2.0}} - haqiqiy yuzasi {:.3f}", d.yuza());
std::println("");
havolaBoyicha(d);
qiymatBoyicha(d);
std::println("");
std::println("Konteynerda ham xuddi shunday:");
std::vector<Shakl> yomon;
yomon.push_back(Doira{3.0});
std::println(" vector<Shakl> ichida: {} yuza {:.3f}",
yomon[0].nomi(), yomon[0].yuza());
std::vector<std::unique_ptr<Shakl>> yaxshi;
yaxshi.push_back(std::make_unique<Doira>(3.0));
std::println(" vector<unique_ptr> ichida: {} yuza {:.3f}",
yaxshi[0]->nomi(), yaxshi[0]->yuza());
return 0;
}
Doira{2.0} - haqiqiy yuzasi 12.566
havola bo'yicha: doira yuza 12.566
qiymat bo'yicha: shakl yuza 0.000
Konteynerda ham xuddi shunday:
vector<Shakl> ichida: shakl yuza 0.000
vector<unique_ptr> ichida: doira yuza 28.274
Shakl s = d; da faqat Shakl qismi nusxalanadi.
Doira ning r maydoni tashlab yuboriladi va
virtual jadval ko'rsatkichi Shakl niki bo'lib qoladi.
Kompilyator hech qanday ogohlantirish bermaydi.
Kesilish sodir bo'ladigan joylar:
| Kod | Kesiladimi |
|---|---|
void f(Shakl s) | Ha |
void f(const Shakl& s) | Yo'q |
void f(Shakl* s) | Yo'q |
std::vector<Shakl> | Ha |
std::vector<std::unique_ptr<Shakl>> | Yo'q |
Shakl s = d; | Ha |
Shakl& s = d; | Yo'q |
Himoya usuli: polimorf bazaviy sinfning nusxa
operatsiyalarini protected yoki = delete qiling.
Shunda kesilish kompilyatsiya xatosiga aylanadi.
Abstrakt interfeys #
// Sof virtual funksiya - ta'rifsiz
class Toshkichik
{
public:
virtual ~Toshkichik() = default;
virtual bool tolash(long long tiyin) = 0;
virtual std::string nomi() const = 0;
virtual long long komissiya(long long tiyin) const = 0;
};
class Karta : public Toshkichik
{
long long balans_;
public:
explicit Karta(long long balans) : balans_(balans) {}
bool tolash(long long tiyin) override
{
long long jami = tiyin + komissiya(tiyin);
if (jami > balans_) return false;
balans_ -= jami;
return true;
}
std::string nomi() const override { return "karta"; }
long long komissiya(long long tiyin) const override { return tiyin / 100; }
long long balans() const { return balans_; }
};
class Naqd : public Toshkichik
{
public:
bool tolash(long long) override { return true; }
std::string nomi() const override { return "naqd"; }
long long komissiya(long long) const override { return 0; }
};
void xarid(Toshkichik& usul, long long narx)
{
bool natija = usul.tolash(narx);
std::println(" {:<6} {:>10} tiyin, komissiya {:>6}, natija: {}",
usul.nomi(), narx, usul.komissiya(narx), natija);
}
int main()
{
Karta k{1'000'000};
Naqd n;
std::println("to'lovlar:");
xarid(k, 300'000);
xarid(n, 300'000);
xarid(k, 900'000);
std::println("");
std::println("kartada qoldi: {} tiyin", k.balans());
// Toshkichik t; ← kompilyatsiya xatosi: abstrakt sinf
return 0;
}
to'lovlar:
karta 300000 tiyin, komissiya 3000, natija: true
naqd 300000 tiyin, komissiya 0, natija: true
karta 900000 tiyin, komissiya 9000, natija: false
kartada qoldi: 697000 tiyin
Kompozitsiya - ko'pincha yaxshiroq #
// YOMON: Stack Vector dan voris qilingan
// class Stack : public std::vector<int> { };
// Muammo: Stack endi push_back, insert, operator[] ni ham beradi
// YAXSHI: kompozitsiya
class Stek
{
std::vector<int> ma_; // A'ZO, bazaviy sinf emas
public:
void qoy(int q) { ma_.push_back(q); }
int ol()
{
int oxirgi = ma_.back();
ma_.pop_back();
return oxirgi;
}
int tepa() const { return ma_.back(); }
bool boshmi() const { return ma_.empty(); }
std::size_t soni() const { return ma_.size(); }
};
int main()
{
Stek s;
s.qoy(10);
s.qoy(20);
s.qoy(30);
std::println("elementlar: {}", s.soni());
std::println("tepa: {}", s.tepa());
std::println("");
std::print("chiqarish tartibi:");
while (!s.boshmi()) std::print(" {}", s.ol());
std::println("");
std::println("");
std::println("Stek faqat 5 ta amalni ochadi.");
std::println("vector dan voris qilinsa - 40 dan ortiq amal ochilardi,");
std::println("shu jumladan stek qoidasini buzadiganlari.");
return 0;
}
elementlar: 3
tepa: 30
chiqarish tartibi: 30 20 10
Stek faqat 5 ta amalni ochadi.
vector dan voris qilinsa - 40 dan ortiq amal ochilardi,
shu jumladan stek qoidasini buzadiganlari.
Ko'p holatda vorislik kerak emas:
1. Kompozitsiya - "menda bor" munosabati:
class Mashina {
Dvigatel dvigatel_; // voris emas, a'zo
};
2. std::function - bitta xatti-harakat kerak
bo'lsa, butun sinf ierarxiyasi ortiqcha:
class Tugma {
std::function<void()> bosilganda_;
};
3. Shablon - tanlov kompilyatsiya paytida ma'lum:
template <typename Saqlash>
class Kesh { Saqlash saqlash_; };
Uchinchisi eng tez - virtual chaqiruv umuman yo'q.
Vorislikni faqat ish paytida turli obyektlarni bir xil interfeys orqali boshqarish kerak bo'lganda ishlating.
std::function bilan #
class Hisoblagich
{
std::string nom_;
int qiymat_{};
std::function<void(int)> ozgarganda_;
public:
explicit Hisoblagich(std::string nom) : nom_(std::move(nom)) {}
void kuzat(std::function<void(int)> f) { ozgarganda_ = std::move(f); }
void osir(int n = 1)
{
qiymat_ += n;
if (ozgarganda_) ozgarganda_(qiymat_);
}
int qiymat() const { return qiymat_; }
const std::string& nom() const { return nom_; }
};
int main()
{
Hisoblagich h{"tashriflar"};
std::println("kuzatuvchisiz:");
h.osir();
h.osir(5);
std::println(" qiymat: {}", h.qiymat());
std::println("");
std::println("kuzatuvchi bilan:");
int ogohlantirish = 0;
h.kuzat([&ogohlantirish](int q) {
std::println(" o'zgardi -> {}", q);
if (q > 10) ++ogohlantirish;
});
h.osir(3);
h.osir(4);
h.osir();
std::println("");
std::println("10 dan oshgan holatlar: {}", ogohlantirish);
std::println("Bu yerda birorta virtual funksiya yo'q.");
return 0;
}
kuzatuvchisiz:
qiymat: 6
kuzatuvchi bilan:
o'zgardi -> 9
o'zgardi -> 13
o'zgardi -> 14
10 dan oshgan holatlar: 2
Bu yerda birorta virtual funksiya yo'q.
Virtual chaqiruvning narxi #
struct Bazaviy
{
virtual ~Bazaviy() = default;
virtual int hisobla(int n) const { return n; }
};
struct Voris : Bazaviy
{
int hisobla(int n) const override { return n * 2; }
};
struct Oddiy
{
int hisobla(int n) const { return n * 2; }
};
int main()
{
std::println("Bo'sh oddiy sinf: {} bayt", sizeof(Oddiy));
std::println("Bo'sh virtual sinf: {} bayt", sizeof(Bazaviy));
std::println("Ko'rsatkich hajmi: {} bayt", sizeof(void*));
std::println("");
std::println("Virtual sinfda yashirin jadval ko'rsatkichi bor.");
std::println("Bo'sh oddiy sinf 1 bayt - chunki C++ da har bir");
std::println("obyektning manzili boshqacha bo'lishi kerak.");
std::println("");
std::println("Chaqiruv narxi:");
std::println(" oddiy: to'g'ridan-to'g'ri, inline bo'lishi mumkin");
std::println(" virtual: jadvaldan o'qish + bilvosita chaqiruv");
std::println(" inline BO'LMAYDI (odatda)");
std::unique_ptr<Bazaviy> p = std::make_unique<Voris>();
std::println("");
std::println("virtual natija: {}", p->hisobla(21));
Oddiy o;
std::println("oddiy natija: {}", o.hisobla(21));
return 0;
}
Bo'sh oddiy sinf: 1 bayt
Bo'sh virtual sinf: 8 bayt
Ko'rsatkich hajmi: 8 bayt
Virtual sinfda yashirin jadval ko'rsatkichi bor.
Bo'sh oddiy sinf 1 bayt - chunki C++ da har bir
obyektning manzili boshqacha bo'lishi kerak.
Chaqiruv narxi:
oddiy: to'g'ridan-to'g'ri, inline bo'lishi mumkin
virtual: jadvaldan o'qish + bilvosita chaqiruv
inline BO'LMAYDI (odatda)
virtual natija: 42
oddiy natija: 42
Virtual chaqiruv qo'shadi:
| Xarajat | Miqdor |
|---|---|
| Obyekt hajmi | Bitta ko'rsatkich (8 bayt) |
| Chaqiruv | Bitta bilvosita o'qish |
| Optimallashtirish | Inline qilib bo'lmaydi |
Ko'p dasturda bu sezilmaydi. Lekin ichki tsiklda millionlab marta chaqirilsa - farq seziladi, chunki kompilyator inline qila olmaydi va uning atrofidagi optimallashtirishlar ham yo'qoladi.
Shuning uchun o'yin dvigatellari va ma'lumotlar bazalari
issiq yo'llarda vorislik o'rniga shablon yoki switch
ishlatadi.
Lekin: avval o'lchang. Virtual chaqiruvni optimallashtirish - odatda oxirgi qadam, birinchi emas.
- Bazaviy sinf va ikki voris yozing, virtual funksiyani qayta aniqlang.
virtual ~T()ni olib tashlang va vorisni bazaviy ko'rsatkich orqali o'chiring.overridesizconstni tushirib qoldiring - qaysi funksiya chaqiriladi?- Voris obyektini qiymat bo'yicha funksiyaga uzating - kesilishni ko'ring.
std::vector<Bazaviy>vastd::vector<unique_ptr<Bazaviy>>ni solishtiring.- Sof virtual funksiya bilan abstrakt interfeys yozing.
- Abstrakt sinfdan obyekt yaratishga urinib ko'ring.
std::vectordan voris qilingan sinf yozing - qanday amallar ochiladi?- Xuddi shu sinfni kompozitsiya bilan qayta yozing.
- Virtual sinf va oddiy sinf hajmini
sizeofbilan solishtiring.
Xulosa #
- Vorislik uchun mo'ljallangan sinfda
virtual ~T() = defaultshart. - Har bir qayta aniqlashga
overrideyozing - kompilyator imzoni tekshiradi. overridesiz mos kelmagan imzo yangi funksiya yaratadi, xato bermaydi.- Kesilish: qiymat bo'yicha uzatishda voris qismi jimgina yo'qoladi.
- Polimorf obyektlarni havola, ko'rsatkich yoki
unique_ptrorqali boshqaring. - Sof virtual funksiya (
= 0) abstrakt interfeys yaratadi. - Vorislik - interfeys uchun, kod qayta ishlatish uchun emas.
- Kod qayta ishlatish kerak bo'lsa - kompozitsiya (a'zo qiling).
- Bitta xatti-harakat kerak bo'lsa -
std::functionierarxiyadan yengil. - Tanlov kompilyatsiya paytida ma'lum bo'lsa - shablon eng tez.
- Virtual chaqiruv narxi kichik, lekin nol emas - o'lchamasdan optimallashtirmang.
Keyingi bo'limda shablonlar va konseptlar ni ko'ramiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.