انسخ المنشئ و= التحميل الزائد للمشغل في C++:هل هناك وظيفة مشتركة ممكنة؟

StackOverflow https://stackoverflow.com/questions/1734628

سؤال

منذ منشئ نسخة

MyClass(const MyClass&);

و = التحميل الزائد للمشغل

MyClass& operator = (const MyClass&);

لديهم نفس الكود إلى حد كبير، ونفس المعلمة، ويختلفون فقط عند الإرجاع، هل من الممكن أن يكون لديهم وظيفة مشتركة ليستخدموها معًا؟

هل كانت مفيدة؟

المحلول

نعم.هناك خياران شائعان.أحدهما - وهو ما لا يُشجع عمومًا - هو الاتصال بـ operator= من مُنشئ النسخ بشكل صريح:

MyClass(const MyClass& other)
{
    operator=(other);
}

ومع ذلك، توفير جيدة operator= يمثل تحديًا عندما يتعلق الأمر بالتعامل مع الحالة القديمة والقضايا الناشئة عن التنازل عن الذات.بالإضافة إلى ذلك، تتم تهيئة جميع الأعضاء والقواعد بشكل افتراضي أولاً حتى لو تم تعيينهم من other.قد لا يكون هذا صالحًا حتى لجميع الأعضاء والقواعد، وحتى عندما يكون صالحًا، فهو زائد عن الحاجة لغويًا وقد يكون مكلفًا من الناحية العملية.

الحل الذي يحظى بشعبية متزايدة هو التنفيذ operator= باستخدام منشئ النسخ وطريقة المبادلة.

MyClass& operator=(const MyClass& other)
{
    MyClass tmp(other);
    swap(tmp);
    return *this;
}

او حتى:

MyClass& operator=(MyClass other)
{
    swap(other);
    return *this;
}

أ swap عادةً ما تكون الوظيفة سهلة الكتابة لأنها تقوم فقط بتبديل ملكية الأجزاء الداخلية ولا تحتاج إلى تنظيف الحالة الحالية أو تخصيص موارد جديدة.

تتمثل مزايا لغة النسخ والمبادلة في أنها آمنة للتخصيص الذاتي تلقائيًا - وبشرط أن تكون عملية المبادلة غير قابلة للرمي - كما أنها آمنة جدًا للاستثناءات.

لكي تكون آمنة للغاية للاستثناءات، يتعين على مشغل التعيين المكتوب "يدويًا" عادةً تخصيص نسخة من الموارد الجديدة قبل إلغاء تخصيص الموارد القديمة للمُعيَّن، بحيث في حالة حدوث استثناء في تخصيص الموارد الجديدة، لا يزال من الممكن إرجاع الحالة القديمة إلى .كل هذا يأتي مجانًا مع خاصية النسخ والتبديل، ولكنه عادة ما يكون أكثر تعقيدًا، وبالتالي عرضة للخطأ، عند القيام به من البداية.

الشيء الوحيد الذي يجب توخي الحذر منه هو التأكد من أن طريقة المبادلة هي مبادلة حقيقية، وليست افتراضية std::swap الذي يستخدم منشئ النسخ ومشغل المهمة نفسه.

عادة ما يكون عضوا swap يستخدم. std::swap يعمل ويضمن "عدم الرمي" مع جميع الأنواع الأساسية وأنواع المؤشرات.يمكن أيضًا تبديل معظم المؤشرات الذكية بضمان عدم الرمي.

نصائح أخرى

ينفذ منشئ النسخ التهيئة لأول مرة للأشياء التي كانت ذاكرة خامية. يتجاوز مشغل الواجب، Otoh، القيم الموجودة بأخرى جديدة. في كثير من الأحيان أما، فإن هذا ينطوي على رفض الموارد القديمة (على سبيل المثال، الذاكرة) وتخصيص منها جديدة.

إذا كان هناك تشابه بين الاثنين، فمن أن مشغل الواجب ينفذ الدمار والنسخ. اعتاد بعض المطورين على تنفيذ المهمة في الواقع عن طريق التدمير الموضعي تليها بناء نسخة. ومع ذلك، هذا هو جداً فكرة سيئة. (ماذا لو كان هذا هو عامل التعيين من فئة أساسية تسمى أثناء تعيين فئة مشتقة؟)

ما يعتبر عادة المصطلح الكنسي في الوقت الحاضر يستخدم swap كما اقترح تشارلز:

MyClass& operator=(MyClass other)
{
    swap(other);
    return *this;
}

هذا يستخدم بناء النسخ (لاحظ ذلك other يتم نسخه) وتدمير (مدمر في نهاية الوظيفة) - ويستخدمها بالترتيب الصحيح أيضا: البناء (قد تفشل) قبل الدمار (يجب ألا تفشل).

شيء يزعجني:

MyClass& operator=(const MyClass& other)
{
    MyClass tmp(other);
    swap(tmp);
    return *this;
}

أولا، وقراءة كلمة "مبادلة" عندما تفكر في ذهني "نسخ" تهيج الحس السليم. أيضا، أنا أشك في هدف هذه الخدعة الهوى. نعم، يجب أن تحدث أي استثناءات في بناء الموارد الجديدة (المنسوخة) قبل المبادلة، والتي تبدو وكأنها طريقة آمنة للتأكد من ملء جميع البيانات الجديدة قبل أن تصنعها مباشرة.

هذا جيّد. لذلك، ماذا عن الاستثناءات التي تحدث بعد المبادلة؟ (عندما تدمير الموارد القديمة عندما يخرج الكائن المؤقت من النطاق) من منظور مستخدم الواجب، فشلت العملية، إلا أنها لم تفعل ذلك. لديها تأثير جانبي كبير: حدثت النسخة فعلا. كان فقط بعض تنظيف الموارد الذي فشل. تم تغيير حالة الكائن الوجهة على الرغم من أن العملية تبدو من الخارج لفشلها.

لذلك، أقترح بدلا من "المبادلة" للقيام ب "نقل" أكثر طبيعية:

MyClass& operator=(const MyClass& other)
{
    MyClass tmp(other);
    transfer(tmp);
    return *this;
}

لا يزال هناك بناء الكائن المؤقت، ولكن الإجراء الفوري التالي هو تحرير جميع الموارد الحالية للوجهة قبل التحرك (والانتخاب حتى لا يتم تحريرها مزدوجة) موارد المصدر إليها.

بدلا من {bring، نقل، التدمير}، أقترح {بناء، التدمير، نقل}. هذه الخطوة، التي هي أخطر العمل، هي التي اتخذت آخر مرة بعد تسوية كل شيء آخر.

نعم، تفشل الدمار مشكلة في أي مخطط. إما إما إما تالفة (نسخ عندما لا تعتقد أنه) أو فقدت (تحرير عندما لا تعتقد أنه كان). فقدت أفضل من التالفة. لا توجد بيانات أفضل من البيانات السيئة.

نقل بدلا من المبادلة. هذا اقتراحي على أي حال.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top