డైనమిక్ మెమరీ & Smart Pointers

🌐 Modern C++ (C++20/C++17/C++14) 🟢 Chapter 12 of 20 📂 Phase 03: ఫంక్షన్స్, మెమరీ & పాయింటర్లు 📅 2026 Edition
📌 Covered in this chapter: new & delete ఆపరేటర్లు · మెమరీ లీక్స్ & డాంగ్లింగ్ పాయింటర్లు · RAII సూత్రం · std::unique_ptr · std::shared_ptr & std::weak_ptr

కంప్యూటర్ ప్రోగ్రామ్‌లు రన్ అవుతున్నప్పుడు డేటా పరిమాణం ఎల్లప్పుడూ ముందే తెలియదు. ఒక సర్వర్‌కు ఒకేసారి 10 మంది యూజర్లు కనెక్ట్ అవ్వవచ్చు, లేదా పీక్ అవర్స్‌లో 10 లక్షల మంది యూజర్లు రావచ్చు. ప్రోగ్రామ్ రన్ అయ్యే సమయంలోనే అవసరమైనప్పుడు మెమరీని పెంచుకోవడం లేదా తగ్గించుకోవడాన్ని డైనమిక్ మెమరీ అలొకేషన్ (Dynamic Memory Allocation) అంటారు.

సాంప్రదాయ C++ లో మాన్యువల్ మెమరీ కేటాయింపులు (new మరియు delete) ప్రోగ్రామర్లకు పూర్తి స్వేచ్ఛను ఇచ్చినప్పటికీ, ఒక చిన్న పొరపాటు జరిగినా మెమరీ లీక్స్ (Memory Leaks), డబుల్ ఫ్రీ (Double Free) మరియు డాంగ్లింగ్ పాయింటర్లు వంటి వినాశకరమైన బగ్స్‌కు కారణమయ్యేవి. దీనిని రూపుమాపడానికే Modern C++ మనకు RAII (Resource Acquisition Is Initialization) సిద్ధాంతాన్ని మరియు విప్లవాత్మకమైన స్మార్ట్ పాయింటర్లను (Smart Pointers) అందించింది.

ఈ సమగ్ర మాస్టర్‌క్లాస్‌లో మనం Stack vs Heap మెమరీ ఆర్కిటెక్చర్, std::unique_ptr యొక్క జీరో-కాస్ట్ ఓవర్‌హెడ్ ఎక్స్‌క్లూజివ్ ఓనర్‌షిప్, std::shared_ptr యొక్క రిఫరెన్స్ కౌంటింగ్, సర్క్యులర్ డిపెండెన్సీలను విచ్ఛిన్నం చేసే std::weak_ptr, మరియు పూర్తి స్థాయి "Enterprise Multi-Tenant Cloud Session Cache & Resource Manager" ప్రాజెక్ట్‌ను లోతుగా అధ్యయనం చేసి నిర్మిద్దాం!

1 స్టాక్ వర్సెస్ హీప్ మెమరీ (Stack vs Heap Memory: The Two Worlds of RAM)

కంప్యూటర్ ఆపరేటింగ్ సిస్టమ్ ఒక C++ ప్రోగ్రామ్‌ను ఎగ్జిక్యూట్ చేసేటప్పుడు RAM ను ప్రధానంగా రెండు వేర్వేరు వర్క్‌స్పేస్‌లుగా విభజిస్తుంది: స్టాక్ (Stack) మరియు హీప్ (Heap లేదా Free Store). ఈ రెండింటి ప్రాథమిక నిర్మాణాన్ని అర్థం చేసుకోవడం ప్రతి సిస్టమ్ ఇంజనీర్‌కు అత్యంత ఆవశ్యకం.

[Virtual Process Memory Layout (High to Low Addresses)]
+-------------------------------------------------------+ 0xFFFFFFFF (High Address)
| STACK MEMORY (Grows DOWNWARDS ↓) |
| Local Variables, Function Call Frames, Return Ptrs |
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| ↓ Free Space (Guard Pages) |
| ↑ Free Space |
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| HEAP MEMORY (Grows UPWARDS ↑) |
| Dynamic Allocations (new, malloc), Large Data Buffers|
+-------------------------------------------------------+
| BSS & DATA SEGMENTS (Global & Static Variables) |
+-------------------------------------------------------+
| TEXT / CODE SEGMENT (Compiled Machine Instructions) |
+-------------------------------------------------------+ 0x00000000 (Low Address)
అంశం / ఫీచర్ స్టాక్ మెమరీ (Stack Memory) హీప్ మెమరీ (Heap / Free Store)
నిర్వహణ (Management) CPU హార్డ్‌వేర్ మరియు కంపైలర్ చేత పూర్తిగా ఆటోమేటిక్ ప్రోగ్రామర్ / స్మార్ట్ పాయింటర్ల చేత మాన్యువల్ నియంత్రణ
వేగం (Performance) అత్యంత వేగవంతం (కేవలం Stack Pointer రిజిస్టర్ మూవ్ అవుతుంది) కాస్త నెమ్మది (OS కర్నెల్ మెమరీ బ్లాక్స్ వెతికి కేటాయించాలి)
పరిమాణం (Size Limit) పరిమితం (సాధారణంగా 1 MB నుండి 8 MB వరకు). దాటితే Stack Overflow! విశాలమైనది (కంప్యూటర్‌లో ఉన్న మొత్తం RAM మరియు వర్చువల్ మెమరీ)
జీవితకాలం (Lifetime) ఫంక్షన్ స్కోప్ ముగియగానే ఆటోమేటిక్‌గా నాశనమవుతుంది మనం డిలీట్ చేసే వరకు (లేదా ప్రోగ్రామ్ ముగిసే వరకు) శాశ్వతంగా ఉంటుంది

