اشاره گر های هوشمند: shared_ptr

توی پست قبل درباره اینکه چرا باید از اشاره گر های هوشمند استفاده کنیم حرف زدم و کلاس std::unique_ptr رو بررسی سطحی کردیم. توی این پست قراره درباره نوع دیگه ای از اشاره گر های هوشمند بنویسم که برعکس unique_ptr به ما اجازه میده چند شئ بتونن یک حافظه یکسان رو مدیریت کنن.

ادامه خواندن “اشاره گر های هوشمند: shared_ptr”

اشاره گر های هوشمند: unique_ptr

موضوع اشاره گر ها همیشه یکی از چالش های زبان هایی مثل سی و سی++ بوده. اینکه یه حافظه ای رو از سیستم بگیریم، آزادش کنیم و مراقب باشیم که حافظه ای که گرفته شده معتبر باشه تا برای استفاده کردن ازش یا آزاد کردنش مشکلی پیش نیاد و هزارتا چیز دیگه.

اشاره گر های هوشمند اومدن تا کار مارو راحت کنن. تا دیگه کمتر ذهن برنامه نویس درگیر امنیت اشاره گر ها و مسائل دیگه باشه.

بنابراین یکی از ارکان سی++ مدرن میتونه استفاده از اشاره‌گر های هوشمند باشه.

توی این پست راجع به کلاس unique_ptr و make_unique می‌نویسم که قبلا یه اشاره های ریزی بهش کرده بودم.

ادامه خواندن “اشاره گر های هوشمند: unique_ptr”

بررسی کانال لیبیدوئیسم

دیشب از یکی از دوستان خیلی خوبم پرسیدم که «عدد ۳۱۷ که توی صفحه پروفایلت نوشتی یعنی چی؟» گفت «من لیبیدوئیست ام».

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

شروع کردم به اینکه از اول همه مطالب رو بخونم. خیلی زیاد به نظر میان و من سعی می‌کنم فقط مهماش رو بخونم.

اولین چیزی که با خوندن مطالب اولیه‌ش به ذهن خطور میکنه که این یکی از همین کانال های چگونه موفق شویم و «روز خود را با من می‌توانم شروع کنید» و این چیزاست. بیشترش مطالب انگیزشی و غیرتخصصی درباره روانشناسی و موفقیته.

ادامه خواندن “بررسی کانال لیبیدوئیسم”

ارث بری چندگانه در سی++

سی++ یکی از زبان هاییه که این اجازه رو به ما میده تا کلاسی که نوشتیم چندین کلاس والد داشته باشه یا به عبارت دیگه، قابلیت ارث بری چندگانه یا multiple inheritance رو داره.

چطور استفاده می‌شه

خودِ ارث بری چندتایی خیلی عجیب غریب نیست و تنها فرقش اینه که موقع نوشتن کلاس، با استفاده از «کاما» میایم و Base Class هارو جدا می‌کنیم. مثال:

#include <string>
 
class Person
{
private:
    std::string m_name;
    int m_age;
 
public:
    Person(std::string name, int age)
        : m_name(name), m_age(age)
    {
    }
 
    std::string getName() { return m_name; }
    int getAge() { return m_age; }
};
 
class Employee
{
private:
    std::string m_employer;
    double m_wage;
 
public:
    Employee(std::string employer, double wage)
        : m_employer(employer), m_wage(wage)
    {
    }
 
    std::string getEmployer() { return m_employer; }
    double getWage() { return m_wage; }
};
 
// Teacher publicly inherits Person and Employee
class Teacher: public Person, public Employee
{
private:
     int m_teachesGrade;
 
public:
    Teacher(std::string name, int age, std::string employer, double wage, int teachesGrade)
        : Person(name, age), Employee(employer, wage), m_teachesGrade(teachesGrade)
    {
    }
};

اما بیشتر از مزایاش، چالش ها و مشکلاتش قابل بحثه!

مشکل Diamond Problem

