قوة Troy

قوة Troy

تمرين تعلّمي

مقدمة

الكلمة المفتاحية 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 مع تعبيرات lambda، وحلقات 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، يُفضَّل استخدام std::make_unique() بدلًا من استخدام new مباشرةً لتخصيص الذاكرة. يوفر 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

مستعد لبدء قوة Troy؟

سجّل في Exercism لتتعلّم وتتقن C++ عبر 19 مفهومًا100 تمرين، وإرشاد بشري حقيقي، وكل ذلك مجانًا.