2 సాంప్రదాయ డైనమిక్ మెమరీ: new మరియు delete ఆపరేటర్లు

C++ లో హీప్ (Heap) మెమరీ నుండి డైనమిక్‌గా స్థలాన్ని కేటాయించుకోవడానికి new ఆపరేటర్‌ను, ఆ మెమరీని తిరిగి ఆపరేటింగ్ సిస్టమ్‌కు విడుదల చేయడానికి delete ఆపరేటర్‌ను ఉపయోగిస్తాం.

ఇక్కడ గుర్తుంచుకోవాల్సిన అతి ముఖ్యమైన నియమం: ఎన్ని new లు రాస్తే, ఖచ్చితంగా అన్ని delete లు ఉండాలి!

raw_new_delete.cpp
#include <iostream>

int main() {
    // 1. హీప్‌పై ఒక సింగిల్ ఇంటిజర్‌ను అలొకేట్ చేయడం
    int* ptr = new int(100); // హీప్‌పై 4 బైట్లు కేటాయించి 100 స్టోర్ చేస్తుంది
    std::cout << "హీప్ అడ్రస్: " << ptr << " | విలువ: " << *ptr << "\n";

    // ఉపయోగించిన తర్వాత తప్పనిసరిగా డిలీట్ చేయాలి!
    delete ptr;
    ptr = nullptr; // డాంగ్లింగ్ కాకుండా సేఫ్‌గా నల్ చేయడం

    // 2. హీప్‌పై డైనమిక్ అర్రే అలొకేషన్
    int size = 5;
    int* dynamicArr = new int[size]; // 5 పూర్ణాంకాల మెమరీ కేటాయింపు

    for (int i = 0; i < size; ++i) {
        dynamicArr[i] = (i + 1) * 10;
    }

    std::cout << "డైనమిక్ అర్రే మూడవ ఎలిమెంట్: " << dynamicArr[2] << "\n";

    // ⚠️ అత్యంత ముఖ్యం: అర్రేలను delete[] తో మాత్రమే డిలీట్ చేయాలి!
    delete[] dynamicArr;
    dynamicArr = nullptr;

    return 0;
}
⚠️ తీవ్రమైన హెచ్చరిక (delete vs delete[]): ఒక సింగిల్ ఆబ్జెక్ట్‌ను new తో సృష్టిస్తే delete ptr; వాడాలి. ఒక అర్రేను new T[n] తో సృష్టిస్తే తప్పనిసరిగా delete[] arr; వాడాలి! అర్రేకు కేవలం delete arr; రాస్తే కేవలం మొదటి ఎలిమెంట్ మాత్రమే డిస్ట్రక్ట్ అవుతుంది లేదా అన్-డిఫైన్డ్ బిహేవియర్ (UB) ఏర్పడుతుంది.

3 మాన్యువల్ మెమరీ మేనేజ్‌మెంట్ శాపాలు (Memory Leaks & Double Free)

సాంప్రదాయ new మరియు delete తో కోడింగ్ చేయడం ఎందుకు ప్రమాదకరం? మానవ సహజమైన పొరపాట్ల వల్ల సాఫ్ట్‌వేర్ ఉత్పత్తుల్లో క్రింది మూడు ప్రధాన భద్రతా లోపాలు ఏర్పడతాయి:

  1. మెమరీ లీక్ (Memory Leak): హీప్‌పై new ద్వారా మెమరీని అలొకేట్ చేసుకుని, దానిని delete చేయడం మర్చిపోతే, ఆ మెమరీ ప్రోగ్రామ్ రన్ అయినంత కాలం బ్లాక్ అయిపోతుంది. సర్వర్ అప్లికేషన్లలో రోజులు గడిచేకొద్దీ RAM నిండిపోయి సర్వర్ మొత్తం క్రాష్ అవుతుంది (Out of Memory - OOM Killer).
  2. డాంగ్లింగ్ పాయింటర్ (Dangling Pointer): మెమరీని delete చేసిన తర్వాత కూడా ఆ పాయింటర్‌ను అలాగే ఉంచి, పొరపాటున *ptr = 50; అని మళ్లీ డేటా రాయడానికి ప్రయత్నించడం. దీనివల్ల వేరొక ప్రాసెస్ డేటా కరప్ట్ అవుతుంది.
  3. డబుల్ ఫ్రీ (Double Free): ఒకే మెమరీ అడ్రస్‌ను రెండుసార్లు delete చేయడానికి ప్రయత్నించడం. ఇది OS మెమరీ హీప్ మేనేజర్‌ను క్రాష్ చేస్తుంది మరియు హ్యాకర్లకు భద్రతా దాడులకు (Remote Code Execution) మార్గం సుగమం చేస్తుంది.
memory_leak_antipattern.cpp
#include <iostream>

void processUserData() {
    int* buffer = new int[1000]; // 4000 బైట్లు కేటాయించాం

    bool errorOccurred = true;
    if (errorOccurred) {
        // పొరపాటు: ఎర్రర్ రాగానే ఫంక్షన్ నుండి బయటకు వెళ్లిపోయాం!
        // delete[] buffer చేయలేదు!
        std::cout << "❌ లోపం సంభవించింది! మెమరీ లీక్ అయ్యింది (4000 Bytes కోల్పోయాం).\n";
        return; 
    }

    delete[] buffer;
}

int main() {
    for (int i = 0; i < 3; ++i) {
        processUserData();
    }
    return 0;
}

4 Modern C++ విప్లవం: RAII సిద్ధాంతం (Resource Acquisition Is Initialization)

