فصل ۱۱: اشارهگرهای هوشمند (Smart Pointers) در C++

۱. مقدمه: چرا به اشارهگرهای هوشمند نیاز داریم؟
در فصل قبل دیدیم که اگر مدیریت حافظه پویا را خودمان با new و delete انجام بدهیم، احتمال خطاهایی مثل:
- Memory Leak (نشت حافظه)
- Dangling Pointer (اشاره به آدرس آزاد شده)
- Double Delete (آزاد کردن دو بار یک حافظه)
وجود دارد.
در برنامههای بزرگ یا پیچیده، مدیریت دستی حافظه دشوار و خطرناک است. برای حل این مشکل، زبان C++ از نسخهی C++11 به بعد اشارهگرهای هوشمند را معرفی کرد.
۲. اشارهگر هوشمند چیست؟
تعریف ساده: اشارهگر هوشمند یک شیء از کلاس کتابخانه استاندارد است که درست مثل اشارهگر معمولی به داده اشاره میکند، اما آزاد کردن حافظه را خودش بهطور خودکار انجام میدهد.
یعنی:
- وقتی اشارهگر از محدوده (Scope) خارج شود، خودش حافظه را آزاد میکند.
- به مدیریت حافظه هوشمندانه کمک میکند.
- از بسیاری باگها جلوگیری میکند.
کتابخانه <memory> این کلاسها را فراهم میکند.
۳. انواع اشارهگرهای هوشمند
در STL سه نوع اصلی داریم:
std::unique_ptr– مالکیت یکتا (فقط یک اشارهگر میتواند مالک حافظه باشد)std::shared_ptr– مالکیت اشتراکی (چند اشارهگر میتوانند یک حافظه را به اشتراک بگذارند)std::weak_ptr– اشارهگر ضعیف (برای جلوگیری از حلقههای مالکیت درshared_ptr)
۴. unique_ptr – مالکیت یکتا
ویژگیها:
- فقط یک
unique_ptrمیتواند حافظه را مدیریت کند. - وقتی از محدوده خارج میشود، حافظه را آزاد میکند.
- قابل کپی کردن نیست، اما میتوان مالکیت را منتقل (move) کرد.
مثال:
#include <iostream>
#include <memory> // برای unique_ptr
using namespace std;
int main() {
unique_ptr<int> p1(new int(42));
cout << *p1 << endl;
// انتقال مالکیت
unique_ptr<int> p2 = move(p1);
if (!p1)
cout << "p1 دیگر مالک حافظه نیست" << endl;
cout << *p2 << endl;
}
توضیح:
new int(42)حافظه میگیرد.p1مالک است.- با
move(p1)مالکیت بهp2منتقل میشود. - وقتی
p2از محدوده خارج شود، حافظه آزاد میشود.
۵. shared_ptr – مالکیت اشتراکی
ویژگیها:
- چند
shared_ptrمیتوانند به یک حافظه اشاره کنند. - حافظه وقتی آزاد میشود که آخرین
shared_ptrاز بین برود. - شمارندهی مرجع (Reference Count) دارد.
مثال:
#include <iostream>
#include <memory>
using namespace std;
int main() {
shared_ptr<int> sp1(new int(5));
shared_ptr<int> sp2 = sp1; // اشتراک مالکیت
cout << "sp1: " << *sp1 << endl;
cout << "sp2: " << *sp2 << endl;
cout << "تعداد مالکان: " << sp1.use_count() << endl;
}
خروجی:
sp1: 5
sp2: 5
تعداد مالکان: 2
۶. weak_ptr – اشارهگر ضعیف
ویژگیها:
- به حافظهای که توسط
shared_ptrمدیریت میشود اشاره میکند، اما مالک نیست. - شمارنده مرجع را افزایش نمیدهد.
- برای جلوگیری از حلقههای بیپایان (Circular Reference) در
shared_ptrاستفاده میشود. - قبل از استفاده باید بررسی شود که حافظه هنوز معتبر است.
مثال:
#include <iostream>
#include <memory>
using namespace std;
int main() {
shared_ptr<int> sp = make_shared<int>(10);
weak_ptr<int> wp = sp;
cout << "تعداد مالکان: " << sp.use_count() << endl;
if (auto temp = wp.lock()) {
cout << "Value: " << *temp << endl;
} else {
cout << "حافظه آزاد شده است" << endl;
}
}
۷. چرا make_unique و make_shared؟
استفاده از تابعهای کمکی:
- امنتر هستند.
- احتمال خطا کمتر است.
- سریعتر و بهینهتر مدیریت میشوند.
مثال:
auto ptr = make_unique<int>(25);
auto sp = make_shared<string>("Hello");
۸. مثال عملی کامل
مدیریت یک کلاس با اشارهگر هوشمند:
#include <iostream>
#include <memory>
using namespace std;
class Person {
public:
string name;
Person(string n) : name(n) {
cout << "ساخت شخص: " << name << endl;
}
~Person() {
cout << "حذف شخص: " << name << endl;
}
};
int main() {
{
unique_ptr<Person> p1 = make_unique<Person>("Ali");
cout << p1->name << endl;
} // اینجا پ1 از محدوده خارج میشود و حافظه آزاد میگردد
{
shared_ptr<Person> sp1 = make_shared<Person>("Sara");
shared_ptr<Person> sp2 = sp1;
cout << "تعداد مالکان: " << sp1.use_count() << endl;
} // وقتی آخرین shared_ptr از بین برود، حافظه آزاد میشود
}
۹. نکات و بهترین شیوهها
- همیشه
make_uniqueوmake_sharedرا بهnewترجیح دهید. - از
unique_ptrبرای مالکیت یکتا استفاده کنید؛ سریعتر ازshared_ptrاست. - از
weak_ptrبرای شکستن حلقههای مالکیت استفاده کنید. - نیازی به
deleteدستی نیست، چون مدیریت توسط کلاس انجام میشود. - از اشارهگرهای خام (
raw pointers) فقط وقتی استفاده کنید که همیشه معلوم است عمر داده دقیقاً توسط دیگری مدیریت میشود.
۱۰. تمرینها
- یک برنامه بنویسید که یک کلاس ساده با سازنده و مخرب داشته باشد و آن را با
unique_ptrمدیریت کند. - برنامهای بسازید که چند
shared_ptrبه یک کلاس بدهد و تعداد مالکان را چاپ کند. - مثال حلقه مالکیت در
shared_ptrایجاد کنید و باweak_ptrآن را بشکنید. - با
make_uniqueوmake_sharedچند شیء ایجاد و مقادیر آنها را تغییر دهید.
۱۱. جمعبندی
- اشارهگرهای هوشمند ابزار ایمن و مدرن مدیریت حافظه در C++ هستند.
- با خارج شدن اشارهگر از محدوده، حافظه آزاد میشود.
- استفاده از آنها به جای
newوdeleteدستی باعث کم شدن باگها و مدیریت بهتر منابع میشود. - در فصلهای بعد، با کاربردهای این اشارهگرها در پروژههای واقعی آشنا خواهیم شد.