نقل المهمة غير متوافقة مع النسخة القياسية والمبادلة

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

سؤال

اختبار الخلاصة الجديدة الدلالات.

لقد سألت للتو عن المشكلات التي كنت أواجهها مع مُنشئ الحركة. ولكن كما اتضح في التعليقات ، فإن المشكلة هي في الحقيقة أن مشغل "Move Configment" ومشغل "المهمة القياسية" عندما تستخدم المصطلح القياسي "النسخة" و "المبادلة".

هذا هو الفصل الذي أستخدمه:

#include <string.h>
#include <utility>

class String
{
    int         len;
    char*       data;

    public:
        // Default constructor
        // In Terms of C-String constructor
        String()
            : String("")
        {}

        // Normal constructor that takes a C-String
        String(char const* cString)
            : len(strlen(cString))
            , data(new char[len+1]()) // Allocate and zero memory
        {
            memcpy(data, cString, len);
        }

        // Standard Rule of three
        String(String const& cpy)
            : len(cpy.len)
            , data(new char[len+1]())
        {
            memcpy(data, cpy.data, len);
        }
        String& operator=(String rhs)
        {
            rhs.swap(*this);
            return *this;
        }
        ~String()
        {
            delete [] data;
        }
        // Standard Swap to facilitate rule of three
        void swap(String& other) throw ()
        {
            std::swap(len,  other.len);
            std::swap(data, other.data);
        }

        // New Stuff
        // Move Operators
        String(String&& rhs) throw()
            : len(0)
            , data(null)
        {
            rhs.swap(*this);
        }
        String& operator=(String&& rhs) throw()
        {
            rhs.swap(*this);
            return *this;
        }
};

معيار مستنقع جميل على ما أعتقد.

ثم اختبرت الكود الخاص بي مثل هذا:

int main()
{
    String  a("Hi");
    a   = String("Test Move Assignment");
}

هنا المهمة ل a يجب استخدام مشغل "تحرك". ولكن هناك اشتباك مع مشغل "المهمة القياسية" (المكتوبة كنسخة قياسية ومبادلة).

> g++ --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/usr/include/c++/4.2.1
Apple LLVM version 5.0 (clang-500.2.79) (based on LLVM 3.3svn)
Target: x86_64-apple-darwin13.0.0
Thread model: posix

> g++ -std=c++11 String.cpp
String.cpp:64:9: error: use of overloaded operator '=' is ambiguous (with operand types 'String' and 'String')
    a   = String("Test Move Assignment");
    ~   ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
String.cpp:32:17: note: candidate function
        String& operator=(String rhs)
                ^
String.cpp:54:17: note: candidate function
        String& operator=(String&& rhs)
                ^

الآن يمكنني إصلاح ذلك عن طريق تعديل مشغل "المهمة القياسية" إلى:

    String& operator=(String const& rhs)
    {
        String copy(rhs);
        copy.swap(*this);
        return *this;
    }

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

هل أفتقد شيئًا غير واضح؟

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

المحلول

إذا قمت بتحديد مشغل المهمة لاتخاذ قيمة ، فلا يجب (لا تحتاج إلى) تحديد مشغل المهمة الذي يتخذ مرجعًا RValue. لا جدوى من ذلك.

بشكل عام ، تحتاج فقط إلى توفير الحمل الزائد لاتخاذ مرجع RValue عندما تحتاج إلى التمييز بين LVALUE عن rvalue ، ولكن في هذه الحالة يعني اختيارك للتنفيذ أنك لا تحتاج إلى التمييز. سواء كان لديك lvalue أو rvalue ، فسوف تقوم بإنشاء الوسيطة وتبادل المحتويات.

String f();
String a;
a = f();   // with String& operator=(String)

في هذه الحالة ، سيقوم المترجم بحل المكالمة ليكون a.operator=(f()); سوف يدرك أن السبب الوحيد لقيمة الإرجاع هو الوسيطة operator= وسوف يرفع أي نسخة -هذه هي الهدف من جعل الوظيفة تأخذ قيمة في المقام الأول!

نصائح أخرى

تشير الإجابات الأخرى إلى وجود تحميل زائد واحد فقط operator =(String rhs) أخذ الحجة بالقيمة ولكن هذا ليس التنفيذ الأكثر كفاءة.

صحيح أنه في هذا المثال من قبل ديفيد رودريغيز - Dribeas

String f();
String a;
a = f();   // with String& operator=(String)

لم يتم إجراء نسخة. ومع ذلك ، افترض فقط operator =(String rhs) يتم توفيره والنظر في هذا المثال:

String a("Hello"), b("World");
a = b;

ما يحدث هو

  1. b يتم نسخه إلى rhs (تخصيص الذاكرة + memcpy);
  2. a و rhs يتم تبديلها
  3. rhs دمرت.

إذا قمنا بتنفيذ operator =(const String& rhs) و operator =(String&& rhs) ثم يمكننا تجنب تخصيص الذاكرة في الخطوة 1 عندما يكون للهدف طول أكبر من المصدر. على سبيل المثال ، هذا تطبيق بسيط (ليس مثاليًا: يمكن أن يكون أفضل إذا String كان capacity عضو):

String& operator=(const String& rhs) {
    if (len < rhs.len) {
        String tmp(rhs);
        swap(tmp);
    else {
        len = rhs.len;
        memcpy(data, rhs.data, len);
        data[len] = 0;
    }
    return *this;
}

String& operator =(String&& rhs) {
    swap(rhs);
}

بالإضافة إلى نقطة الأداء إذا swap هو noexcept, ، ومن بعد operator =(String&&) يمكن ان يكون noexcept كذلك. (وهذا ليس هو الحال إذا كان تخصيص الذاكرة "يحتمل".

شاهد المزيد من التفاصيل في هذا الممتاز تفسير بقلم هوارد هينانت.

كل ما تحتاجه للنسخ والتعيين هو:

    // As before
    String(const String& rhs);

    String(String&& rhs)
    :   len(0), data(0)
    {
        rhs.swap(*this);
    }

    String& operator = (String rhs)
    {
        rhs.swap(*this);
        return *this;
    }

   void swap(String& other) noexcept {
       // As before
   }
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top