C++ రూపకర్త Bjarne Stroustrup ప్రోగ్రామింగ్ ప్రపంచానికి అందించిన అత్యంత విప్లవాత్మకమైన మరియు ప్రభావవంతమైన సాఫ్ట్‌వేర్ డిజైన్ సిద్ధాంతం: RAII (Resource Acquisition Is Initialization).

RAII అసలు రహస్యం ఏమిటి?

"ఏదైనా ఒక కంప్యూటర్ రిసోర్స్ (హీప్ మెమరీ, ఫైల్ హ్యాండిల్, నెట్‌వర్క్ సాకెట్, లేదా మ్యూటెక్స్ లాక్) ను పొందేటప్పుడు దానిని ఒక స్టాక్ ఆబ్జెక్ట్ కన్‌స్ట్రక్టర్ (Constructor) లో కేటాయించండి; ఆ ఆబ్జెక్ట్ స్కోప్ ముగిసి నశించినప్పుడు దాని డిస్ట్రక్టర్ (Destructor) ఆటోమేటిక్‌గా ఆ రిసోర్స్‌ను క్లీన్ చేసేలా నిర్మించండి!"

స్టాక్ వేరియబుల్స్ వాటి స్కోప్ (Curly Braces { ... }) ముగియగానే 100% గ్యారెంటీగా డిస్ట్రక్ట్ అవుతాయి — ప్రోగ్రామ్ సాధారణంగా ముగిసినా, return చేసినా, లేదా మధ్యలో ఎక్సెప్షన్ (Exception) విసిరినా సరే! దీనివల్ల మానవ ప్రమేయం లేకుండా మెమరీ క్లీనప్ జరుగుతుంది.

raii_principle_demo.cpp
#include <iostream>

// RAII Wrapper క్లాస్
class SafeIntBuffer {
private:
    int* data;
public:
    SafeIntBuffer(int val) {
        data = new int(val);
        std::cout << "  [RAII కన్‌స్ట్రక్టర్] హీప్ మెమరీ కేటాయించబడింది: " << *data << "\n";
    }

    ~SafeIntBuffer() {
        std::cout << "  [RAII డిస్ట్రక్టర్] ఆటోమేటిక్‌గా మెమరీ విడుదల చేయబడింది: " << *data << "\n";
        delete data;
    }

    int getValue() const { return *data; }
};

int main() {
    std::cout << "స్కోప్ ప్రారంభం...\n";
    {
        SafeIntBuffer buffer(777); // స్టాక్ ఆబ్జెక్ట్ సృష్టించబడింది
        std::cout << "బఫర్ విలువ: " << buffer.getValue() << "\n";
    } // <-- ఇక్కడ స్కోప్ ముగిసింది! డిస్ట్రక్టర్ స్వయంచాలకంగా పిలువబడుతుంది!
    std::cout << "స్కోప్ ముగిసింది! మెమరీ లీక్ సున్నా!\n";
    return 0;
}

5 Modern Smart Pointers పరిచయం (<memory> Header)

మనం ప్రతిసారీ స్వంతంగా RAII క్లాసులను రాయాల్సిన అవసరం లేకుండా, Modern C++ స్టాండర్డ్ లైబ్రరీ #include <memory> హెడర్ ఫైల్ ద్వారా మూడు అత్యున్నత స్థాయి స్మార్ట్ పాయింటర్లను అందించింది:

  1. std::unique_ptr (Exclusive Ownership): ఆ రిసోర్స్‌పై ఏకైక యాజమాన్యం కలిగి ఉంటుంది. దీనిని వేరొకరికి కాపీ చేయలేము, కేవలం ఓనర్‌షిప్‌ను బదిలీ (Move) మాత్రమే చేయవచ్చు. జీరో రన్‌టైమ్ ఓవర్‌హెడ్!
  2. std::shared_ptr (Shared Ownership): బహుళ యజమానులు ఒకే రిసోర్స్‌ను పంచుకుంటారు. అంతర్గతంగా ఒక రిఫరెన్స్ కౌంటర్‌ను నిర్వహిస్తుంది. చివరి యజమాని కూడా వదిలేసినప్పుడు మెమరీ డిలీట్ అవుతుంది.
  3. std::weak_ptr (Non-Owning Observer): shared_ptr తో కలిసి పనిచేస్తుంది. రిఫరెన్స్ కౌంట్‌ను పెంచకుండా ఆబ్జెక్ట్‌ను గమనిస్తుంది. సైక్లిక్ డిపెండెన్సీ వల్ల వచ్చే మెమరీ లీక్స్‌ను నివారిస్తుంది.
🌟 Modern C++ Core Guideline: "Never use raw new or delete in application code. Use std::make_unique and std::make_shared instead!"

6 std::unique_ptr (Exclusive Ownership & Zero-Cost Abstraction)

std::unique_ptr అనేది Modern C++ లో అత్యంత ప్రాధాన్యత కలిగిన స్మార్ట్ పాయింటర్. దీని ప్రధాన లక్షణాలు:

  • ఏకైక యాజమాన్యం (Exclusive Ownership): ఒక మెమరీ బ్లాక్‌కు ఒకే ఒక్క unique_ptr యజమానిగా ఉంటాడు.
  • జీరో ఓవర్‌హెడ్ (Zero Runtime Overhead): ఇది రా పాయింటర్ (T*) వలె ఖచ్చితంగా 8 బైట్లు మాత్రమే ఉంటుంది. ఎటువంటి అదనపు మెమరీని లేదా CPU సైకిళ్లను వృధా చేయదు!
  • కాపీయింగ్ నిషిద్ధం (Non-Copyable): unique_ptr ను డూప్లికేట్ చేయలేము. కేవలం std::move ద్వారా ఓనర్‌షిప్‌ను బదిలీ చేయవచ్చు.
  • ఆటోమేటిక్ డిలీషన్: unique_ptr స్కోప్ దాటిపోగానే లోపలి మెమరీని ఆటోమేటిక్‌గా delete చేస్తుంది.