این مشکل که به Deadly Diamond of Death هم معروفه، از این قراره که فرض کنید کلاس های B و C دو کلاس مجزا باشند و هر دوتاشون از کلاس A ارث بری کرده باشند. حالا اگر ما کلاسی مثل D داشته باشیم که به شکل همزمان از B و C ارث بری کرده، دوبار کلاس A رو در کلاس D خواهیم داشت که این باعث مشکل می‌شه.

نمایش تصویری Diamond problem. حالا دلیل اسمش معلوم شد؟ 🙂

اگر تابعی مثل print در کلاس A داشته باشیم که در هیچکدوم از ارث بری ها بازنویسی نشده باشه و حالا از طریق کلاس D بخواد فراخوانی بشه چه اتفاقی میوفته؟

به ارور برمیخوریم! چون کامپایلر نمیدونه از کدوم یکی از نسخه های A که الآن در اختیار داره باید استفاده کنه و از کدوم مسیر باید بره.

از طریق کلاس C بره و به A برسه یا از طریق کلاس B ؟

حل مسئه Diamond problem

راه حل اول اینه که دقیقا برای کامپایلر مشخص کنیم از چه مسیری باید به کلاس A برسه. مثال:

class A {
	public:
		print() {}
};

class B : public A {};
class C : public A {};

// multiple inheritance 

class D : public B, public C {};

D object;
// explicitly determine a way to "A"
D.B::print();

یک راه دیگه هم وجود داره.

Virtual Inheritance

با استفاده از ارث بری مجازی، سی++ فقط یک نسخه از A رو به عنوان والد در نظر میگیره و نمیذاره چند نسخه از A بوجود بیاد.

فقط کافیه که ارث بری‌مون رو به شکل virtual انجام بدیم:

class A {
	public:
		virtual print() {}
};

class B : virtual public A {};
class C : virtual public A {};

// multiple inheritance 

class D : public B, public C {};

D object;
// no need to explicit qualification
D.print();

نحوه عمل کردن virtual inheritance بسیار شبیه به Virtual function ها هست که توی این پست درباره‌ش توضیح دادم.

در آخر

همونطور که دیدیم، ظاهرا چالش ها و مشکلات این روش بیشتر از فایده‌ش هست اما جاهایی هم هست که بهترین راهیه که میتونیم ازش استفاده کنیم.

بهتره که تا جای ممکن سعی کنیم راه الگوریتم‌مون رو بدون استفاده از ارث بری چندگانه پیاده سازی بکنیم مگر اینکه پیاده سازی به این روش باعث بشه که پیچیدگی کار کمتر بشه.

Delegating Constructors

قبلا وقتی داشتم یه کلاسی می‌نوشتم که چنتا کانستراکتور داشت و اون کانستراکتور ها فقط در بخش کوچیکی از کارها باهم تفاوت داشتن، میومدم و کدهارو چندبار کپی می‌کردم.

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

نمی‌دونستم که سی++ یه قابلیتی داره به اسم constructor delegation

فرض کنیم همچین کدی داریم:

class Foo
{
public:
    Foo()
    {
        // code to do A
    }
 
    Foo(int value)
    {
        // code to do A
        // code to do B
    }
};

همونطور که می‌بینیم، کد کانستراکتور اولی توی کانستراکتور دومی کپی شده. برای اینکه از اینکار جلوگیری کنیم، از قابلیت delegate کردن کانستراکتور ها استفاده می‌کنیم و کد ما این شکلی می‌شه:

class Foo
{
private:
 
public:
    Foo()
    {
        // code to do A
    }
 
    Foo(int value): Foo{} // use Foo() default constructor to do A
    {
        // code to do B
    }
 
};

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

نکات

  • امکان اینکه کانستراکتور A یک delegate برای کانستراکتور B باشه و همین کانستراکتور B یک delegate برای کانستراکتور A باشه وجود داره. این باعث میشه که برنامه شما کرش کنه. پس باید مراقب باشیم که حلقه بی نهایت پیش نیاد.
  • کانستراکتوری که delegator هست نمیتونه عمل member initialization رو انجام بده. در حالت کلی، یک کانستراکتور نمیتونه همزمان هم initializer باشه و هم delegator.

معرفی std::string_view

