بیایید در مورد تمیز و امن نگه داشتن کدهای C++ صحبت کنیم، مخصوصاً با تمرکز روی جلوگیری از نشت حافظه (Memory Leak) با استفاده از Smart Pointerها.

۱. دردسر اصلی: پوینترهای خام و نشت حافظه

بزرگترین دردسر پوینترهای خام (مثل int* data = new int(10);) اینه که نیاز به پاکسازی دستی دارن (delete data;). اگه یه تابع به خاطر ارور، دستور return یا یه exception زودتر تموم بشه، اون دستور delete اجرا نمیشه.

نتیجه: حافظه اختصاص داده شده هرگز به سیستم برنمی‌گرده. بوم، نشت حافظه.

۲. راه نجات: std::unique_ptr

اینجاست که std::unique_ptr وارد میشه. این یه Smart Pointer هست که طراحی شده تا مشکل نشت حافظه رو با اجبار مالکیت انحصاری و پاکسازی خودکار حل کنه.

الف. std::unique_ptr چیه؟

یه پوینتر هوشمنده که یه پوینتر به یه آبجکت توی heap رو نگه میداره. تضمین میکنه که در هر لحظه فقط یک unique_ptr میتونه به اون آبجکت اشاره کنه.

  • مالکیت انحصاری: شما نمیتونید یه unique_ptr رو کپی کنید. فقط میتونید مالکیت رو با استفاده از std::move منتقل کنید.
  • حذف خودکار: وقتی unique_ptr از scope خارج میشه (مثلاً وقتی تابع تموم میشه)، مخرب (destructor) اون به صورت خودکار صدا زده میشه، که اونم به نوبه خودش delete رو روی پوینتر خامی که نگه داشته اجرا میکنه.

اینم شکل کدنویسیش:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <memory>
#include <iostream>

void safeFunction() {
    // ساخت یک unique_ptr که مالک یک عدد صحیح با مقدار ۱۰ است
    std::unique_ptr<int> data = std::make_unique<int>(10);

    std::cout << "Value: " << *data << std::endl;

    // ... انجام کارها ...
    // اگر اینجا خطایی رخ دهد، 'data' همچنان پاکسازی می‌شود!

} // اینجا 'data' از scope خارج می‌شود و حافظه به صورت خودکار آزاد می‌شود.

سطح بالاتر: مثال‌های بیشتر

۱. پاس دادن مالکیت (مثل سیب‌زمینی داغ!)

چون unique_ptr انحصاریه، نمی‌تونید کپیش کنید. باید جابجاش (move) کنید.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void processItem(std::unique_ptr<int> item) {
    std::cout << "Processing item: " << *item << std::endl;
} // عمر item اینجا تموم میشه

int main() {
    auto myItem = std::make_unique<int>(100);
    
    // processItem(myItem); // ارور! کپی ممنوع
    processItem(std::move(myItem)); // حله! مالکیت منتقل شد
    
    // myItem الان دیگه خالیه (nullptr)
}

۲. کار با منابع قدیمی (C-Style)

با کتابخونه‌های قدیمی C کار می‌کنید؟ unique_ptr هنوزم می‌تونه با استفاده از یه پاک‌کننده سفارشی (Custom Deleter) نجاتتون بده.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <cstdio>

// یه پاک‌کننده سفارشی برای بستن فایل‌ها
struct FileCloser {
    void operator()(FILE* fp) const {
        if (fp) {
            std::cout << "Closing file automatically..." << std::endl;
            fclose(fp);
        }
    }
};

void writeFile() {
    // unique_ptr که به جای delete، تابع fclose رو صدا میزنه
    std::unique_ptr<FILE, FileCloser> file(fopen("log.txt", "w"));
    
    if (file) {
        fprintf(file.get(), "Hello RAII!");
    }
} // fclose اینجا به صورت خودکار صدا زده میشه

۴. پشت صحنه: واقعاً چطور کار می‌کنه؟

تا حالا فکر کردید چه جادویی باعث میشه این کار کنه؟ جادویی در کار نیست، فقط یه کلاس C++ معمولیه! کامپایلر رفتار خاصی با اسمارت پوینترها نداره؛ اون‌ها فقط کلاس‌های استانداردی هستن که هوشمندانه از قابلیت‌های C++ استفاده می‌کنن.