Modern C++14 నుండి unique_ptr ను సృష్టించడానికి ఎల్లప్పుడూ std::make_unique<T>() ఫంక్షన్‌ను ఉపయోగించాలి.

unique_ptr_mastery.cpp
#include <iostream>
#include <memory>
#include <string>

class DeviceNode {
public:
    std::string name;
    DeviceNode(std::string n) : name(n) {
        std::cout << "  🔌 పరికరం ఆన్‌లైన్ అయ్యింది: " << name << "\n";
    }
    ~DeviceNode() {
        std::cout << "  🛑 పరికరం సురక్షితంగా షట్‌డౌన్ అయ్యింది: " << name << "\n";
    }
    void ping() const {
        std::cout << "  📡 Ping విజయవంతమైంది: " << name << "\n";
    }
};

int main() {
    std::cout << "===== std::unique_ptr డెమో =====\n";

    // C++14 make_unique ఉపయోగించి సురక్షిత సృష్టి
    std::unique_ptr<DeviceNode> dev1 = std::make_unique<DeviceNode>("Router_A1");
    dev1->ping();

    // std::unique_ptr<DeviceNode> dev2 = dev1; // ❌ కంపైలర్ ఎర్రర్! కాపీ చేయడం నిషిద్ధం!

    std::cout << "\nఓనర్‌షిప్‌ను dev2 కు బదిలీ (std::move) చేస్తున్నాం...\n";
    std::unique_ptr<DeviceNode> dev2 = std::move(dev1);

    if (dev1 == nullptr) {
        std::cout << "dev1 ఇప్పుడు సురక్షితంగా nullptr (ఖాళీ) అయ్యింది.\n";
    }
    dev2->ping();

    std::cout << "\nమెయిన్ ఫంక్షన్ ముగింపు...\n";
    return 0; // dev2 స్కోప్ ముగియగానే DeviceNode ఆటోమేటిక్‌గా డిస్ట్రక్ట్ అవుతుంది!
}

7 std::shared_ptr (Shared Ownership & Reference Counting)

కొన్ని సందర్భాల్లో ఒకే డేటా ఆబ్జెక్ట్‌ను సిస్టమ్‌లోని బహుళ భాగాలు (ఉదాహరణకు మల్టీ-థ్రెడెడ్ టాస్క్‌లు లేదా గ్రాఫ్ నోడ్స్) ఏకకాలంలో పంచుకోవాల్సి వస్తుంది. అటువంటి సమయాల్లో std::shared_ptr ను ఉపయోగిస్తాం.

std::shared_ptr ఎలా పనిచేస్తుంది?

  • ఇది హీప్‌పై ఒక కంట్రోల్ బ్లాక్ (Control Block) ను సృష్టించి, అందులో ఒక రిఫరెన్స్ కౌంటర్ (Reference Counter) ను నిర్వహిస్తుంది.
  • ఎప్పుడైతే ఒక కొత్త shared_ptr అదే మెమరీని పాయింట్ చేస్తుందో, రిఫరెన్స్ కౌంట్ $+1$ పెరుగుతుంది (use_count()).
  • ఒక shared_ptr స్కోప్ ముగిసి నశించినప్పుడు, రిఫరెన్స్ కౌంట్ $-1$ తగ్గుతుంది.
  • రిఫరెన్స్ కౌంట్ ఖచ్చితంగా సున్నా (0) అయిన తక్షణమే, కంట్రోల్ బ్లాక్ ఆ అసలైన మెమరీని ఆటోమేటిక్‌గా delete చేస్తుంది!
shared_ptr_demo.cpp
#include <iostream>
#include <memory>

class DatabaseConnection {
public:
    DatabaseConnection() { std::cout << "  🗄️ డేటాబేస్ కనెక్షన్ ఓపెన్ అయ్యింది!\n"; }
    ~DatabaseConnection() { std::cout << "  🔒 డేటాబేస్ కనెక్షన్ పూర్తిగా క్లోజ్ చేయబడింది!\n"; }
    void query() { std::cout << "  ⚡ SQL Query అమలు చేయబడుతోంది...\n"; }
};

int main() {
    std::cout << "===== std::shared_ptr రిఫరెన్స్ కౌంటింగ్ డెమో =====\n";

    // make_shared ద్వారా సింగిల్ మెమరీ అలొకేషన్
    std::shared_ptr<DatabaseConnection> conn1 = std::make_shared<DatabaseConnection>();
    std::cout << "conn1 ప్రారంభం | రిఫరెన్స్ కౌంట్: " << conn1.use_count() << "\n";

    {
        std::shared_ptr<DatabaseConnection> conn2 = conn1; // కాపీ సవ్యమైనది!
        std::cout << "conn2 చేరింది    | రిఫరెన్స్ కౌంట్: " << conn1.use_count() << "\n";

        {
            std::shared_ptr<DatabaseConnection> conn3 = conn2;
            std::cout << "conn3 చేరింది    | రిఫరెన్స్ కౌంట్: " << conn1.use_count() << "\n";
            conn3->query();
        } // conn3 నశించింది

        std::cout << "conn3 వెళ్ళిపోయింది | రిఫరెన్స్ కౌంట్: " << conn1.use_count() << "\n";
    } // conn2 నశించింది

    std::cout << "conn2 వెళ్ళిపోయింది | రిఫరెన్స్ కౌంట్: " << conn1.use_count() << "\n";
    std::cout << "మెయిన్ ఫంక్షన్ ముగింపు...\n";
    return 0; // conn1 నశించగానే కౌంట్ 0 అయ్యి డేటాబేస్ కనెక్షన్ క్లోజ్ అవుతుంది!
}