یکی از ویژگی های جالبی که به سی++ ۱۷ اضافه شده، std::string_view هست که برای کار با رشته های ثابت خیلی زیاد به کار آدم میاد.

کد پایین رو ببینید:

#include <iostream>
#include <string>
 
int main()
{
  char text[]{ "hello" };
  std::string str{ text };
  std::string more{ str };
 
  std::cout << text << ' ' << str << ' ' << more << '\n';
 
  return 0;
}

توی این کد، با اینکه تنها کاری که ما کردیم چاپ کردن رشته بوده و رشته هم ثابت بوده، اما برای همین استفاده ساده ۴ بار از اون رشته کپی گرفته شده.

یک بار رشته ثابت توی کد باینری قرار گرفته. بعد کپی شده توی استک (برای text)، و دو بار هم std::string ها کپیش کردن برای ساختن شئ خودشون.

سی++ ۱۷ قابلیتی به اسم string_view اضافه کرده که برای همینکار درست شده 🙂

کد پایین رو ببینید:

#include <iostream>
#include <string_view>
 
int main()
{
  std::string_view text{ "hello" }; // view the text "hello", which is stored in the binary
  std::string_view str{ text }; // view of the same "hello"
  std::string_view more{ str }; // view of the same "hello"
 
  std::cout << text << ' ' << str << ' ' << more << '\n';
 
  return 0;
}

توی این کد رشته ما فقط یکبار کپی شده (و اونم توی باینری) و بقیه متغییر های ما به نوعی دارن به همون رشته اصلی اشاره می‌کنن.

و نکته مثبتش؟ اینکه خیلی از توابع مربوط به std::string رو هم ساپورت می‌کنه.

تغییر دادن رشته

اساسا این کلاس برای رشته های read-only ساخته شده و عملا نمیتونه تغییری توی رشته اصلی انجام بده. همونطور که از اسمش معلومه فقط یک view از رشته هست.

یک پنجره رو فرض کنید، شما از طریق پنجره به منظره بیرون نگاه می‌کنید. وقتی پرده رو می‌کشید دید شما محدود میشه اما در منظره بیرون تغییری ایجاد نمیشه. اما اگر منظره بیرون تغییر کنه، دید شما هم تغییر میکنه.

این مثال دقیقا برای این کلاس کاربرد داره. اگر رشته اصلی ای که string_view باهاش ساخته شده تغییر بکنه، رشتهٔ ای که string_view داره نمایش میده هم تغییر می‌کنه.

اگر رشته ای که string_view باهاش ساخته شده از بین بره، دیگه نمیشه از اون string_view استفاده کرد و فراخوانی کردنش یک undefined behaviour هست.

مثال:

#include <iostream>
#include <string_view>
 
int main()
{
  char arr[]{ "Gold" };
  std::string_view str{ arr };
 
  std::cout << str << '\n'; // Gold
 
  // Change 'd' to 'f' in arr
  arr[3] = 'f';
 
  std::cout << str << '\n'; // Golf
 
  return 0;
}

در پایان

  • از این کلاس برای رشته هایی که قرار نیست تغییر بکنن استفاده کنیم.
  • برای رشته هایی که قراره تغییر بکنن از همون std::string استفاده کنیم.

فصل ۲۲ دایتل: نکات اضافه

توی این فصل درمورد struct ها، bit field ها، اوپراتور های بیتی (bitwise) و توابع مربوط به دستکاری رشته های در سی صحبت شده.

البته دو مورد آخر رو من بلد بودم بنابراین توی این پست دربارشون چیزی نمی‌نویسم. راستی، این آخرین فصل کتاب فیزیکیه 🙂

structure ها

نکته جالب اینکه struct ها تقریبا همون کلاس ها هستند و تنها تفاوت‌شون اینه که اعضای یک struct بر خلاف کلاس، به صورت پیش‌فرض public هست و همچنین نوع ارث بری به صورت پیش‌فرض public هست(که در کلاس ها private هست).

typedef و using

همونطور که از قبل می‌دونستیم، از typedef برای تعریف کردن alias برای انواع داده هامون استفاده می‌کردیم. مثلا:

