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

Smart Pointers in C++ and How to Use Them

Smart-Pointers-in-CPP-and-How-to-Use-Them

سلام به همه‌ی کاربران روکسو. در این مقاله، به بررسی اشاره‌گر‌های هوشمند در زبان برنامه‌نویسی ++C خواهیم پرداخت. این‌که اشاره‌گر‌های هوشمند چیستند، چرایی استفاده و نحوه‌ی استفاده صحیح و درست از آن‌ها از مباحث این مقاله هستند.

اشاره‌گر‌ها برای دسترسی به منابعی که خارج از برنامه هستند، مانند حافظه‌ی heap، استفاده می‌شوند. بنابراین برای دسترسی به حافظه‌ی heap (اگر چیزی در داخل حافظه‌ی heap ایجاد شده باشد)، از اشاره‌گرها استفاده می‌شود. ما فقط یک کُپی از منبع استفاده می‌کنیم، زمانی که به هر منبع خارجی دسترسی داریم. اگر هر تغییری در آن منبع رخ دهد، درواقع تنها نسخه‌ی کپی‌شده‌ی آن را تغییر داده‌ایم. اما، اگر از یک اشاره‌گر برای منبع استفاده کنیم، می‌توانیم منبع اصلی را تغییر دهیم.

مشکلات اشاره‌گرها در حالت عادی

قطعه‌کُد زیر را در نظر داشته باشید:

#include <iostream>
using namespace std;
 
class Rectangle {
private:
    int length;
    int breadth;
};
 
void fun()
{
    // By taking a pointer p and
    // dynamically creating object
    // of class rectangle
    Rectangle* p = new Rectangle();
}
 
int main()
{
    // Infinite Loop
    while (1) {
        fun();
    }
}

در تابع fun، تابع، اشاره‌گری ایجاد خواهد کرد که به شیء Rectangle اشاره می‌کند. شی‌ء Rectangle شامل 2 متغیر از نوع عدد صحیح است، یکی length و دیگری breadth. زمانی که تابع fun به انتهای خودش می‌رسد، اشاره‌گر p که به عنوان متغیر محلی (Local variable) است، از بین خواهد رفت. اما، حافظه‌‌‌ای که اشاره‌گر p گرفته است، آزاد‌سازی نشده است! چرا که فراموش کرده‌ایم که از ;delete p در انتهای تابع fun استفاده کنیم. و به این معنی است که حافظه برای استفاده توسط دیگر منابع، آزاد نشده است. اما، ما متغیر دیگری نیاز نداریم، بلکه حافظه را نیاز خواهیم داشت.

در تابع main، تابع fun در یک حلقه‌ی بی‌نهایت فراخوانی شده است. که همان‌طور که واضح است، به طور مترب شیء p ایجاد می‌کند و حافظه‌ی بیشتر و بیشتر تخصیص می‌دهد و از آنجایی که ما آن‌ را آزاد‌سازی نکرده‌ایم، آن‌ها در حافظه آزاد‌سازی نمی‌شوند و به این صورت به جایی می‌رسد که حافظه دیگر نمی‌تواند مورد استفاده قرار گیرد و نَشت حافظه رُخ می‌دهد. به این دلیل که حافظه‌ی heap کاملا بی‌استفاده است.

در 11++C این مسئله با اشاره‌گر‌های هوشمند یا Smart Pointerها حل شده است.

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

همان‌‌طور که می‌دانیم، ناآگاهانه آزادسازی نکردن یک اشاره‌گر، باعث نشت حافظه خواهد شد که ممکن است منجر به هنگِ (crash) برنامه شود. زبان‌های مانند #Java، C مکانیزم زباله‌روب (Garbage Collection) را دارند که به طور هوشمند حافظه‌ی بلااستفاده را آزاد می‌کنند، تا حافظه مجددا مورد استفاده قرار گیرد.  برنامه‌نویس نباید نگران نشت حافظه باشد. 11++C با مکانیزم خودش که اشاره‌گر‌های هوشمند است، زمانی که شیء از بین می‌رود، اشاره‌گر‌های هوشمند هم حافظه را آزاد می‌کنند. بنابراین،‌ از آنجایی که اشاره‌گر‌های هوشمند شیء را مدیریت می‌کنند، دیگر نباید از delete برای آزاد‌سازی حافظه استفاده کنیم.

یک اشاره‌گر هوشمند یک کلاس اشاره‌گر است با یک اُپریتور مانند * و <-. اشیاء کلاس‌ اشاره‌گر هوشمند مانند اشاره‌گر‌های معمولی هستند. اما، برخلاف اشاره‌گرهای عادی، اشاره‌گر‌های هوشمند می‌توانند حافظه را از شیء از بین رفته، آزاد کنند.

می‌خواهیم کلاسی همراه با یک اشاره‌گر، تخریب‌گر‌ (destructor) و اُپریتور‌های سربارگذاری مانند * و <-، ایجاد کنیم. از آن‌جایی که تخریب‌گر، هنگامی که یک شیء از محدوده خارج می‌شود، به طور خودکار فراخوانی می‌شود، حافظه‌ی اختصاص داده‌شده به طور پویا (the dynamically allocated memory) به طور خودکار آزاد خواهد شد.

کلاس SmartPtr  زیر را که به طور ساده نوشته شده را در نظر بیگیرید:

