مسیرها
/
C++
C++
/
تمرین‌ها
/
قدرت تروی
قدرت تروی

قدرت تروی

تمرین یادگیری

مقدمه

کلیدواژه‌ی auto در C++

در C++، کلیدواژه‌ی auto ویژگی قدرتمندی است که در C++11 معرفی شد و برای تعریف متغیرهایی با نوع داده‌ی استنتاج‌شده به کار می‌رود. کامپایلر نوع متغیر را بر اساس مقدار اولیه‌اش استنتاج می‌کند و این کار می‌تواند کد را خواناتر کند و نگهداری از آن را آسان‌تر سازد.

نمونه‌ی استفاده

به نمونه‌ی زیر توجه کنید که در آن از auto برای تعریف متغیرها استفاده شده است:

auto dragon_population{3};          // dragon_population is deduced as an integer
auto westeros{7.7777};              // westeros is deduced as a double
auto wedding_location{"The Twins"}; // wedding_location is deduced as a const char*, not std::string

در هر مورد، نوع متغیر از مقداری که با آن مقداردهی اولیه شده است استنتاج می‌شود.

استنتاج نوع

کلیدواژه‌ی auto با کم کردن پرگویی نوع‌های صریح، به نوشتن کدی موجزتر و خواناتر کمک می‌کند.

const std::vector<std::string> pigeon_pie{"flour", "butter", "pigeon", "salt"};
auto purple_wedding_pie{pigeon_pie};
purple_wedding_pie.emplace_back("the strangler");

در این حلقه، auto نوع purple_wedding_pie را std::vector<std::string> استنتاج می‌کند و نیازی به مشخص کردن دوباره‌ی صریح نوع از بین می‌رود.

سازگاری

کلیدواژه‌ی auto با ساختارهای مختلف C++ سازگار است و آن را به ابزاری همه‌کاره در برنامه‌نویسی مدرن C++ تبدیل می‌کند.

auto& element{array[0]};           // reference to an element
const auto object{otherObject};    // const type version of otherObject's type
auto* ptr{&x};                     // pointer to x with the same type as x, but as a pointer.

در مفاهیم بعدی، کلیدواژه‌ی auto را اغلب همراه با عبارات لامبدا، حلقه‌های For مبتنی بر بازه و تکرارگرها خواهیم دید.

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

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

نحوه‌ی نگارش کلی

اشاره‌گرهای هوشمند معمولاً به‌صورت قالب‌های کلاس در کتابخانه‌ی استاندارد C++ پیاده‌سازی می‌شوند. دو اشاره‌گر هوشمند پرکاربرد std::unique_ptr و std::shared_ptr هستند.

اشاره‌گرهای یکتا

std::unique_ptr اشاره‌گری هوشمند است که مالکیت شیء را به‌صورت انحصاری در دست دارد. تضمین می‌کند که در هر لحظه، تنها یک شیء std::unique_ptr مالک منبع باشد. هنگامی که std::unique_ptr مالک نابود یا بازنشانی شود، به‌طور خودکار اشیاء را نابود و حافظه‌شان را آزاد می‌کند.

#include <memory>
// Declaring and defining a unique pointer
auto rightful_king_of_england = std::make_unique<std::string>("Excalibur");

// Unique pointers cannot be copied or assigned
auto mordred = rightful_king_of_england; // Error: Cannot copy a unique_ptr

مزایای std::make_unique()

هنگام ساخت std::unique_ptr، بهتر است به‌جای استفاده‌ی مستقیم از new برای تخصیص حافظه، از std::make_unique() استفاده کنید. std::make_unique() چندین مزیت دارد:

  1. ایمنی در برابر استثنا: std::make_unique() ایمنی در برابر استثنا را تضمین می‌کند. اگر هنگام ساخت شیء استثنایی پرتاب شود، حافظه به‌طور خودکار آزاد می‌شود و از نشت حافظه جلوگیری می‌کند.
  2. خوانایی: استفاده از std::make_unique() کد را روشن‌تر و موجزتر می‌کند. نیازی به مشخص کردن صریح نوع تخصیص‌یافته از بین می‌رود، چون آرگومان‌های قالب به‌طور خودکار استنتاج می‌شوند.
  3. فرصت‌های بهینه‌سازی: کامپایلرها می‌توانند std::make_unique() را مؤثرتر از تخصیص دستی حافظه با new بهینه کنند و این احتمالاً به بهبود کارایی می‌انجامد.
  4. پرهیز از استفاده‌ی نادرست: هنگامی که std::unique_ptr به‌صورت دستی ساخته شود، حذف منبع زیرین امکان‌پذیر است. این کار هنگامی که std::unique_ptr در پایان حوزه‌ی خود تلاش می‌کند آن را حذف کند، به رفتار تعریف‌نشده می‌انجامد.

