عند استخدام الدالة std::make_unique
لإنشاء كائن من النوع المشتق (Derived
) وتخزينه في مؤشر فريد من نوع std::unique_ptr
، هناك بعض التفاصيل التي يجب أن نأخذها في الاعتبار.
في النمط الأول (1)، الذي يعمل، يتم إعادة المؤشر المنشأ مباشرة من std::make_unique
ويتم تحويله ضمن تحويل ضمني إلى std::unique_ptr
بسبب عملية التحويل القابلة للتحقق التي تستخدمها C++ للمشتقات. وبما أنه لا يوجد تحويل غير ضمني آخر معقد، فإن الأمر يعمل بشكل صحيح.
أما في الحالة الثانية (2)، التي تفشل في الترجمة، فإن المشكلة تكمن في محاولة إرجاع المؤشر المؤقت auto a
، الذي يكون من نوع std::unique_ptr
، مباشرة كمؤشر std::unique_ptr
في دالة GetDerived2()
. يحاول المترجم بناءً على التوقعات تحويل نوع المؤشر، ولكنه يفشل في ذلك لأن النوع المطلوب هو std::unique_ptr
والنوع المُقدم هو std::unique_ptr
، ولا يوجد تحويل ضمني مباشر بين هذين النوعين.
أما في الحالة الثالثة (3)، فالدالة std::move()
تقوم بتحريك الموارد المملوكة بواسطة a
إلى المؤشر المُرجع من دالة GetDerived3()
، مما يجعل النوع المُرجع متوافقًا مع النوع المتوقع للإرجاع.
في الحالتين الأخيرتين (4)، عند استخدام std::make_unique
، يتم إنشاء كائن من النوع Base
، الذي يمكن تحويله ضمنيًا إلى std::unique_ptr
بسهولة، وهذا هو السبب في نجاح كل من الحالتين.
بالتالي، يمكن تجاوز مشكلة الحالة الثانية (2) عن طريق استخدام std::move()
كما هو موضح في الحالة الثالثة (3)، أو يمكن إصلاحها مباشرة بتحويل نوع المؤشر المؤقت إلى std::unique_ptr
في العبارة التي تُرجع القيمة في دالة GetDerived2()
، وهذا سيعمل أيضًا بشكل صحيح.
المزيد من المعلومات
بعد فهم الفروق في السلوك بين الحالات المختلفة، يمكننا الآن التفكير في السبب وراء هذا السلوك وكيفية عمل الأمور تحت الغطاء في كل حالة.
في الحالة الأولى (1)، حينما نستخدم std::make_unique
، يتم إنشاء كائن جديد من النوع Derived
، ومن ثم يتم تحويل المؤشر المنشأ بواسطة std::make_unique
ضمنيًا إلى مؤشر من النوع الأساسي Base
. هذا التحويل الضمني يعمل بشكل جيد لأن Derived
يرث Base
، لذا يمكننا بسهولة استخدام مؤشر من النوع Base
للإشارة إلى كائن من النوع Derived
.
أما في الحالة الثانية (2)، حيث نخزن المؤشر المؤقت auto a
الذي يمتلك نوع std::unique_ptr
في متغير من النوع std::unique_ptr
، فإن المترجم لا يستطيع تحويل النوع تلقائيًا. يجب علينا بشكل صريح تحويل نوع المؤشر المؤقت a
إلى std::unique_ptr
ليتمكن المترجم من فهم السياق بشكل صحيح.
في الحالتين الثالثة (3) والرابعة (4)، يتم استخدام std::move()
لتحويل ملكية المورد من المؤشر المؤقت a
إلى المؤشر المرجع في دالة الإرجاع. يتم تحويل المؤشر بالفعل ضمنيًا إلى النوع المناسب std::unique_ptr
بعد استخدام std::move()
.
بالتالي، يمكن القول إن فشل الحالة الثانية يعود إلى عدم القدرة على تحويل النوع ضمنيًا، ويمكن حل هذه المشكلة بإما استخدام std::move()
أو بتحويل نوع المؤشر بشكل صريح كما في الحالة الثالثة أو الرابعة.
توضح هذه الحالات كيفية استخدام std::make_unique
بالإضافة إلى التحويلات الضمنية واستخدام std::move()
لإدارة المؤشرات المؤقتة بشكل صحيح في C++، مما يساعد في فهم السلوك المتوقع وحل المشاكل المحتملة عند استخدام هذه الميزات في الشفرة.