it-swarm.asia

ج + 11 عودة قيمة التحسين أو التحرك؟

لا أفهم متى يجب استخدام std::move ومتى يجب أن أسمح للمترجم بتحسين ... على سبيل المثال:

using SerialBuffer = vector< unsigned char >;

// let compiler optimize it
SerialBuffer read( size_t size ) const
{
    SerialBuffer buffer( size );
    read( begin( buffer ), end( buffer ) );
    // Return Value Optimization
    return buffer;
}

// explicit move
SerialBuffer read( size_t size ) const
{
    SerialBuffer buffer( size );
    read( begin( buffer ), end( buffer ) );
    return move( buffer );
}

ما الذي يجب علي استخدامه؟

155
elvis.dukaj

استخدم حصريًا الطريقة الأولى:

Foo f()
{
  Foo result;
  mangle(result);
  return result;
}

هذه الإرادة بالفعل تسمح باستخدام منشئ الخطوة ، إذا كان ذلك متاحًا. في الواقع ، يمكن أن يرتبط المتغير المحلي بمرجع rvalue في عبارة return بدقة عند السماح بنسخ النسخة.

الإصدار الثاني يحظر بنشاط نسخة elision. الإصدار الأول هو أفضل عالميا.

96
Kerrek SB

جميع قيم الإرجاع إما بالفعل moved أو تم تحسينها ، لذلك ليست هناك حاجة للتنقل صراحة باستخدام قيم الإرجاع.

يُسمح للمترجمين بنقل القيمة المرجعة تلقائيًا (لتحسين النسخة) ، وحتى تحسين هذه الخطوة!

القسم 12.8 من المسودة القياسية n3337 (C++ 11):

عند استيفاء معايير معينة ، يُسمح للتطبيق بحذف إنشاء نسخة/نقل لكائن فئة ، حتى إذا كان مُنشئ النسخ/النقل و/أو المدمر للكائن له آثار جانبية. في مثل هذه الحالات ، يعامل التطبيق المصدر والهدف من عملية النسخ/النقل المحذوفة على أنهما طريقتان مختلفتان للإشارة إلى نفس الكائن ، ويحدث تدمير ذلك الكائن في وقت لاحق من الأوقات التي يكون فيها الكائنان تم تدميره دون التحسين. يُسمح بإجراء عمليات النسخ/النقل هذه ، ويُسمى copy elision ، في الحالات التالية (والتي يمكن دمجها لإزالة نسخ متعددة):

[...]

مثال :

class Thing {
public:
Thing();
   ~Thing();
   Thing(const Thing&);
};

Thing f() {
   Thing t;
   return t;
}

Thing t2 = f();

هنا يمكن دمج معايير elision لإزالة مكالمتين لمُنشئ نسخة الفئة Thing: نسخ الكائن التلقائي المحلي t في الكائن المؤقت لقيمة الإرجاع للدالة f() ونسخ ذلك الكائن المؤقت إلى كائن t2. على نحو فعال ، يمكن عرض بناء الكائن المحلي t على أنه تهيئة مباشرة للكائن العمومي t2 ، وسيحدث تدمير هذا الكائن عند الخروج من البرنامج. إن إضافة مُنشئ نقل إلى Thing له نفس التأثير ، لكن بناء الانتقال من الكائن المؤقت إلى t2 هو الذي تمت محاذاته. - مثال النهاية ]

عندما يتم استيفاء معايير اختيار عملية النسخ أو سيتم استيفائها ، ما عدا أن الكائن المصدر هو معلمة دالة ، ويتم تعيين الكائن المراد نسخه بواسطة قيمة ، ودقة التحميل الزائد لتحديد مُنشئ النسخة هي أولاً تم إجراؤها كما لو أن الكائن قد تم تعيينه بواسطة rvalue. إذا فشل تحليل الحمولة الزائدة ، أو إذا كان نوع المعلمة الأولى للمنشئ المحدد ليس مرجع rvalue لنوع الكائن (ربما مؤهل بـ cv) ، يتم تنفيذ دقة التحميل الزائد مرة أخرى ، مع مراعاة الكائن باعتباره قيمة.

114
Jamin Grey

انها بسيطة جدا.

return buffer;

إذا قمت بذلك ، فسيحدث إما NRVO أو لن يحدث. إذا لم يحدث ذلك ، فسيتم نقل buffer.

return std::move( buffer );

إذا قمت بذلك ، فإن NVRO لن يحدث ، وسيتم نقل buffer من.

لذلك لا يوجد شيء يمكن ربحه باستخدام std::move هنا ، والكثير لنخسره.

حقائب سفر:

Buffer read(Buffer&& buffer) {
    //...
    return std::move( buffer );
}

إذا كانت buffer هي مرجع rvalue ، فعليك استخدام std::move. هذا لأن المراجع ليست مؤهلة للحصول على NRVO ، لذلك بدون std::move سيؤدي ذلك إلى نسخة من قيمة.

هذا مجرد مثال على القاعدة "دائمًا move rvalue reference و forward reference reference" ، التي لها الأسبقية على القاعدة "لا move قيمة الإرجاع".

42
Oktalist

إذا كنت ترجع متغيرًا محليًا ، فلا تستخدم move(). سيسمح هذا للمترجم باستخدام NRVO ، وفشل ذلك ، سيظل المحول البرمجي مسموحًا به لتنفيذ حركة (تصبح المتغيرات المحلية قيمًا R ضمن عبارة return). إن استخدام move() في هذا السياق من شأنه ببساطة أن يمنع NRVO ويفرض المترجم على استخدام خطوة (أو نسخة إذا كانت الخطوة غير متوفرة). إذا كنت تُرجع شيئًا غير المتغير المحلي ، فإن NRVO ليس خيارًا على أي حال ويجب عليك استخدام move() إذا كنت (وإذا فقط) تنوي سرقة الكائن.

24
Adam H. Peterson