اشاره‌گرهای اشتراکی

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

// Declaring and defining a shared pointer to a dynamically allocated string
auto martian_congressional_republic = std::make_shared<std::string>("protomolecule");

// Creating more shared pointer that shares ownership
auto outer_planets_alliance = martian_congressional_republic;
auto united_nations = martian_congressional_republic;
Caution

در C++17 و نسخه‌های پیش‌تر، استفاده از std::shared_ptr همراه با آرایه‌ها از طریق std::make_shared<T[]> به‌طور مستقیم پشتیبانی نمی‌شود. هرچند تخصیص آرایه‌ها با std::make_shared<T[]> امکان‌پذیر است، ساخت مستقیم اشاره‌گرهای اشتراکی از آن‌ها می‌تواند به‌دلیل تفاوت‌های مدیریت حافظه میان اشیاء تک و آرایه‌ها به رفتار تعریف‌نشده بیانجامد. در عوض، برای مدیریت کارآمد آرایه‌ها با اشاره‌گرهای اشتراکی، استفاده از std::vector یا توابع حذف سفارشی را در نظر بگیرید. هنگام کار با تخصیص آرایه‌ها و اشاره‌گرهای اشتراکی در C++17، همیشه سازگاری با کامپایلر و پیاده‌سازی کتابخانه‌ی استاندارد خود را در نظر بگیرید.

مزایای std::make_shared()

مشابه std::make_unique()، تابع std::make_shared() مزایایی مانند کارایی بهتر حافظه، ایمنی در برابر استثنا و خوانایی ارائه می‌دهد. تخصیص حافظه برای بلوک کنترل و شیء مدیریت‌شده را در یک عملیات واحد ترکیب می‌کند و بدین‌ترتیب کارایی را افزایش و خطر نشت حافظه را کاهش می‌دهد. علاوه بر این، استنتاج خودکار آرگومان‌های قالب کد را ساده‌تر و خواناتر می‌کند. استفاده از std::make_shared() هنگام کار با اشیاء std::shared_ptr در C++ کدی پاکیزه‌تر، ایمن‌تر و کارآمدتر به‌دست می‌دهد.

توصیه‌ی استفاده

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

دستورالعمل‌ها

این تمرین شما را به دنیای تروا می‌برد. زندگی مردم آن پر از شگفتی و جادو است. بسیاری از انسان‌های تروا دارای «قدرت» هستند که در زندگی روزمره‌شان بارها به کار می‌آید. قدرت‌ها برای تغییر شکل دادن به دنیا یا تأثیر گذاشتن بر جانوران تروا و دیگر انسان‌ها به کار می‌روند. جادو همچنین در «آرتیفکت‌های یکتا» بروز می‌کند که ماجراجویان، پیشه‌وران و حکیمان به‌شدت در پی آن‌ها هستند.

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

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

1. انسان‌ها را به دنیای تروا بیاورید

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

پیاده‌سازی‌های پایه‌ای ساختارهای artifact و power در اختیار شما قرار گرفته است. یک ساختار (یا کلاس) human پیاده‌سازی کنید که یک «اشاره‌گر هوشمند» به متغیر عضوی از نوع artifact به اسم possession داشته باشد. هر آرتیفکت در هر لحظه فقط می‌تواند در اختیار یک انسان باشد.

human باید دو متغیر عضو دیگر هم داشته باشد. یکی own_power را نگه می‌دارد و دیگری influenced_by است؛ یعنی قدرتی که انسان تحت تأثیر آن قرار گرفته. هر دوی own_power و influenced_by اشاره‌گرهای هوشمند به powers هستند. هر power ممکن است در اختیار یک انسان باشد، اما همزمان بر انسان‌های دیگر هم اثر بگذارد.