8 సర్క్యులర్ డిపెండెన్సీ సమస్య & std::weak_ptr (Breaking Cycles)

std::shared_ptr చాలా అద్భుతమైనదే అయినప్పటికీ, దానికి ఒక ప్రమాదకరమైన బలహీనత ఉంది: సర్క్యులర్ రిఫరెన్స్ (Circular Dependency).

ఆలోచించండి: ఆబ్జెక్ట్ A అనేది ఆబ్జెక్ట్ B ని shared_ptr తో పాయింట్ చేస్తోంది; ఆబ్జెక్ట్ B కూడా ఆబ్జెక్ట్ A ని shared_ptr తో పాయింట్ చేస్తోంది అనుకుందాం. ఇప్పుడు బయట ఉన్న పాయింటర్లు పోయినా, ఈ రెండూ ఒకదాన్నొకటి పట్టుకుని ఉండటం వల్ల రిఫరెన్స్ కౌంట్ ఎప్పటికీ సున్నా కాదు! దీనివల్ల భారీ మెమరీ లీక్ ఏర్పడుతుంది.

[The Deadlock of Circular shared_ptr]
[Node A] ---(shared_ptr)---> [Node B]
[Node A] <---(shared_ptr)--- [Node B]
Both retain use_count = 1 forever! Neither destructor will EVER be called!

[The Solution: std::weak_ptr]
[Node A] ---(shared_ptr)---> [Node B]
[Node A] <---(weak_ptr)----- [Node B] (Non-owning observer, use_count stays safe!)

ఈ సమస్యను పరిష్కరించడానికి std::weak_ptr ఉపయోగపడుతుంది:

  • weak_ptr అనేది ఆబ్జెక్ట్‌పై యాజమాన్యాన్ని (Ownership) తీసుకోదు.
  • ఇది రిఫరెన్స్ కౌంటర్‌ను పెంచదు.
  • ఆబ్జెక్ట్ ఇంకా బ్రతికే ఉందో లేదో తెలుసుకోవడానికి weak.expired() లేదా తాత్కాలిక shared_ptr గా మార్చడానికి weak.lock() ను ఉపయోగిస్తాం.
weak_ptr_solution.cpp
#include <iostream>
#include <memory>

struct NodeB; // ఫార్వర్డ్ డిక్లరేషన్

struct NodeA {
    std::shared_ptr<NodeB> ptrB;
    ~NodeA() { std::cout << "  NodeA డిస్ట్రక్ట్ అయ్యింది!\n"; }
};

struct NodeB {
    // 💡 సర్కిల్‌ను విచ్ఛిన్నం చేయడానికి weak_ptr వాడాం!
    std::weak_ptr<NodeA> ptrA; 
    ~NodeB() { std::cout << "  NodeB డిస్ట్రక్ట్ అయ్యింది!\n"; }
};

int main() {
    std::cout << "===== std::weak_ptr సైకిల్ నివారణ డెమో =====\n";
    {
        auto a = std::make_shared<NodeA>();
        auto b = std::make_shared<NodeB>();

        a->ptrB = b;
        b->ptrA = a; // weak_ptr అసైన్‌మెంట్ (use_count పెరగదు!)

        std::cout << "NodeA use_count: " << a.use_count() << "\n";
        std::cout << "NodeB use_count: " << b.use_count() << "\n";

        // weak_ptr నుండి డేటాను యాక్సెస్ చేసే విధానం:
        if (auto lockedA = b->ptrA.lock()) {
            std::cout << "weak_ptr సురక్షితంగా లాక్ చేయబడింది! ఆబ్జెక్ట్ సజీవంగా ఉంది.\n";
        }
    } // స్కోప్ ముగియగానే NodeA మరియు NodeB రెండూ సవ్యంగా డిస్ట్రక్ట్ అవుతాయి!

    std::cout << "స్కోప్ ముగిసింది. మెమరీ లీక్ విజయవంతంగా నివారించబడింది!\n";
    return 0;
}

9 కస్టమ్ డెలీటర్లు (Custom Deleters with Smart Pointers)

స్మార్ట్ పాయింటర్లు కేవలం హీప్ ర్యామ్ మెమరీని delete చేయడానికి మాత్రమే పరిమితం కాలేదు! సిస్టమ్ ప్రోగ్రామింగ్‌లో ఫైల్ హ్యాండిల్స్ (FILE*), C-లైబ్రరీ సాకెట్లు, డేటాబేస్ కర్సర్లు, విండో హ్యాండిల్స్ వంటి ఏ రిసోర్స్‌నైనా ఆటోమేటిక్‌గా క్లీన్ చేయడానికి కస్టమ్ డెలీటర్ (Custom Deleter) ను జోడించవచ్చు.

custom_deleter_file.cpp
#include <iostream>
#include <memory>
#include <cstdio>

// C-File హ్యాండిల్ కోసం కస్టమ్ డెలీటర్ లాంబ్డా
int main() {
    auto fileCloser = [](FILE* fp) {
        if (fp) {
            std::cout << "  📄 కస్టమ్ డెలీటర్: fclose() ద్వారా ఫైల్ ఆటోమేటిక్‌గా క్లోజ్ చేయబడింది!\n";
            std::fclose(fp);
        }
    };

    {
        // unique_ptr తో ఫైల్ హ్యాండిల్ నిర్వహణ
        std::unique_ptr<FILE, decltype(fileCloser)> myFile(std::fopen("test_log.txt", "w"), fileCloser);

        if (myFile) {
            std::fputs("Our Compiler Modern C++ RAII Log", myFile.get());
            std::cout << "ఫైల్‌లోకి లాగ్ డేటా వ్రాయబడింది.\n";
        }
    } // <-- ఇక్కడ స్కోప్ ముగియగానే ఫైల్ ఆటోమేటిక్‌గా క్లోజ్ అవుతుంది! fclose() పిలవాల్సిన పని లేదు!

    return 0;
}

