10-bo‘lim
Aqlli ko'rsatkichlar va egalik
Kim resursni o'chiradi degan savolga kodda javob berish - unique_ptr, shared_ptr va weak_ptr.
Ushbu bo‘lim mundarijasi
C++ da eng muhim savol: kim bu obyektni o'chiradi?
Xom ko'rsatkich bu savolga javob bermaydi. Aqlli ko'rsatkichlar esa javobni turning o'zida ifodalaydi.
Egalik nima #
#include <print>
#include <string>
#include <vector>
#include <memory>
#include <algorithm>
struct Resurs
{
std::string nom;
explicit Resurs(std::string n) : nom(std::move(n))
{
std::println(" + {} yaratildi", nom);
}
~Resurs()
{
std::println(" - {} yo'q qilindi", nom);
}
void ishla() const
{
std::println(" {} ishlamoqda", nom);
}
};
// Bu funksiya obyektni o'chirishi kerakmi? KOD AYTMAYDI.
void nomalum(Resurs* r)
{
r->ishla();
// delete r; ← qilish kerakmi? Bilib bo'lmaydi.
}
int main()
{
std::println("xom ko'rsatkich:");
Resurs* xom = new Resurs{"xom"};
nomalum(xom);
delete xom; // chaqiruvchi eslab qolishi kerak
return 0;
}
xom ko'rsatkich:
+ xom yaratildi
xom ishlamoqda
- xom yo'q qilindi
Resurs* r parametri to'rt xil ma'noda bo'lishi mumkin:
| Ma'no | Kim o'chiradi |
|---|---|
| "Ko'rib chiq, tegma" | Chaqiruvchi |
| "Egalikni ol" | Funksiya |
| "Bu massiv boshi" | Noaniq |
| "Bo'sh bo'lishi mumkin" | Noaniq |
Kod hech qaysisini aytmaydi. Javob hujjatda yoki dasturchi boshida qoladi - va u yerda unutiladi.
Aqlli ko'rsatkichlar bu ma'nolarni turga ko'chiradi:
| Tur | Ma'nosi |
|---|---|
std::unique_ptr<T> | Yagona egalik |
std::shared_ptr<T> | Bo'lishilgan egalik |
std::weak_ptr<T> | Egaliksiz kuzatuvchi |
T* (xom) | Egaliksiz ko'rinish |
T& | Egaliksiz, nullptr bo'lmaydi |
Endi funksiya imzosining o'zi javob beradi.
std::unique_ptr #
struct Resurs
{
std::string nom;
explicit Resurs(std::string n) : nom(std::move(n))
{ std::println(" + {}", nom); }
~Resurs() { std::println(" - {}", nom); }
void ishla() const { std::println(" {} ishlamoqda", nom); }
};
void kuzatuvchi(const Resurs& r) // egalik olmaydi
{
r.ishla();
}
void egalikOladi(std::unique_ptr<Resurs> r) // egalik oladi
{
std::println(" egalik olindi: {}", r->nom);
// funksiya oxirida obyekt yo'q qilinadi
}
int main()
{
{
std::println("qamrov ichida:");
auto p = std::make_unique<Resurs>("birinchi");
p->ishla();
kuzatuvchi(*p); // hali ham bizniki
std::println(" p bo'shmi: {}", p == nullptr);
}
std::println("qamrovdan chiqdi");
std::println("");
std::println("egalikni uzatish:");
auto q = std::make_unique<Resurs>("ikkinchi");
egalikOladi(std::move(q)); // move MAJBURIY
std::println(" q endi bo'shmi: {}", q == nullptr);
return 0;
}
qamrov ichida:
+ birinchi
birinchi ishlamoqda
birinchi ishlamoqda
p bo'shmi: false
- birinchi
qamrovdan chiqdi
egalikni uzatish:
+ ikkinchi
egalik olindi: ikkinchi
- ikkinchi
q endi bo'shmi: true
unique_ptr nusxalanmaydi - faqat ko'chiriladiauto a = std::make_unique<Resurs>("x");
auto b = a; // KOMPILYATSIYA XATOSI
auto b = std::move(a); // to'g'ri - a endi bo'sh
Bu ataylab shunday: agar nusxalash mumkin bo'lsa, ikki obyekt bitta resursga ega bo'lardi va destruktor ikki marta chaqirilardi.
std::move yozuvi kodda ko'rinadi - egalik
o'tayotgani o'quvchiga ayon bo'ladi.
Muhim: unique_ptr xom ko'rsatkichdan kattaroq emas:
int main()
{
std::println("int* {} bayt", sizeof(int*));
std::println("std::unique_ptr<int> {} bayt",
sizeof(std::unique_ptr<int>));
std::println("std::shared_ptr<int> {} bayt",
sizeof(std::shared_ptr<int>));
std::println("std::weak_ptr<int> {} bayt",
sizeof(std::weak_ptr<int>));
std::println("");
std::println("unique_ptr - nol qo'shimcha xarajat.");
std::println("shared_ptr - ikki barobar (ko'rsatkich + hisoblagich bloki).");
return 0;
}
int* 8 bayt
std::unique_ptr<int> 8 bayt
std::shared_ptr<int> 16 bayt
std::weak_ptr<int> 16 bayt
unique_ptr - nol qo'shimcha xarajat.
shared_ptr - ikki barobar (ko'rsatkich + hisoblagich bloki).
unique_ptr konteynerda #
struct Shakl
{
virtual ~Shakl() = default;
virtual double yuza() const = 0;
virtual std::string nomi() const = 0;
};
struct Doira : Shakl
{
double radius;
explicit Doira(double r) : radius(r) {}
double yuza() const override { return 3.14159265 * radius * radius; }
std::string nomi() const override { return "doira"; }
};
struct Kvadrat : Shakl
{
double tomon;
explicit Kvadrat(double t) : tomon(t) {}
double yuza() const override { return tomon * tomon; }
std::string nomi() const override { return "kvadrat"; }
};
int main()
{
std::vector<std::unique_ptr<Shakl>> shakllar;
shakllar.push_back(std::make_unique<Doira>(2.0));
shakllar.push_back(std::make_unique<Kvadrat>(3.0));
shakllar.push_back(std::make_unique<Doira>(1.5));
double jami = 0;
for (const auto& s : shakllar) {
std::println("{:<10} yuza {:.3f}", s->nomi(), s->yuza());
jami += s->yuza();
}
std::println("");
std::println("jami yuza: {:.3f}", jami);
// Eng katta shakl
auto eng = std::ranges::max_element(shakllar,
[](const auto& a, const auto& b) { return a->yuza() < b->yuza(); });
std::println("eng katta: {} ({:.3f})", (*eng)->nomi(), (*eng)->yuza());
// Vektor yo'q qilinganda hamma shakl ham yo'q qilinadi
return 0;
}
doira yuza 12.566
kvadrat yuza 9.000
doira yuza 7.069
jami yuza: 28.635
eng katta: doira (12.566)
unique_ptr kerakstd::vector<Shakl> yozib bo'lmaydi - Shakl abstrakt
sinf va uning hajmi noma'lum.
std::vector<std::unique_ptr<Shakl>> esa ishlaydi:
vektor ko'rsatkichlarni saqlaydi, obyektlar esa uyumda
yashaydi.
Diqqat: bazaviy sinfda virtual destruktor bo'lishi shart:
struct Shakl {
virtual ~Shakl() = default; // MAJBURIY
...
};
Aks holda unique_ptr<Shakl> yo'q qilinganda faqat
Shakl ning destruktori chaqiriladi - vorislarniki
emas. Bu 14-bo'limda batafsil ko'riladi.
std::shared_ptr #
struct Malumot
{
std::string nom;
explicit Malumot(std::string n) : nom(std::move(n))
{ std::println(" + {}", nom); }
~Malumot() { std::println(" - {}", nom); }
};
int main()
{
std::shared_ptr<Malumot> birinchi;
{
auto a = std::make_shared<Malumot>("umumiy");
std::println("a yaratildi, egalar soni: {}", a.use_count());
{
auto b = a; // NUSXA - hisoblagich oshadi
std::println("b nusxa olindi, egalar: {}", a.use_count());
auto c = a;
std::println("c nusxa olindi, egalar: {}", a.use_count());
}
std::println("b va c qamrovdan chiqdi, egalar: {}", a.use_count());
birinchi = a; // tashqariga uzatamiz
std::println("tashqariga uzatildi, egalar: {}", a.use_count());
}
std::println("a qamrovdan chiqdi, egalar: {}", birinchi.use_count());
std::println("obyekt hali TIRIK - chunki egasi bor");
std::println("");
std::println("main tugamoqda:");
return 0;
}
+ umumiy
a yaratildi, egalar soni: 1
b nusxa olindi, egalar: 2
c nusxa olindi, egalar: 3
b va c qamrovdan chiqdi, egalar: 1
tashqariga uzatildi, egalar: 2
a qamrovdan chiqdi, egalar: 1
obyekt hali TIRIK - chunki egasi bor
main tugamoqda:
- umumiy
shared_ptr - oxirgi chora, birinchi emasshared_ptr qulay ko'rinadi, lekin uning narxi bor:
| Narx | Izoh |
|---|---|
| Ikki barobar hajm | Ko'rsatkich + boshqaruv bloki |
| Atom hisoblagich | Har nusxada oshadi/kamayadi |
| Qo'shimcha xotira ajratish | make_shared buni birlashtiradi |
| Egalik noaniq | Kim oxirgi bo'lishini bilib bo'lmaydi |
Oxirgi qator eng muhimi. shared_ptr "bu obyektni kim
o'chiradi" savoliga "bilmayman, oxirgisi" deb javob
beradi.
Amaliy tavsiya:
| Vaziyat | Tanlov |
|---|---|
| Bitta egasi bor | unique_ptr |
| Egalik uzatiladi | unique_ptr + move |
| Kuzatish kerak | T* yoki T& |
| Haqiqatan bir necha ega | shared_ptr |
| Halqa bo'lishi mumkin | shared_ptr + weak_ptr |
Ko'p loyihada shared_ptr haddan tashqari ishlatiladi -
ko'pincha unique_ptr yetarli bo'lgan joyda.
Halqa muammosi #
struct Tugun
{
std::string nom;
std::shared_ptr<Tugun> keyingi;
explicit Tugun(std::string n) : nom(std::move(n))
{ std::println(" + {}", nom); }
~Tugun() { std::println(" - {}", nom); }
};
int main()
{
std::println("1. halqasiz:");
{
auto a = std::make_shared<Tugun>("A");
auto b = std::make_shared<Tugun>("B");
a->keyingi = b;
std::println(" A egalari: {}, B egalari: {}",
a.use_count(), b.use_count());
}
std::println(" ikkalasi ham yo'q qilindi");
std::println("");
std::println("2. halqa bilan:");
{
auto a = std::make_shared<Tugun>("X");
auto b = std::make_shared<Tugun>("Y");
a->keyingi = b;
b->keyingi = a; // HALQA
std::println(" X egalari: {}, Y egalari: {}",
a.use_count(), b.use_count());
}
std::println(" destruktor CHAQIRILMADI - xotira sizib chiqdi");
return 0;
}
1. halqasiz:
+ A
+ B
A egalari: 1, B egalari: 2
- A
- B
ikkalasi ham yo'q qilindi
2. halqa bilan:
+ X
+ Y
X egalari: 2, Y egalari: 2
destruktor CHAQIRILMADI - xotira sizib chiqdi
shared_ptr halqasi - jimgina sizishIkkinchi holatda destruktorlar chaqirilmadi.
Sabab: a qamrovdan chiqqanda X ning hisoblagichi 2 dan
1 ga tushdi (chunki Y hali ham unga ishora qiladi).
Y bilan ham xuddi shunday.
Natijada ikkalasi ham 1 da qoldi va hech qachon nolga tushmaydi.
Bu axlat yig'uvchili tillarda muammo emas - ular
erishib bo'lmaydigan halqalarni topa oladi. shared_ptr
esa oddiy hisoblagich va u halqani ko'rmaydi.
Halqa paydo bo'ladigan tipik joylar:
| Tuzilma | Halqa qayerda |
|---|---|
| Ikki tomonlama ro'yxat | Oldinga va orqaga havolalar |
| Daraxt + ota havolasi | Bola → ota → bola |
| Kuzatuvchi naqshi | Manba ↔ kuzatuvchi |
| Grafik | Istalgan sikl |
weak_ptr - halqani uzish #
struct Bola;
struct Ota
{
std::string nom;
std::vector<std::shared_ptr<Bola>> bolalar;
explicit Ota(std::string n) : nom(std::move(n))
{ std::println(" + Ota {}", nom); }
~Ota() { std::println(" - Ota {}", nom); }
};
struct Bola
{
std::string nom;
std::weak_ptr<Ota> ota; // WEAK - egalik olmaydi
explicit Bola(std::string n) : nom(std::move(n))
{ std::println(" + Bola {}", nom); }
~Bola() { std::println(" - Bola {}", nom); }
void otaniAyt() const
{
// weak_ptr ni ishlatish uchun lock() kerak
if (auto p = ota.lock()) {
std::println(" {} ning otasi: {}", nom, p->nom);
} else {
std::println(" {} ning otasi yo'q", nom);
}
}
};
int main()
{
std::shared_ptr<Bola> saqlangan;
{
auto ota = std::make_shared<Ota>("Husanboy");
auto bola = std::make_shared<Bola>("Aziza");
ota->bolalar.push_back(bola);
bola->ota = ota; // weak - hisoblagich oshmaydi
std::println("");
std::println(" ota egalari: {}", ota.use_count());
std::println(" bola egalari: {}", bola.use_count());
bola->otaniAyt();
saqlangan = bola;
}
std::println("");
std::println("qamrovdan chiqdi - ota yo'q qilindi:");
saqlangan->otaniAyt();
std::println("");
std::println("bola hali tirik, chunki uni biz saqladik:");
return 0;
}
+ Ota Husanboy
+ Bola Aziza
ota egalari: 1
bola egalari: 2
Aziza ning otasi: Husanboy
- Ota Husanboy
qamrovdan chiqdi - ota yo'q qilindi:
Aziza ning otasi yo'q
bola hali tirik, chunki uni biz saqladik:
- Bola Aziza
weak_ptr ni ishlatish qoidasiweak_ptr ni to'g'ridan-to'g'ri ishlatib bo'lmaydi -
avval lock() chaqiriladi:
if (auto p = zaif.lock()) {
p->ishla(); // obyekt hali tirik
} else {
// obyekt yo'q qilingan
}
lock() shared_ptr qaytaradi - u obyektni shu blok
davomida ushlab turadi. Bu poyga holatining oldini
oladi: expired() tekshiruvi bilan foydalanish orasida
obyekt yo'q bo'lishi mumkin edi.
Amaliy qoida:
| Munosabat | Kim shared_ptr |
|---|---|
| Ota → bola | Ota (egalik) |
| Bola → ota | weak_ptr |
| Kesh → obyekt | weak_ptr |
| Kuzatuvchi ro'yxati | weak_ptr |
Umumiy tamoyil: egalik yo'nalishi bir tomonlama
bo'lsin. Teskari havolalar weak_ptr yoki xom ko'rsatkich.
make_unique va make_shared #
struct Obyekt
{
int a, b;
Obyekt(int x, int y) : a(x), b(y) {}
};
int main()
{
// Tavsiya etiladigan usul
auto p1 = std::make_unique<Obyekt>(1, 2);
auto p2 = std::make_shared<Obyekt>(3, 4);
std::println("make_unique: {} {}", p1->a, p1->b);
std::println("make_shared: {} {}", p2->a, p2->b);
// Eski usul - ishlaydi, lekin tavsiya etilmaydi
std::unique_ptr<Obyekt> p3{new Obyekt{5, 6}};
std::println("new bilan: {} {}", p3->a, p3->b);
std::println("");
std::println("make_* ning ustunliklari:");
std::println(" 1. 'new' so'zi kodda umuman yo'q");
std::println(" 2. Tur bir marta yoziladi");
std::println(" 3. shared_ptr uchun BITTA xotira ajratish");
std::println(" 4. Istisno xavfsizligi");
return 0;
}
make_unique: 1 2
make_shared: 3 4
new bilan: 5 6
make_* ning ustunliklari:
1. 'new' so'zi kodda umuman yo'q
2. Tur bir marta yoziladi
3. shared_ptr uchun BITTA xotira ajratish
4. Istisno xavfsizligi
make_shared bitta ajratish qiladistd::shared_ptr<T> p{new T{}} ikki marta xotira
ajratadi:
Tobyekti uchun- Boshqaruv bloki (hisoblagichlar) uchun
std::make_shared<T>() esa ikkalasini bitta blokda
joylashtiradi - tezroq va kesh uchun yaxshiroq.
Yagona kamchilik: obyekt xotirasi barcha weak_ptr lar
yo'q bo'lgunicha bo'shatilmaydi (chunki blok umumiy).
Juda katta obyektlar va uzoq yashaydigan weak_ptr lar
bo'lsa - buni hisobga oling.
make_unique da bunday nozik joy yo'q - uni har doim
ishlating.
Maxsus o'chiruvchi #
#include <cstdio>
int main()
{
// C kutubxonasi resurslarini ham RAII ga o'rash mumkin
auto fayliniYop = [](std::FILE* f) {
if (f) {
std::fclose(f);
std::println(" [fayl yopildi]");
}
};
{
std::unique_ptr<std::FILE, decltype(fayliniYop)> fayl{
std::fopen("/tmp/sr_cpp_sinov.txt", "w"), fayliniYop};
if (fayl) {
std::fputs("RAII bilan yozildi\n", fayl.get());
std::println(" faylga yozildi");
}
}
std::println("qamrovdan chiqdi");
// O'qib tekshiramiz
std::println("");
if (auto f = std::fopen("/tmp/sr_cpp_sinov.txt", "r")) {
char buf[64]{};
if (std::fgets(buf, sizeof(buf), f)) {
std::println("faylda: {}", std::string_view{buf}.substr(0,
std::string_view{buf}.size() - 1));
}
std::fclose(f);
}
return 0;
}
faylga yozildi
[fayl yopildi]
qamrovdan chiqdi
faylda: RAII bilan yozildi
Tanlov jadvali #
int main()
{
std::println("{:<26} {}", "VAZIYAT", "TANLOV");
std::println("{}", std::string(64, '-'));
std::println("{:<26} {}", "Mahalliy obyekt", "oddiy o'zgaruvchi");
std::println("{:<26} {}", "Sinf a'zosi (qiymat)", "oddiy a'zo");
std::println("{:<26} {}", "Yagona egalik", "unique_ptr");
std::println("{:<26} {}", "Egalikni uzatish", "unique_ptr + move");
std::println("{:<26} {}", "Faqat o'qish (majburiy)", "const T&");
std::println("{:<26} {}", "Faqat o'qish (ixtiyoriy)", "const T*");
std::println("{:<26} {}", "Bir necha ega", "shared_ptr");
std::println("{:<26} {}", "Teskari havola", "weak_ptr");
std::println("{:<26} {}", "Polimorf konteyner", "vector<unique_ptr<T>>");
std::println("{:<26} {}", "C resursi", "unique_ptr + o'chiruvchi");
std::println("");
std::println("Kodingizda 'new' yoki 'delete' bo'lsa -");
std::println("deyarli har doim uni almashtirish mumkin.");
return 0;
}
VAZIYAT TANLOV
----------------------------------------------------------------
Mahalliy obyekt oddiy o'zgaruvchi
Sinf a'zosi (qiymat) oddiy a'zo
Yagona egalik unique_ptr
Egalikni uzatish unique_ptr + move
Faqat o'qish (majburiy) const T&
Faqat o'qish (ixtiyoriy) const T*
Bir necha ega shared_ptr
Teskari havola weak_ptr
Polimorf konteyner vector<unique_ptr<T>>
C resursi unique_ptr + o'chiruvchi
Kodingizda 'new' yoki 'delete' bo'lsa -
deyarli har doim uni almashtirish mumkin.
- Xom ko'rsatkich oluvchi funksiya yozing - u egalik oladimi?
std::make_uniquebilan obyekt yarating va qamrovdan chiqaring.unique_ptrni nusxalashga urinib ko'ring.std::movebilan egalikni funksiyaga uzating.unique_ptr,shared_ptrva xom ko'rsatkich hajmini solishtiring.- Polimorf shakllarni
vector<unique_ptr<Shakl>>da saqlang. shared_ptrhisoblagichini turli qamrovlarda kuzating.- Ikki obyekt orasida halqa yarating - destruktorlar chaqirildimi?
- Bir tomonni
weak_ptrqilib halqani uzing. - C faylini
unique_ptrva maxsus o'chiruvchi bilan o'rang.
Xulosa #
- Xom ko'rsatkich egalikni ifodalamaydi - kim o'chirishi noma'lum qoladi.
- Aqlli ko'rsatkichlar bu ma'noni turga ko'chiradi.
unique_ptr- yagona egalik, standart tanlov, xom ko'rsatkich bilan bir xil hajm.- U nusxalanmaydi - egalik
std::movebilan ko'rinadigan tarzda uzatiladi. - Polimorf obyektlar uchun
vector<unique_ptr<T>>va virtual destruktor kerak. shared_ptrhisoblagichga ega va u atom - narxi bor.- Ikki
shared_ptrbir-biriga ishora qilsa - halqa va jimgina sizish. weak_ptregalik olmaydi va halqani uzadi; ishlatish uchunlock().make_uniquevamake_sharedni ishlating -newkodda qolmasin.unique_ptrmaxsus o'chiruvchi bilan C resurslarini ham boshqara oladi.
Keyingi bo'limda sinflar bilan tanishamiz.
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.