typdef char* string;

توی سی++ ۱۱ قابلیتی اضافه شده که میشه اینکار رو با استفاده از using انجام داد و عبارت پایین برابر با همون typedef عمل می‌کنه:

using string = char*;

Binary Literals ها

در سی++‌۱۴ میتونیم از Binary Literal ها استفاده بکنیم.

برای اینکار کافیه که پشت عبارتمون، کاراکتر 0b یا 0B رو قرار بدیم:

const unsigned binary{0b1000000'00000000'00000000'00000000};

Bit Field ها

‌سی++‌ این امکان رو به ما میده که تعیین کنیم یک عضو خاص(از نوع int یا enum) یک کلاس یا یک struct چقدر از حافظه رو اشغال بکنه(تعداد بیت ها). به عضوی که این قابلیت براش استفاده شده باشه میگن Bit Field.

struct BitCard {
unsigned face : 4;
unsigned suit : 2;
unsigned color : 1;
};

با توجه به مثال بالا، اندازهٔ حافظه ای(تعداد بیت ها) که میخوایم عضو ما داشته باشه رو با یک «:» جدا می‌کنیم.

اصطلاحا به اندازه ای که تعیین می‌کنیم میگن width of the bit field.

unnamed bit field

سی++ این امکان رو به ما میده که عضوی تعریف بکنیم که به عنوان padding استفاده بشه. این عضو هیچ اسمی نداره و صرفا به تعداد بیت ای که مشخص می‌کنیم، حافظه رو رزرو می‌کنه و اون بخش از حافظه قابل استفاده نیست.

struct Example {
unsigned a : 13;
unsigned : 3; // align to next storage-unit boundary
unsigned b : 4;
};

توی مثال بالا تعیین کردیم که ۳ بیت از حافظه رزرو بشه.

این رزرو کردن به یک شکل دیگه هم میتونه نوشته بشه و اون هم اینکه تعداد بیت های unnamed رو برابر با صفر قرار بدیم.

در این حالت خود کامپایلر میاد و هرچقدر که فضا در اون واحد حافظه باقی مونده رو رزرو می‌کنه. عضو بعدی ای که تعریف می‌کنیم در یک واحد دیگه قرار می‌گیره.

struct Example {
unsigned a : 13;
unsigned : 0; // align to next storage-unit boundary
unsigned b : 4;
};

نکات بیت فیلد ها

  • نباید با استفاده از اوپراتور & سعی کنیم که آدرس یک بیت فیلد رو بگیریم.
  • استفاده از بیت فیلد ها باعث میشه که کامپایلر کد کندتری رو تولید بکنه. بخاطر اینکه محاسبه های بیشتری برای پیدا کردن آدرس های حافظه باید انجام بده.

پایان

خب این فصل هم به پایان رسید و رسما فصل های موجود در کتاب pdf «آموزش برنامه نویسی سی++ دایتل» تموم شد. از این به بعد نکات اضافی ای که از منابع دیگه یاد میگیرم رو در پست های جدا می‌نویسم. کتاب جالبی بود 🙂

فصل ۲۱ دایتل: رشته ها

توی این فصل در مبحث رشته ها در سی++ بیشتر عمیق میشیم. درواقع کلاس string از STL رو بررسی می‌کنیم.

پست کوتاهیه چون بیشتر چیزها رو بلد بودم 🙂

تابع compare

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

اینطوری:

string str1{"This is string"};
string str2{"oh This string"};
if (str1.compare(0, 3, str2, 3, 5)) {
// do something
}

تابع replace و نکته‌ش

تابع replace میاد و از مکانی که براش مشخص می‌کنیم میگرده و مقدار مشخص شده رو پیدا می‌کنه و رشتهٔ جایگزین رو بجاش می‌ذاره.

نکته‌ش اینه که اگر مثلا شما دنبال یک رشته بگردید که ۲ کاراکتر داره و بخواین این زیر رشته رو با یک رشته که ۴ کاراکتر داره جایگزین کنید، ۲ کاراکتر بعد از رشته مورد جستجوی ما از بین میره.

الآن حوصله نوشتن مثال ندارم. امیدوارم گنگ نگفته باشم 🙂

برای اینکه به این مشکل برنخوریم بهتره که اول موقعیت رخداد رشته مورد جستجو رو با استفاده از تابع find پیدا بکنیم و بعد با استفاده از تابع insert رشته مورد نظرمون رو بذاریم بجاش.

تبدیل رشته به عدد

توابعی که برای اینکار وجود دارن اینا هستن:

توابع مربوط به تبدیل رشته به عدد

تابع هایی که می‌بینیم درواقع ۳ تا ورودی دارن(به جز تابع های مربوط به اعداد اعشاری که ورودی سوم رو ندارن) که دوتای آخرشون default دارن.

  • ورودی اول: رشته ای که میخوایم به عدد تبدیل بشه
  • یک اشاره گر به size_t برای ذخیره کردن اولین اندیسی که این تابع قادر به تبدیلش نبوده
  • یک عدد صحیح بین ۲ تا ۳۶ که مبنای عدد رو مشخص می‌کنه.

به عنوان مثال فرض کنیم str یک رشته است که مقدار “123” در اون ذخیره شده. اینطور میشه به int تبدیلش کرد:

int convertedInt = stoi(str, nullptr, 10);

دوتا آرگومان آخر دلخواه هستند و میشه اونهارو ننوشت.

پایان

خیلی خوبه که بیشتر نکات این چندفصل آخر رو بلد بودم و دارم سریع پیش میرم 🙂 باعث میشه اعتماد به نفس بهتری داشته باشم.

فصل ۱۹ دایتل: نکاتی درباره پیاده سازی ساختمان داده ها

این فصل از دایتل درباره پیاده سازی ساختمان داده های مرسوم مثل لیست پیوندی، صف، استک و درخت دودویی با استفاده از تمپلیت ها بود. چون من این چیز ها رو از قبل بلد بودم بنابراین این پست بسیار کوتاه بود چون همونطور که میدونیم، من فقط چیزهایی رو می‌نویسم که به نظرم جدیدن.

کلاس Self-Referential

کلاس های خود ارجاعی به کلاسی گفته میشه که یک data member داشته باشه که به یک شئ از جنس خود کلاس اشاره میکنه.

نام های وابسته و غیر وابسته (dependent names vs non-dependent)

به کد زیر توجه کنید:

توابعی که توی این کد استفاده شده در کلاس List تعریف شدن.

همونطور که می‌بینید، کلاس Stack از کلاس List که یک class template هست ارث بری کرده و همونطور که می‌دونیم، تمپلیت ها درواقع همون function overloading هستند که وقتی type رو براشون مشخص می‌کنیم، کامپایلر با اون type مشخص شده کد رو تولید می‌کنه.

خط ۱۳ و خط ۱۸ نمونهٔ اسم های وابسته هستن.

یعنی چی؟ یعنی اینکه تا وقتی نوع STACKTYPE مشخص نشده باشه، کد کلاس List تولید نشده و در نتیجه توابع ذکر شده هم تولید نشدن و کامپایلر این رو می‌فهمه(چون می‌بینه در ورودی‌شون یه STACKTYPE دارن).

خط ۲۳ و ۲۸ نمونه اسم های غیر وابسته یا non-dependent هستن.

این تابع ها(مثل isEmpty و print) چون هیچ ورودی ای ندارن، پس وابسته به type نیستند و کامپایلر وقتی به خطی می‌رسه که داره تابع print رو صدا می‌زنه،‌ گمون می‌کنه که این یک تابع معمولیه و کدش موجوده(درحالی که این تابع جزئی از کلاس List هست و تا مشخص شدن STACKTYPE کدی براش تولید نمیشه).

این مسئله باعث میشه ارور بوجود بیاد.

برای اینکه به کامپایلر بفهمونیم این توابع نباید در زمان دیده شدن resolve بشن و resolve شدن‌شون رو باید به بعد از تولید کد template موکول کرد، از کلمه this استفاده می‌کنیم.

پایان

فصل بعدی درباره الگوریتم های مرتب سازی (sort) و جستجو (search) هست.

فصل ۱۷ دایتل: نگاهی عمیق تر به Exception Handling

توی این فصل قراره نکات عمیق تری رو راجع به مدیریت استثنا ها یاد بگیریم بنابراین مفاهیم اولیه مثل اینکه exception چی هست و چرا باید استفاده بشه و چطور میشه یک استثنا برای خودمون بنویسیم رو ذکر نمی‌کنم.

یادآوری

نکته اول اینکه همیشه باید سعی کنیم توی بلوک catch، تایپ مربوط به exception رو به صورت رفرنس بگیریم چراکه اولا از کپی شدن آبجکت اکسپشن جلوگیری میکنه و دوم اینکه باعث میشه اگر اکسپشن ما از stdexcept ارث بری شده، بتونه به درستی اجرا بشه.

یه بلاک try میتونه چندین بلاک catch رو بعد از خودش داشته باشه که هرکدوم یک استثنا خاصی رو هندل میکنن.

دو نوع مدل برای هندل کردن استثنا داریم:

termination model of exception handling

توی این مدل(که زبان سی++ از این مدل استفاده می‌کنه) وقتی داخل try یک اکسپشن پرت میشه(throw)، در همون نقطه از بلاک try بیرون میاد(اصطلاحا بهش میگن throw point) و بعد از پیدا کردن بلاک catch مورد نظرش میره و خطی که بعد از catch هست رو اجرا میکنه. نه خطی که بعد از throw point هست.

resumption model of exception handling

این مدل برعکس مدل بالاست و وقتی کار بلوک catch تموم میشه، برمیگرده و از ادامهٔ throw point یا همون نقطه پرتاب کد هارو اجرا می‌کنه.

با اینکه کلمهٔ throw میتونه هر چیزی رو پرت بکنه(مثل return) اما بهتره که فقط exception object رو پرت کنیم.

پرت کردن دوباره یک استثنا یا rethrowing exception

گاهی ممکنه وضعیتی پیش بیاد که نیاز باشه یک استثنا رخ داده شده رو چندبار پردازش کنیم. به عنوان مثال فرض کنید در یک تابع چند حافظه رو new کردیم و بعد از تخصیص حافظه و در جایی از این تابع یک exception پرت بشه. اینجا ما میخوایم هم حافظه ای که گرفته شده رو delete کنیم و هم به تابع صدا زننده‌مون اطلاع بدیم که در این تابع یک استثنا رخ داده.

برای اینکه اینکار انجام بشه کافیه در بلوک catch ای که داریم یکبار دیگه throw کنیم تا به تابع صدا زننده بره. کد زیر کاملا شفافه و با خوندنش میتونیم بفهمیم دقیقا منظور از rethrowing exception چیه.

مثال برای پرت کردن دوبارهٔ یک استثنا

Stack unwinding

وقتی یک استثنا پرتاب میشه، برنامه به دنبال یک بلوک catch می‌گرده که متناسب با استثنا پرتاب شده باشه. اگر در جایی(تابعی) که قرار داره نتونه یک catch رو پیدا بکنه، اون تابع رو terminate می‌کنه و میره به جایی که تابع ما داخلش صدا زده شده. اگر اونجا هم بلوک catch ای وجود نداشت که متناسب با استثنا پرت شده بود، باز هم تابع رو terminate میکنه و میره به تابع صدا زننده‌ش و این کار تا موقعی که بتونه یک catch رو پیدا کنه ادامه داره.

اگر هیچ catch متناسبی پیدا نشه در نهایت برنامه بسته میشه.

به این فرآیند میگن stack unwinding چراکه function stack رو پیمایش می‌کنه و دونه دونه به سمت تابع بیرونی حرکت می‌کنه.

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

کلمه noexcept

اگر تابعی داشته باشیم که به هیچ عنوان استثنا ای رو پرت نمی‌کنه یا توابعی رو صدا میزنه که اونها هم استثنا ای رو پرت نمی‌کنن، میتونیم صراحتا به عنوان تابعی که استثنا نداره تعریفش کنیم:

int functionWithoutException() noexcept;

اینکار به کسای دیگه (و حتی خودمون) کمک میکنه که وقتی داریم کد رو می‌خونیم بدونیم این تابع هیچجوره استثنا رو پرت نمی‌کنه و بنابراین با خیال راحت میتونیم خارج از بلوک try قرارش بدیم.

نکته: اگر تابع ما const هست،‌ حتما کلمهٔ noexcept رو باید بعد از const بذاریم. نمیدونم چرا.

استثنا در Constructor و Destructor

یه سری نکات وجود داره که بهش می‌پردازیم:

  • از اشیاء ای که به صورت گلوبال تعریف شدن و اشیاء ای که به صورت static تعریف شدن نباید استثنا ای پرت بشه چرا که این اشیاء قبل از main ساخته میشن و نمیشه catch کردشون.
  • اگر یک شئ رو با استفاده از new ساخته باشیم و در کانستراکتورش یک استثنا رخ بده، اون حافظه ای که گرفته شده خود به خود آزاد میشه.
  • اگر کانستراکتور حافظه ای رو تخصیص داده، قبل از اینکه استثنا ای رو پرت بکنه باید حتما حافظه ای که گرفته رو پاک کنه!

استثنا ها در new

یکی از ویژگی های جالب سی++ که نظرمو جلب کرد، تابع set_new_handler (از هدر <new>)بود. این تابع یک تابع(بدون ورودی و خروجی void) رو به عنوان ورودی خودش میگیره و هر زمان و هرجای برنامه که موقع new کردن یک حافظه، مشکلی ایجاد بشه، اون تابع رو فراخوانی می‌کنه.

اگر تابع handler رجیستر نشده باشه،‌ در حالت پیشفرض new میاد و استثنا bad_alloc رو پرتاب می‌کنه.

کلاس unique_ptr

توضیحاتش زیاده ولی اگر مختصر بخوام بگم، یکی از اشاره گر های هوشمند سی++ هست که این نیاز رو که مجبوریم هر حافظه ای که گرفتیم رو به صورت دستی delete کنیم از بین می‌بره. خودش به صورت خودکار وقتی که out of scope بشه، حافظه رو برمیگردونه به سیستم.

یه مثال ازش میزنم: (کلاس Integer کار خاصی نمی‌کنه. فقط موقع نابود شدن یه متن چاپ میکنه و یه setter و getter داره)

تابع make_unique درواقع کار همون new رو انجام می‌ده و خط ۱۴ رو میشه به این شکل هم نوشت:

unique_ptr<Integer> ptrToInteger {new Integer(7)}

مالکیت در unique_ptr

هر اشاره گری فقط میتونه توسط یک شئ unique_ptr مدیریت بشه و این امکان که یک اشاره گر توسط چند شئ مدیریت بشن وجود نداره. درواقع وقتی یک شئ از این کلاس به یک شئ دیگه نسبت داده میشه(assign)، مالکیت اشاره گر از شئ سمت راست به شئ سمت چپ منتقل میشه.

این موضوع باعث میشه که بتونیم از unique_ptr برای پاس دادن آرگومان ها به تابع و یا برگردوندن اشاره گر از یک تابع استفاده بکنیم.

سلسله مراتب Exception های استاندارد

سلسله مراتب استثناهای استاندارد

با catch کردن کلاس والد، همه استثنا هایی که فرزند اون کلاس هستند هم catch می‌شن.

اگر می‌خوایم همهٔ انواع استثنا هارو catch بکنیم، میتونیم اینطوری بنویسیم:

catch (...) { // code here }

یکی از بدیای این روش اینه که دیگه نمی‌تونیم به جزئیات ارور دسترسی داشته باشیم. البته، اگر در سطوح پایین تر(توابعی که تابع موجود رو صدا زدند) catch ای وجود داشته باشه، میتونیم با rethrow کردن استثنا به جزئیات هم دسترسی داشته باشیم.

پایان

در فصل بعد درباره Template ها صحبت می‌کنیم. فصل باحالیه.