10 స్మార్ట్ పాయింటర్ల ఎంపిక మార్గదర్శి (Decision Matrix)

ప్రాజెక్ట్ నిర్మాణంలో ఏ పాయింటర్ ఎప్పుడు వాడాలో నిర్ణయించడానికి క్రింది సింపుల్ డెసిషన్ గైడ్‌ను అనుసరించండి:

ప్రశ్న / అవసరం సరైన ఎంపిక కారణం & ప్రయోజనం
నాకు సింగిల్ ఓనర్ మరియు గరిష్ట పనితీరు కావాలి std::unique_ptr సున్నా ఓవర్‌హెడ్ (8 బైట్లు), ఫాస్ట్, కాపీ చేయడం కుదరదు. డిఫాల్ట్ ఎంపిక!
బహుళ భాగాలు ఆబ్జెక్ట్‌ను ఏకకాలంలో ఓన్ చేసుకోవాలి std::shared_ptr థ్రెడ్-సేఫ్ రిఫరెన్స్ కౌంటింగ్. చివరి యూజర్ పోగానే ఆటో-డిలీట్.
ఆబ్జెక్ట్‌ను గమనించాలి, కానీ ఓనర్‌షిప్ తీసుకోకూడదు std::weak_ptr రిఫరెన్స్ కౌంట్ పెరగదు, సర్క్యులర్ డిపెండెన్సీ లీక్స్ నివారిస్తుంది.
ఫంక్షన్ పారామీటర్‌గా భారీ ఆబ్జెక్ట్ పాస్ చేయాలి const T& (Reference) స్మార్ట్ పాయింటర్ కంటే వేగవంతమైనది. పాయింటర్ ఓవర్‌హెడ్ లేకుండా సున్నా కాపీ.
ఆప్షనల్ ఆబ్జెక్ట్ (నల్ ఉండవచ్చు) కానీ ఓనర్‌షిప్ లేదు const T* (Raw Pointer) ఓనర్‌షిప్ లేని నాన్-ఓనింగ్ అబ్జర్వర్‌గా రా పాయింటర్లు ఇప్పటికీ ఆమోదయోగ్యమే.

11 స్మార్ట్ పాయింటర్లలో బిగినర్స్ చేసే తప్పులు & నివారణ

  1. ఒకే రా పాయింటర్‌తో రెండు shared_ptr లను సృష్టించడం:
    int* raw = new int(10); std::shared_ptr<int> s1(raw); std::shared_ptr<int> s2(raw);
    ప్రమాదం: రెండూ వేర్వేరు కంట్రోల్ బ్లాక్‌లను సృష్టించుకుంటాయి. దీనివల్ల Double Free జరిగి ప్రోగ్రామ్ క్రాష్ అవుతుంది!
    పరిష్కారం: ఎల్లప్పుడూ std::make_shared వాడండి.
  2. ప్రతిచోటా అనవసరంగా shared_ptr వాడటం: unique_ptr సరిపోయే చోట కూడా shared_ptr వాడితే అటామిక్ రిఫరెన్స్ కౌంటింగ్ వల్ల మల్టీ-కోర్ CPU లలో పనితీరు నెమ్మదిస్తుంది.
  3. weak_ptr ను చెక్ చేయకుండా వాడటం: ఎల్లప్పుడూ auto sp = wp.lock() ద్వారా చెక్ చేసి, అది నల్ కాదని నిర్ధారించుకున్నాకే ఉపయోగించాలి.

12 రియల్-వరల్డ్ ప్రాజెక్ట్: Enterprise Multi-Tenant Session Cache & Resource Manager

నిజ జీవితంలో AWS, Google Cloud, Netflix లేదా బ్యాంకింగ్ క్లౌడ్ ప్లాట్‌ఫామ్‌లలో సెకనుకు మిలియన్ల కొద్దీ యూజర్ సెషన్లు, డేటాబేస్ క్యాష్‌లు నడుస్తాయి. అక్కడ మెమరీ లీక్ అవ్వకుండా, సెషన్లు ముగియగానే ఆటోమేటిక్‌గా క్లీన్ అవ్వాలి, మరియు సైక్లిక్ డిపెండెన్సీలు ఉండకూడదు.

ఈ ప్రాజెక్ట్‌లో మనం std::unique_ptr, std::shared_ptr మరియు std::weak_ptr మూడింటి కలయికతో ఒక ఎంటర్‌ప్రైజ్ మల్టీ-టెనెంట్ సెషన్ మేనేజర్ ను నిర్మిస్తున్నాం:

EnterpriseSessionManager.cpp
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>

// టెనెంట్ కాన్ఫిగరేషన్ (Exclusive Ownership: unique_ptr)
class TenantConfig {
public:
    std::string tenantId;
    int maxConnections;
    TenantConfig(std::string id, int maxConn) : tenantId(id), maxConnections(maxConn) {
        std::cout << "  🏢 [కన్ఫిగరేషన్ లోడ్ అయింది] టెనెంట్ ID: " << tenantId << "\n";
    }
    ~TenantConfig() {
        std::cout << "  🗑️ [కన్ఫిగరేషన్ డిస్ట్రాయ్ అయింది] టెనెంట్ ID: " << tenantId << "\n";
    }
};

