10-bo‘lim

Aqlli ko'rsatkichlar va egalik

Kim resursni o'chiradi degan savolga kodda javob berish - unique_ptr, shared_ptr va weak_ptr.

🕑 16 daqiqa o‘qish 📄 961 so‘z 👁 3 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Egalik nima
  2. std::unique_ptr
  3. unique_ptr konteynerda
  4. std::shared_ptr
  5. Halqa muammosi
  6. weak_ptr - halqani uzish
  7. make_unique va make_shared
  8. Maxsus o'chiruvchi
  9. Tanlov jadvali
  10. Xulosa

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 #

C++
#include <print>
#include <string>
#include <vector>
#include <memory>
#include <algorithm>
C++
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;
}
Natija
xom ko'rsatkich:
  + xom yaratildi
  xom ishlamoqda
  - xom yo'q qilindi
Xom ko'rsatkich niyatni ifodalamaydi

Resurs* r parametri to'rt xil ma'noda bo'lishi mumkin:

Ma'noKim 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:

TurMa'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 #

C++
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;
}
Natija
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'chiriladi
KOD
auto 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:

C++
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;
}
Natija
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 #

C++
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;
}
Natija
doira      yuza 12.566
kvadrat    yuza 9.000
doira      yuza 7.069

jami yuza: 28.635
eng katta: doira (12.566)
Polimorf obyektlar uchun unique_ptr kerak

std::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:

KOD
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 #

C++
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;
}
Natija
  + 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 emas

shared_ptr qulay ko'rinadi, lekin uning narxi bor:

NarxIzoh
Ikki barobar hajmKo'rsatkich + boshqaruv bloki
Atom hisoblagichHar nusxada oshadi/kamayadi
Qo'shimcha xotira ajratishmake_shared buni birlashtiradi
Egalik noaniqKim 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:

VaziyatTanlov
Bitta egasi borunique_ptr
Egalik uzatiladiunique_ptr + move
Kuzatish kerakT* yoki T&
Haqiqatan bir necha egashared_ptr
Halqa bo'lishi mumkinshared_ptr + weak_ptr

Ko'p loyihada shared_ptr haddan tashqari ishlatiladi - ko'pincha unique_ptr yetarli bo'lgan joyda.

Egalikni tur bilan ifodalash unique_ptr<T> Yagona egalik ptr obyekt Nusxalanmaydi, ko'chiriladi Hajmi: xom ko'rsatkich bilan bir xil STANDART TANLOV shared_ptr<T> Bo'lishilgan egalik p1 p2 obyekt hisoblagich: 2 Oxirgi ega yo'q bo'lganda o'chadi Atom hisoblagich - narxi bor T* va T& Egaliksiz kuzatuv ptr birovniki Hech narsa o'chirmaydi Obyekt yo'q bo'lsa - osiladi Faqat KO'RISH uchun Funksiya imzosi nima deydi void f(const T& t) → o'qiydi, egalik olmaydi, nullptr bo'lmaydi void f(T* t) → o'qiydi/yozadi, egalik olmaydi, nullptr mumkin void f(std::unique_ptr<T> t) → EGALIKNI OLADI Halqa muammosi Ikki shared_ptr bir-biriga ishora qilsa, hisoblagich hech qachon nolga tushmaydi. Yechim: bir tomonni weak_ptr qilish.
Egalik - turning bir qismi, hujjatdagi izoh emas

Halqa muammosi #

C++
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;
}
Natija
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 sizish

Ikkinchi 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:

TuzilmaHalqa qayerda
Ikki tomonlama ro'yxatOldinga va orqaga havolalar
Daraxt + ota havolasiBola → ota → bola
Kuzatuvchi naqshiManba ↔ kuzatuvchi
GrafikIstalgan sikl

weak_ptr - halqani uzish #

C++
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;
}
Natija
  + 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 qoidasi

weak_ptr ni to'g'ridan-to'g'ri ishlatib bo'lmaydi - avval lock() chaqiriladi:

KOD
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:

MunosabatKim shared_ptr
Ota → bolaOta (egalik)
Bola → otaweak_ptr
Kesh → obyektweak_ptr
Kuzatuvchi ro'yxatiweak_ptr

Umumiy tamoyil: egalik yo'nalishi bir tomonlama bo'lsin. Teskari havolalar weak_ptr yoki xom ko'rsatkich.

make_unique va make_shared #

C++
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;
}
Natija
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 qiladi

std::shared_ptr<T> p{new T{}} ikki marta xotira ajratadi:

  1. T obyekti uchun
  2. 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 #

C++
#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;
}
Natija
  faylga yozildi
  [fayl yopildi]
qamrovdan chiqdi

faylda: RAII bilan yozildi

Tanlov jadvali #

C++
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;
}
Natija
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.
Amaliy topshiriq
  1. Xom ko'rsatkich oluvchi funksiya yozing - u egalik oladimi?
  2. std::make_unique bilan obyekt yarating va qamrovdan chiqaring.
  3. unique_ptr ni nusxalashga urinib ko'ring.
  4. std::move bilan egalikni funksiyaga uzating.
  5. unique_ptr, shared_ptr va xom ko'rsatkich hajmini solishtiring.
  6. Polimorf shakllarni vector<unique_ptr<Shakl>> da saqlang.
  7. shared_ptr hisoblagichini turli qamrovlarda kuzating.
  8. Ikki obyekt orasida halqa yarating - destruktorlar chaqirildimi?
  9. Bir tomonni weak_ptr qilib halqani uzing.
  10. C faylini unique_ptr va 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::move bilan ko'rinadigan tarzda uzatiladi.
  • Polimorf obyektlar uchun vector<unique_ptr<T>> va virtual destruktor kerak.
  • shared_ptr hisoblagichga ega va u atom - narxi bor.
  • Ikki shared_ptr bir-biriga ishora qilsa - halqa va jimgina sizish.
  • weak_ptr egalik olmaydi va halqani uzadi; ishlatish uchun lock().
  • make_unique va make_shared ni ishlating - new kodda qolmasin.
  • unique_ptr maxsus o'chiruvchi bilan C resurslarini ham boshqara oladi.

Keyingi bo'limda sinflar bilan tanishamiz.

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.