به‌طور پیش‌فرض، انسان‌ها بدون هیچ آرتیفکتی به دنیا می‌آیند، نه صاحب قدرتی هستند و نه تحت تأثیر قدرتی قرار دارند.

human mindy_mccready{};
mindy_mccready.possession;
// => nullptr
mindy_mccready.own_power;
// => nullptr
mindy_mccready.influenced_by;
// => nullptr

2. آرتیفکت‌ها را به تروا بیاورید

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

تابع give_new_artifact را تعریف کنید که چیزی برنمی‌گرداند اما ارجاعی به یک human و یک string می‌گیرد. این تابع باید با آن string یک شیء artifact جدید تعریف کند و اشاره‌گر possession آن human را مطابق آن تنظیم کند. این تابع نباید چیزی برگرداند.

human erik_magnus_lehnsherr{};
give_new_artifact(erik_magnus_lehnsherr, "Mind shielding helmet");

erik_magnus_lehnsherr.possession->name;
// "Mind shielding helmet"

3. وسایل را قابل مبادله کنید

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

تابع exchange_artifacts را بنویسید که چیزی برنمی‌گرداند اما دو اشاره‌گر هوشمند آرتیفکت می‌گیرد تا وسایل را با هم عوض کند. یادتان باشد که نمی‌توانید یک unique_ptr را کپی کنید. این موضوع شامل استفاده از آن در پارامترهای تابع هم می‌شود. در عوض، از ارجاعی به unique_ptr استفاده کنید.

human uchiha{};
give_new_artifact(uchiha, "konoha headband");
human uzumaki{};
give_new_artifact(uzumaki, "forehead protector");

exchange_artifacts(uchiha.possession, uzumaki.possession);

uchiha.possession->name;
// "forehead protector"
uzumaki.possession->name;
// "konoha headband"

4. به مردم قدرت بدهید

هیجان‌انگیزترین ویژگی تروا، قدرت‌های ویژه‌ای است که مردم ممکن است در اختیار داشته باشند. بعضی می‌توانند با فکرشان آهن را ذوب کنند و بعضی دیگر می‌توانند در شب هر زخمی را بی‌درنگ التیام دهند.

تابع manifest_power را تعریف کنید که چیزی برنمی‌گرداند اما ارجاعی به یک human و یک string می‌گیرد. این تابع باید با آن string یک شیء power جدید تعریف کند و اشاره‌گر own_power آن human را مطابق آن تنظیم کند. این تابع نباید چیزی برگرداند.

human eleven {};
manifest_power(eleven, "psychokinesis");

eleven.own_power->effect;
// "psychokinesis"

5. از قدرت استفاده کنید

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

یک تابع void به اسم use_power بنویسید که دو ارجاع به انسان می‌گیرد. انسان اول اعمال‌کننده‌ی قدرت است و دومی نماینده‌ی هدف. اشاره‌گر influenced_by هدف باید به قدرت اعمال‌کننده اشاره کند.

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

human pamela_isley{};
manifest_power(pamela_isley, "control pheromones");

human count_vertigo{};
use_power(pamela_isley, count_vertigo);
count_vertigo.influenced_by->effect;
// "control pheromones"

6. شدت قدرت را زیر نظر بگیرید

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

تابع power_intensity را تعریف کنید که یک انسان می‌گیرد و شدت قدرتش را به‌صورت یک int برمی‌گرداند. اگر فرد قدرتی نداشته باشد، مقدار بازگشتی باید 0 باشد. در غیر این صورت، شدت باید اعمال‌کننده و همه‌ی افرادی را که در حال حاضر تحت تأثیرند در نظر بگیرد.

human jean_grey{};
manifest_power(jean_grey, "uplifting personality");

human scott{};
human logan{};
human ororo{};

use_power(jean_grey, ororo);
use_power(jean_grey, logan);
use_power(jean_grey, scott);

power_intensity(jean_grey);
// 4
ویرایش از طریق GitHub این لینک در پنجره یا زبانه‌ی جدیدی باز می‌شود
C++ Exercism

آماده‌اید قدرت تروی را شروع کنید؟

در Exercism ثبت‌نام کنید تا C++ را همراه با 19 مفهوم100 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.