#include <iostream>
using namespace std;
 
class SmartPtr {
    int* ptr; // Actual pointer
public:
    // Constructor: Refer https:// www.geeksforgeeks.org/g-fact-93/
    // for use of explicit keyword
    explicit SmartPtr(int* p = NULL) { ptr = p; }
 
    // Destructor
    ~SmartPtr() { delete (ptr); }
 
    // Overloading dereferencing operator
    int& operator*() { return *ptr; }
};
 
int main()
{
    SmartPtr ptr(new int());
    *ptr = 20;
    cout << *ptr;
 
    // We don't need to call delete ptr: when the object
    // ptr goes out of scope, the destructor for it is automatically
    // called and destructor does delete ptr.
 
    return 0;
}

خروجی قطعه‌کُد بالا:

20

قطعه‌کُد بالا،‌ تنها برای نوع int کار خواهد کرد. بنابراین سوالی که پیش می‌آید، این است که آیا باید برای هر شیء اشاره‌گر‌های هوشمند ایجاد کرد؟ خیر، راه‌حل آن Template‌ها هستند. در قطعه‌کُد زیر همان‌طور که می‌توانید ببیند، T می‌تواند هر نوعی باشد.

#include <iostream>
using namespace std;
 
// A generic smart pointer class
template <class T>
class SmartPtr {
    T* ptr; // Actual pointer
public:
    // Constructor
    explicit SmartPtr(T* p = NULL) { ptr = p; }
 
    // Destructor
    ~SmartPtr() { delete (ptr); }
 
    // Overloading dereferencing operator
    T& operator*() { return *ptr; }
 
    // Overloading arrow operator so that
    // members of T can be accessed
    // like a pointer (useful if T represents
    // a class or struct or union type)
    T* operator->() { return ptr; }
};
 
int main()
{
    SmartPtr<int> ptr(new int());
    *ptr = 20;
    cout << *ptr;
    return 0;
}

خروجی قطعه‌کُد بالا:

20

نکته: اشاره‌گر‌های هوشمند، در مدیریت منابعی مانند مدیریت فایل یا سوکت‌های شبکه هم مفید هستند.

انواع اشاره‌گر‌های هوشمند

unique_ptr

این اشاره‌گر هوشمند، تنهای یک اشاره‌گر را ذخیره می‌کند. می‌توانیم یک شیء دیگر را با حذف کردن شی‌ء فعلی از اشاره‌گر، به آن اختصاص دهیم. به کُد زیر دقت کنید. ابتدا، unique_pointer به P1 اشاره می‌کند. اما بعد، P1 حذف شده و P2 به آن اختصاص داده شده، و به این ترتیب، الان، اشاره‌گر به P2 اشاره می‌کند.

unique_ptr

#include <iostream>
using namespace std;
#include <memory>
 
class Rectangle {
    int length;
    int breadth;
 
public:
    Rectangle(int l, int b){
        length = l;
        breadth = b;
    }
 
    int area(){
        return length * breadth;
    }
};
 
int main(){
 
    unique_ptr<Rectangle> P1(new Rectangle(10, 5));
    cout << P1->area() << endl; // This'll print 50
 
    // unique_ptr<Rectangle> P2(P1);
    unique_ptr<Rectangle> P2;
    P2 = move(P1);
 
    // This'll print 50
    cout << P2->area() << endl;
 
    // cout<<P1->area()<<endl;
    return 0;
}

خروجی قطعه‌کُد بالا:

50
50

shared_ptr

با استفاده از این اشاره‌گر، می‌توان با بیش‌از یک اشاره‌گر به یک شیء در یک لحظه اشاره کرد و این اشاره‌گر یک شمارنده‌ی مرجع (Reference Counter) با استفاده از متد ()use_count را نگهِ می‌دارد.

shared_ptr

#include <iostream>
using namespace std;
#include <memory>
 
class Rectangle {
    int length;
    int breadth;
 
public:
    Rectangle(int l, int b)
    {
        length = l;
        breadth = b;
    }
 
    int area()
    {
        return length * breadth;
    }
};
 
int main()
{
 
    shared_ptr<Rectangle> P1(new Rectangle(10, 5));
    // This'll print 50
    cout << P1->area() << endl;
 
    shared_ptr<Rectangle> P2;
    P2 = P1;
 
    // This'll print 50
    cout << P2->area() << endl;
 
    // This'll now not give an error,
    cout << P1->area() << endl;
 
    // This'll also print 50 now
    // This'll print 2 as Reference Counter is 2
    cout << P1.use_count() << endl;
    return 0;
}

خروجی قطعه‌کُد بالا:

50
50
50
2

weak_ptr

این اشاره‌گر هوشمند بسیار شبیه به اشاره‌گر هوشمند shared_ptr است، به جزء اینکه این اشاره‌گر هوشمند، یک شمارنده‌ی مرج نگه نمی‌دارد. در این مورد، اشاره‌گر بر روی شیء مستحکم نیست. دلیلش این است که اگر فرض کنیم که اشاره‌گر‌ها شیء را نگه‌ داشته‌اند، و برای دیگر اشیاء  در حال درخواست هستند، ممکن است بن‌بست (DeadLock) رخُ دهد.

weak_ptr


منبع: وب سایت geeksforgeeks

نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.