// యూజర్ సెషన్ డేటా (Shared Ownership: shared_ptr)
class UserSession {
public:
    std::string sessionId;
    std::string username;
    UserSession(std::string sId, std::string user) : sessionId(sId), username(user) {
        std::cout << "  👤 [లాగిన్] సెషన్ ప్రారంభమైంది: " << username << " (ID: " << sessionId << ")\n";
    }
    ~UserSession() {
        std::cout << "  🚪 [లాగౌట్] సెషన్ మెమరీ ఆటోమేటిక్‌గా విడుదల చేయబడింది: " << username << "\n";
    }
    void executeAction(const std::string& action) {
        std::cout << "  ⚡ యాక్షన్ అమలు: " << username << " -> " << action << "\n";
    }
};

// క్లౌడ్ సెషన్ మేనేజర్ ఇంజిన్
class CloudSessionManager {
private:
    std::unordered_map<std::string, std::shared_ptr<UserSession>> activeSessions;
    std::vector<std::weak_ptr<UserSession>> sessionAuditLog; // Non-owning weak observer

public:
    void registerSession(std::shared_ptr<UserSession> session) {
        activeSessions[session->sessionId] = session;
        sessionAuditLog.push_back(session); // weak_ptr రూపంలో స్టోర్ అవుతుంది
        std::cout << "  [మేనేజర్] సెషన్ నమోదు చేయబడింది. ప్రస్తుత యాక్టివ్ సెషన్లు: " << activeSessions.size() << "\n";
    }

    void terminateSession(const std::string& sessionId) {
        std::cout << "\n[మేనేజర్] సెషన్ ముగింపు ఆదేశం: " << sessionId << "\n";
        activeSessions.erase(sessionId); // మ్యాప్ నుండి తొలగించగానే use_count తగ్గుతుంది
    }

    void runAuditInspection() {
        std::cout << "\n--- [సెక్యూరిటీ ఆడిట్ ఇన్‌స్పెక్షన్ (weak_ptr ద్వారా)] ---\n";
        for (size_t i = 0; i < sessionAuditLog.size(); ++i) {
            if (auto liveSession = sessionAuditLog[i].lock()) {
                std::cout << "  ✅ ఆడిట్ #" << (i + 1) << ": సెషన్ సజీవంగా ఉంది -> " << liveSession->username << "\n";
            } else {
                std::cout << "  ⚠️ ఆడిట్ #" << (i + 1) << ": సెషన్ ఇప్పటికే సురక్షితంగా క్లోజ్ చేయబడింది (Expired!)\n";
            }
        }
    }
};

int main() {
    std::cout << "=======================================================================\n";
    std::cout << "☁️ [ENTERPRISE CLOUD MULTI-TENANT SESSION & RESOURCE ENGINE]\n";
    std::cout << "=======================================================================\n";

    // 1. unique_ptr తో టెనెంట్ కాన్ఫిగరేషన్ (Zero-overhead exclusive ownership)
    std::unique_ptr<TenantConfig> tenant = std::make_unique<TenantConfig>("HDFC_BANK_PROD", 5000);

    CloudSessionManager manager;

    // 2. shared_ptr తో సెషన్ల నిర్వహణ
    auto user1 = std::make_shared<UserSession>("SESS_001", "Ravi_Kumar");
    auto user2 = std::make_shared<UserSession>("SESS_002", "Priya_Sharma");

    manager.registerSession(user1);
    manager.registerSession(user2);

    user1->executeAction("₹ 25,000 NEFT Transfer");
    user2->executeAction("Download Account Statement");

    manager.runAuditInspection();

    // Ravi Kumar లాగౌట్ అయ్యాడు
    manager.terminateSession("SESS_001");
    user1.reset(); // లోకల్ పాయింటర్ కూడా క్లియర్ చేశాం (use_count = 0)

    manager.runAuditInspection();

    std::cout << "\n=======================================================================\n";
    std::cout << "ప్రోగ్రామ్ ముగింపు: మిగిలిన అన్ని సెషన్లు మరియు కాన్ఫిగరేషన్లు RAII ద్వారా ఆటోమేటిక్‌గా క్లీన్ అవుతాయి!\n";
    std::cout << "=======================================================================\n";
    return 0;
}

13 సారాంశం & ఇంటరాక్టివ్ క్విజ్ (Summary & Self-Check Quizzes)

📌 ఈ అధ్యాయంలో నేర్చుకున్న ప్రధాన అంశాలు:

  • Stack vs Heap: స్టాక్ మెమరీ వేగవంతమైనది మరియు ఆటోమేటిక్; హీప్ మెమరీ విశాలమైనది మరియు రన్‌టైమ్ డైనమిక్ కేటాయింపులకు అనువైనది.
  • new & delete: సాంప్రదాయ C++ ఆపరేటర్లు. new[] తో కేటాయించిన అర్రేలకు తప్పనిసరిగా delete[] వాడాలి.
  • RAII సూత్రం: రిసోర్స్ మేనేజ్‌మెంట్‌ను ఆబ్జెక్ట్ లైఫ్‌టైమ్‌తో ముడిపెట్టే గోల్డెన్ డిజైన్ పాటర్న్. ఎక్సెప్షన్ వచ్చినా మెమరీ లీక్ కాదు.
  • std::unique_ptr: ఏకైక యాజమాన్యం. కాపీ కుదరదు, std::move చేయవచ్చు. రా పాయింటర్ అంతే వేగం (Zero Overhead).
  • std::shared_ptr: బహుళ యజమానులు. కంట్రోల్ బ్లాక్ రిఫరెన్స్ కౌంటింగ్ (use_count()) ద్వారా ఆటోమేటిక్ డీఅలొకేషన్.
  • std::weak_ptr: నాన్-ఓనింగ్ అబ్జర్వర్. సర్క్యులర్ డిపెండెన్సీ డెడ్‌లాక్‌లను నివారించి మెమరీ లీక్స్ రాకుండా కాపాడుతుంది.
  • కస్టమ్ డెలీటర్లు: ఫైల్స్, సాకెట్స్ మరియు C-హ్యాండిల్స్‌ను స్మార్ట్ పాయింటర్ల ద్వారా స్వయంచాలకంగా క్లోజ్ చేసే వెసులుబాటు.

