ما هي النسخ Elision وكيف تعمل على تحسين المصطلح والنسخ؟
-
23-09-2019 - |
سؤال
كنت أقرأ نسخ ومبادلة.
حاولت قراءة بعض الروابط على نسخ Elision لكنني لم أستطع معرفة ما يعنيه بشكل صحيح. هل يمكن لأي شخص أن يوضح ماهية هذا التحسين ، وخاصة ما يعنيه النص التالي
هذه ليست مجرد مسألة راحة ولكن في الواقع تحسين. إذا كانت المعلمة (المعلمة) ترتبط بـ LVALUE (كائن آخر غير مؤشر) ، يتم إجراء نسخة من الكائن تلقائيًا أثناء إنشاء المعلمة (المعلمة). ومع ذلك ، عندما ترتبط S بمقصورة (كائن مؤقت ، حرفي) ، تكون النسخة عادة ما تكون محدودة ، والتي تحفظ مكالمة إلى مُنشئ للنسخ ومدمار. في الإصدار السابق من مشغل الواجب حيث يتم قبول المعلمة كمرجع const ، لا تحدث نسخ Elision عندما يرتبط المرجع بـ RValue. هذا ينتج عنه كائن إضافي يتم إنشاؤه وتدميره.
المحلول
يوجد منشئ النسخ لإجراء نسخ. من الناحية النظرية عندما تكتب خطًا مثل:
CLASS c(foo());
سيتعين على المترجم الاتصال بمنشئ النسخ لنسخ عودة foo()
داخل c
.
Copy Elish هي تقنية لتخطي استدعاء مُنشئ النسخ حتى لا تدفع مقابل النفقات العامة.
على سبيل المثال ، يمكن للمترجم ترتيب ذلك foo()
سوف تبني قيمة الإرجاع مباشرة إلى c
.
هذا مثال آخر. لنفترض أن لديك وظيفة:
void doit(CLASS c);
إذا قمت بتسميتها مع وسيطة فعلية ، فيجب على المترجم أن يستدعي مُنشئ النسخ بحيث لا يمكن تعديل المعلمة الأصلية:
CLASS c1;
doit(c1);
ولكن الآن فكر في مثال مختلف ، دعنا نقول أنك تسمي وظيفتك مثل هذه:
doit(c1 + c1);
operator+
سوف تضطر إلى إنشاء كائن مؤقت (rvalue). بدلاً من استدعاء مُنشئ النسخ قبل الاتصال doit()
, ، يمكن للمترجم أن يمرر المؤقتة التي تم إنشاؤها بواسطة operator+
وتمرير ذلك إلى doit()
في حين أن.
نصائح أخرى
هنا مثال:
#include <vector>
#include <climits>
class BigCounter {
public:
BigCounter &operator =(BigCounter b) {
swap(b);
return *this;
}
BigCounter next() const;
void swap(BigCounter &b) {
vals_.swap(b);
}
private:
typedef ::std::vector<unsigned int> valvec_t;
valvec_t vals_;
};
BigCounter BigCounter::next() const
{
BigCounter newcounter(*this);
unsigned int carry = 1;
for (valvec_t::iterator i = newcounter.vals_.begin();
carry > 0 && i != newcounter.vals_.end();
++i)
{
if (*i <= (UINT_MAX - carry)) {
*i += carry;
} else {
*i += carry;
carry = 1;
}
}
if (carry > 0) {
newcounter.vals_.push_back(carry);
}
return newcounter;
}
void someFunction()
{
BigCounter loopcount;
while (true) {
loopcount = loopcount.next();
}
}
في somefunction
الخط loopcount = loopcount.next();
تستفيد بشكل كبير من نسخ elision. إذا لم يُسمح بنسخ Elision ، فسيتطلب هذا الخط 3 دعوات من مُنشئ النسخ ودعوة مرتبطة بمدمار. مع السماح بالنسخ ، يمكن تقليله إلى 1 مكالمة من مُنشئ النسخ ، والآخر داخل BigCount::next()
أين newcounter
تم إعلانه.
لو operator =
تم إعلانه وتعريفه على هذا النحو:
BigCounter &BigCounter::operator =(const BigCounter &b) {
BigCounter tmp(b);
swap(tmp);
return *this;
}
كان من المفترض أن يكون هناك invocations من مُنشئ النسخ ، حتى مع وجود نسخ. واحد لبناء newcounter
والآخر لبناء tmp
. وبدون نسخ ، لا يزال هناك 3. لهذا السبب الإعلان operator =
لذلك تتطلب الوسيطة الخاصة بها استدعاء بناء النسخ يمكن أن يكون تحسينًا عند استخدام المصطلح "النسخ والمبادلة" لمشغل المهمة. عندما يتم استدعاء مُنشئ النسخ لإنشاء وسيطة ، قد يتم احتجازها ، ولكن إذا تم استدعاءها لإنشاء متغير محلي ، فقد لا يكون ذلك كذلك.