این یه نسخه ساده‌شده از چیزیه که std::unique_ptr توی کد منبع کتابخونه استاندارد به نظر میاد:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
template <typename T>
class unique_ptr {
private:
    T* ptr; // پوینتر خام اون داخل قایم شده!

public:
    // Constructor: پوینتر رو میگیره
    explicit unique_ptr(T* p = nullptr) : ptr(p) {}

    // Destructor: قهرمان داستان. پاکسازی خودکار رو انجام میده.
    ~unique_ptr() {
        delete ptr; // جادو همینجا اتفاق میفته!
    }

    // حذف کپی (DELETE): این مالکیت انحصاری رو اجبار میکنه.
    // کدی که بخواد اینو کپی کنه اصلاً کامپایل نمیشه.
    unique_ptr(const unique_ptr&) = delete;
    unique_ptr& operator=(const unique_ptr&) = delete;

    // اجازه جابجایی (Move): مالکیت رو به یکی دیگه منتقل میکنه.
    unique_ptr(unique_ptr&& other) noexcept : ptr(other.ptr) {
        other.ptr = nullptr; // قبلی الان دیگه خالیه.
    }

    // Operator Overloading: باعث میشه حس یه پوینتر واقعی رو بده.
    T& operator*() const { return *ptr; }
    T* operator->() const { return ptr; }
};

توضیحات فنی

  1. پوشش (Wrapper): فقط یه کلاسه که یه T* ptr خام رو نگه داشته.
  2. مخرب (~unique_ptr): این همون بخش RAII هست. وقتی stack خالی میشه، این اجرا میشه و delete رو صدا میزنه.
  3. توابع حذف شده (= delete): اینجوریه که کامپایلر جلوی کپی کردن رو میگیره. این چک کردن موقع اجرا نیست؛ ارور کامپایلره.
  4. اپراتورها: بازنویسی * و -> بهتون اجازه میده دقیقاً مثل raw_ptr->method() ازش استفاده کنید.

ب. بهترین روش (Best Practice)

همیشه برای اختصاص حافظه پویا از std::unique_ptr استفاده کنید مگه اینکه واقعاً نیاز داشته باشید مالکیت رو به اشتراک بذارید (که تو اون حالت از std::shared_ptr استفاده میکنید). روش ترجیحی برای ساختش استفاده از std::make_unique<T>(args) — هم برای exceptionها امن‌تره و هم کارآمدتر.

۳. فوت کوزه‌گری: RAII (Resource Acquisition Is Initialization)

تضمینی که std::unique_ptr برای پاکسازی حافظه میده، مدیون یه اصل بنیادی توی C++ به نام RAII هست.

الف. RAII چیه؟

RAII یه اصطلاحه که توش گرفتن (Acquisition) یه منبع (مثل حافظه، فایل هندل‌ها، قفل‌ها) گره خورده به مقداردهی اولیه (Initialization) یه آبجکت، معمولاً توی سازنده (constructor) اون آبجکت.

ب. RAII چطور کار میکنه

  • گرفتن (Constructor): منبع گرفته میشه. آبجکت حالا مثل یه پوشش هوشمند برای اون منبع عمل میکنه.
  • آزادسازی تضمین شده (Destructor): سی‌پلاس‌پلاس تضمین میکنه که destructor برای یه آبجکت که توی stack ساخته شده همیشه صدا زده میشه، مهم نیست چطور از scope فعلی خارج بشیم (return عادی، return زود هنگام، یا exception).

نقش unique_ptr به عنوان یه آبجکت RAII: سازنده unique_ptr حافظه رو میگیره. destructor اون تضمین شده که اجرا بشه، و داخل اون destructor، عملیات delete اجرا میشه. این باعث میشه مدیریت منابع در برابر exceptionها امن و خودکار باشه.

یه مثال ساده (RAII)

منبع رو مثل یه کتاب کتابخونه در نظر بگیرید و آبجکت RAII (unique_ptr) رو مثل یه کوله پشتی جادویی کتابخونه.

  • گرفتن: شما بلافاصله کتاب رو میذارید توی کوله پشتی.
  • آزادسازی: لحظه‌ای که کوله پشتی رو درمیارید (از scope خارج میشید)، برنامه‌ریزی شده که به صورت خودکار کتاب رو پرواز بده سمت کتابخونه. منبع همیشه تمیز میشه و برای نفر بعدی آماده‌ست.