رفتن به مطلب
جامعه‌ی برنامه‌نویسان مُدرن ایران
  • مدیریت منابع در ++C و آشنایی با اصطلاحات مدرن


    کامبیز اسدزاده

    اصطلاحاتی که بهتر است در مورد C++  مدرن بدانید!

    داشتم به این فکر می‌کردم که برخی از مبتدیان برنامه‌نویسی به خصوص کسانی که به سراغ زبان‌هایی مثل سی++ می‌روند معمولاً مستقیم وارد کد نویسی می‌شوند و به این گمان که آغاز برنامه‌نویسی یعنی نوشتن یک کد با خروجی «سلام، دنیا»! دریغ از آن‌ که بعضی از موارد مانند «معرفی کامپایلر و انواع آن» و حتی «ساختار برنامه‌های نوشته شده تحت سی‌پلاس‌پلاس» و یا حتی «مدیریت حافظه» را در نظر بگیرند! من معمولاً در مقالات و آموزش‌های خودم به این اشاره می‌کنم که قبل از هر چیز باید با ساختار برنامه‌های نوشته شده‌ی یک زبان آشنا شد و سپس به بررسی موارد دیگر مانند نحو زبان و یا دیگر ویژگی‌های آن.

    dangers-of-garbage-collection-blog-header@2x.png

    بنابراین، یکی از خطرناک‌ترین عواملی که موجب خونریزی داخلی یک نرم‌افزار در برنامه‌های نوشته شده توسط برنامه‌نویس درC++  می‌شود عدم مدیریت حافظه‌ی اختصاص یافته است که باید بعد از اختصاص یافتن حافظه در زمان معین آن را آزادسازی کند. در صورتی که این کار صورت نگیرد عمل Memory Leak (نَشتِ حافظه) رخ داده است.

    بسیاری از علاقه‌مندان بر این باورند که چون سی++ دارای GC یا همان Grabbage Collector (زباله‌روب) نیست که البته صحیح است! سی++ دارای GC نیست و این امر محدودیت یا نکته ضعف آن هم نیست! سی++ همه چیز را آزادانه در اختیار برنامه‌نویس قرار می‌دهد تا خود در زمان مناسب روش مدیریت حافظه را انتخاب کند. در علوم رایانه بازیافت حافظه یا زباله‌روبی نوعی مدیریت حافظه‌ی خودکار است که عمل مدیریت حافظه‌های اختصاص یافته شده را به دست می‌گیرد و اکثر زبان‌های برنامه‌نویسی مانند #C، جاوا  و دیگر موارد مشابه به آن مجهز به این ویژگی هستند که البته وجود چنین ابزار‌هایی می‌تواند توهمی را ایجاد کند مبنی بر آن که دیگر نیازی به مدیریت منابع نیست، اما در بعضی موارد مدیریت منابع هنوز یک الزام است چرا که منابع آزاد شده هنوز هم دلیل بر نشتِ حافظه هستند.

    این نشت حافطه زمانی اتفاق می‌افتد که اشیاء هنوز قابل دسترس از طرف اشیاءای که زنده هستند اما هرگز مورد استفاده‌ی دوباره قرار نمی‌گیرند اتفاق بی‌افتد.

    در بسیاری از زبان‌های برنامه‌نویسی این ویژگی وجود دارد که طبیعتاً مدیریت توسط GC راه حل بسیار خوب و بی نقصی نیست. اما با توجه به عدم وجود GC در سی++ اکثراً با روش‌های دستی برای مدیریت حافظه می‌پردازند که رایج‌ترین روش آن استفاده از عمل new و delete در اختصاص دادن و آزاد‌سازی حافظه است.

    بسیاری از ما با سی++ در دانشگاه و یا دروس مرتبط با مفاهیم اولیه برنامه‌نویسی آشنا شده ایم، اما معمولاً مفاهیم مربوطه برای نسل‌های قبلی و منسوخ شده‌ی این زبان است. بهتر است در نظر داشته باشید که برنامه‌نویسی مدرن یعنی پیروی از اصول و قوانین جدیدی که در تکامل یافتن یک زبان به کار گرفته می‌شود.

     

    ram.png

    RAII : Resource Acquisition is initialization

     

    بنابراین، باید در نظر گرفت مدیریت حافظه از استاندارد ۱۱ به بعد این زبان به روش‌های بسیار مدرن‌تری هوشمند سازی شده است. یکی از بهترین تکنیک‌های موجود در هسته‌ی زبان الگوی RAII است.

    تکنیک RAII مخفف «Resource Acquisition is initialization» به مفهوم (کنترل تخصیص منابع و آزاد‌سازی آن‌ها) یک ویژگی اصلی در سی‌پلاس‌پلاس است، که متکی به کامپایلر (همگردان) برای فراخوانی خودکار بعضی موارد عنوان می‌شود. با قرار دادن چنین کدی در مخرب (ویرانگر) دیگر به فراخوانی آن کد توسط برنامه‌نویس نیست و کامپایلر خود این کار را انجام می‌دهد. به طور کلی این الگو هر شیء را مجبور می‌سازد تا در زمان مواجه با رفتار‌های ناهنجار خود را پاکسازی کند.

    •  به طور کلی هنگامی که شما یک شیء را مقدار‌دهی اولیه می‌کنید، قبل از انجام آن باید منابع مورد نیاز آن را تأمین کنید (در سازنده).
    • هنگامی که یک شیء از محدوده‌ خارج می‌شود، هر منبعی را که مورد استفاده قرار داده است باید آزاد کند (در مخرب - ویرانگر).

    نکات کلیدی

    1.  هرگز نباید یک شیء به حالت نیمه آماده یا نیمه از بین‌ رفته وجود داشته باشد!
    2. وقتی که یک شیء ساخته می‌شود، آن شیء باید در حالت آماده باش برای استفاده باشد.
    3. وقتی یک شیء از محدوده خارج می‌شود، باید منابع اختصاص یافته‌ی خود را در حافظه آزاد کند (کاربر مجبور به انجام کار دیگری نیست).

    آیا RAII عنوان بدی برای مفهوم این تکنیک است!

    از نظر خالق سی‌پلاس‌پلاس نام بهتر می‌تواند به صورت زیر باشد:

    مدیریت منابع مبتنی بر حوزه (محدوده یا دامنه) : Scope Based Resource Management

    چیزی که تکنیک RAII را نقض می‌کند چیست؟

    اشاره‌گر‌های خام و تخصیص حافظه

    1. فراخوانی با کلمه‌ی کلیدی new برای دست آوردن یا اختصاص دادن منبع (حافظه).
    2. فراخوانی با کلمه‌ی کلیدی delete برای آزاد‌سازی منبع (حافظه).

    اما این مورد به صورت خودکار بعد از خروج از محدوده توسط اشاره‌گر‌ها صورت نمی‌گیرد.

    void rawPtrFn() {
    // acquire memory 
    resourceNode* n = newNode;
    // manually release memory
    delete n;
    }

    بنابراین در صورتی که برنامه‌نویس استفاده از کلمه‌ی کلیدی delete را برای آزاد‌سازی حافظه فراموش کند (نشتِ حافظه) رخ می‌دهد. بنابراین این عمل کافی است تا تکنیک RAII را نقض کنیم.

    void UseRawPointer()
    {
        // Using a raw pointer -- not recommended.
        Song* pSong = new Song(L"Nothing on You", L"Kambiz Asadzadeh"); 
    
        // Use pSong...
    
        // Don't forget to delete!
        delete pSong;   
    }

    بنابراین، راه حل RAII برای این امر در چیست؟

    کلاسی داشته باشید که :

    1. حافظه را هنگام مقدار‌دهی اولیه تخصیص دهد.
    2. حافظه را هنگام فراخوانی مخرب (ویرانگر) آزاد کند.
    3. دسترسی به اشاره‌گر‌های زیرین را امکان‌پذیر کند.

    اشاره‌گر‌های هوشمند (Smart Pointers)

    این ویژگی اساساً مدیریت حافظه‌ی خودکار را ارائه می‌دهد. زمانی که یک اشاره‌گر هوشمند دیگر استفاده نمی‌شود (زمانی که از محدوده‌ی خود خارج می‌شود) حافظه‌ی مورد نظر خود را به طور خودکار آزاد می‌کند.توجه داشته باشید که اشاره‌گر‌های سنتی با عنوان اشاره‌گر‌های خام (Raw Pointer) شناخته می‌شوند.

    اشاره‌گر‌های هوشمند را می‌تواند یک شکل کلی از GC در نظر گرفت؛ نوعی مدیریت خودکار وقتی که دیگر توسط برنامه مورد استفاده قرار نمی‌گیرند حافظه‌ی اختصاص یافته‌ی آن شیء به طور خودکار حذف می‌شود.

    در استاندارد ۱۱ سی‌پلاس‌پلاس سه نوع اشاره‌گر هوشمند معرفی شده است که همه‌ی آن‌ها در فایل سرآیند <memory> از کتابخانه‌ی استاندارد STL معرفی شده‌اند.

    • کلاس std::unique_ptr یک اشاره‌گر هوشمند که دارای یک منبع تخصیص حافظه‌ی پویا است.
    • کلاس std::shared_ptr شامل یک اشاره‌گری است که دارای یک منبع تخصیص حافظه‌ی پویا با تفاوت اینکه می‌تواند چندین شیء را به صورت اشتراکی از یک منبع مشترک ردیابی کند.
    • کلاس std::weak_ptr مانند std::shared_ptr است که شمارنده‌ی آن افزایش نمی‌یابد.

    به مثال زیر توجه کنید:

    {
      std::unique_ptr<int> p(new int);
      // شیء p قابل استفاده در داخل حوزه است.
    }
    // در این بخش که خارج از دامنه‌ی اشاره‌گر است حافظه‌ی اختصاص یافته آزاد می‌شود.

    همانطور که مشخص است یک شیء که تحت اشاره‌گر هوشمند مورد استفاده قرار می‌گیرد تا زمانی که خارج از حوزه‌ی خود قرار نگیرد قابل استفاده خواهد بود.

    نمونه کد پایین مثالی از نحوه‌ی نمونه سازی تحت اشاره‌گر‌های هوشمند است.

    void UseSmartPointer()
    {
        // Declare a smart pointer on stack and pass it the raw pointer.
        unique_ptr<Song> song2(new Song(L"Nothing on You", L"Kambiz Asadzadeh"));
    
        // Use song2...
        wstring s = song2->duration_;
        //...
    
    } // song2 is deleted automatically here.

    این مقاله در یک فرصت مناسب به به جزئیاتی بیشتری خواهد پرداخت...

    ویرایش شده در توسط کامبیز اسدزاده

    • پسندیدن 2
    • تشکر شده 1


    بازخورد کاربر

    نظرهای پیشنهاد شده

    هیچ دیدگاهی برای نمایش وجود دارد.



    مهمان
    از هم اکنون ارسال دیدگاه قفل گردید

  • کاربران آنلاین در این صفحه   0 کاربر

    هیچ کاربر عضوی،در حال مشاهده این صفحه نیست.

×
×
  • جدید...