🧠 మీ అవగాహనను పరీక్షించుకోండి (5 Interactive Quizzes):

ప్రశ్న 1: C++ లో int* arr = new int[10]; అని కేటాయించిన మెమరీని delete arr; అని డిలీట్ చేస్తే ఏమవుతుంది?
సమాధానం: Undefined Behavior (తీవ్రమైన రన్‌టైమ్ లోపం / మెమరీ కరప్షన్) ఏర్పడుతుంది!
వివరణ: new[] తో కేటాయించిన అర్రేలను తప్పనిసరిగా delete[] arr; తోనే డిలీట్ చేయాలి. కేవలం delete రాస్తే కేవలం మొదటి ఎలిమెంట్ మాత్రమే ప్రాసెస్ అవుతుంది లేదా కంపైలర్ హీప్ హెడర్ మెటాడేటాను తప్పుగా చదివి ప్రోగ్రామ్ క్రాష్ అవుతుంది.
ప్రశ్న 2: std::unique_ptr ను వేరొక ఫంక్షన్‌కు లేదా వేరియబుల్‌కు కాపీ (Copy) చేయడానికి ప్రయత్నిస్తే ఏమవుతుంది?
సమాధానం: కంపైలేషన్ ఎర్రర్ వస్తుంది!
వివరణ: std::unique_ptr లో Copy Constructor మరియు Copy Assignment Operator లు డిలీట్ చేయబడ్డాయి (= delete). ఇది ఎక్స్‌క్లూజివ్ ఓనర్‌షిప్ సూత్రంపై పనిచేస్తుంది. దానిని వేరొకరికి బదిలీ చేయాలంటే కేవలం std::move(ptr) మాత్రమే వాడాలి.
ప్రశ్న 3: std::shared_ptr లో ఆబ్జెక్ట్ మెమరీ ఎప్పుడు ఆటోమేటిక్‌గా డిలీట్ అవుతుంది?
సమాధానం: కంట్రోల్ బ్లాక్‌లోని రిఫరెన్స్ కౌంట్ (Reference Count) ఖచ్చితంగా సున్నా (0) అయినప్పుడు!
వివరణ: ఆ ఆబ్జెక్ట్‌ను పాయింట్ చేస్తున్న చివరి shared_ptr కూడా తన స్కోప్ ముగిసి డిస్ట్రక్ట్ అయినప్పుడు లేదా reset() అయినప్పుడు కౌంట్ 0 అవుతుంది; తక్షణమే ఆబ్జెక్ట్ డిస్ట్రక్టర్ కాల్ అయ్యి హీప్ మెమరీ విడుదలవుతుంది.
ప్రశ్న 4: సర్క్యులర్ రిఫరెన్స్ (Circular Reference) సమస్యను పరిష్కరించడానికి std::weak_ptr ఎందుకు సరైన పరిష్కారం?
సమాధానం: ఎందుకంటే std::weak_ptr రిఫరెన్స్ కౌంటర్‌ను పెంచదు (Non-owning Observer)!
వివరణ: రెండు ఆబ్జెక్టులు ఒకరినొకరు shared_ptr తో చూసుకుంటే కౌంట్ ఎప్పటికీ 1 గానే మిగిలిపోయి మెమరీ లీక్ అవుతుంది. ఒకదానిలో weak_ptr వాడటం వల్ల కౌంట్ పెరగదు, బాహ్య స్కోప్ ముగియగానే ప్రధాన ఆబ్జెక్ట్ సవ్యంగా డిస్ట్రక్ట్ అవుతుంది.
ప్రశ్న 5: Modern C++ లో new MyClass() రాసే బదులు std::make_unique<MyClass>() ఎందుకు వాడాలి?
సమాధానం: Exception Safety (ఎక్సెప్షన్ భద్రత) మరియు క్లీన్ సింటాక్స్ కొరకు.
వివరణ: ఫంక్షన్ కాల్స్ లోపల ఆర్గుమెంట్లు ఎగ్జిక్యూట్ అయ్యే ఆర్డర్‌లో ఎక్సెప్షన్ వస్తే new ద్వారా కేటాయించిన మెమరీ లీక్ అయ్యే అవకాశం ఉంటుంది. make_unique ఎక్సెప్షన్-సేఫ్ గ్యారెంటీని ఇస్తుంది మరియు టైప్ పేరును రెండుసార్లు రాయాల్సిన అవసరం లేకుండా కోడ్‌ను క్లీన్‌గా ఉంచుతుంది.
← మునుపటి అధ్యాయం: 11. పాయింటర్లు & రిఫరెన్సెస్ తర్వాతి అధ్యాయం: 13. క్లాసెస్ & ఆబ్జెక్ట్స్ →

ఈ ప్రాజెక్ట్‌ను లైవ్‌గా మన కంపైలర్‌లో రన్ చేయండి!

ఎటువంటి లోకల్ సెటప్ లేకుండా, బ్రౌజర్‌లోనే నేరుగా Modern C++ (GCC 13 / C++20) కంపైలర్‌లో ఈ కోడ్‌ను ఎగ్జిక్యూట్ చేసి రన్‌టైమ్ అవుట్‌పుట్‌ను తనిఖీ చేయండి.