c++ - 할당 복사가 표준 복사 및 스왑과 호환되지 않음




c++11 move-semantics (2)

새로운 Move Semantics 테스트.

방금 이동 생성자와 관련된 문제에 대해 질문했습니다. 그러나 코멘트에서 밝혀진 바와 같이 문제는 실제로 "이동 할당"연산자와 "표준 할당"연산자가 표준 "복사 및 스왑"관용구를 사용할 때 충돌한다는 것입니다.

이것이 내가 사용하는 수업이다.

#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에 대한 할당은 "Move Assignment"연산자를 사용해야합니다. 하지만 "표준 할당"연산자 (표준 복사 및 스왑으로 작성)와 충돌이 발생합니다.

> 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;
    }

그러나 이것은 컴파일러가 복사 및 스왑을 최적화하는 능력을 망쳐 놓은 것처럼 좋지 않습니다. 복제 및 스왑 이디엄이란?을 참조하십시오. here 와 here

나는 그렇게 분명한 것을 놓치고 있는가?


다른 해답은 하나의 오버로드 operator =(String rhs) 가 값으로 인수를 취하는 것을 제안하지만 이것은 가장 효율적인 구현이 아닙니다 .

David Rodríguez - dribeas의이 예에서

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

복사가 이루어지지 않습니다. 그러나 operator =(String rhs) 가 제공되고 다음 예제를 고려하십시오.

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

일어나는 일은

  1. brhs (메모리 할당 + memcpy )에 복사됩니다.
  2. arhs 가 바뀌 었습니다.
  3. rhs 가 파괴되었습니다.

operator =(const String& rhs)operator =(String&& rhs) 를 구현하면 target이 소스의 길이보다 큰 경우 1 단계에서 메모리 할당을 피할 수 있습니다. 예를 들어 이것은 단순한 구현입니다 (완벽하지는 않습니다 : Stringcapacity 멤버가있는 경우 더 좋을 수 있습니다).

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);
}

swapnoexcept 인 경우 성능 지점 외에도 operator =(String&&)noexcept 수 있습니다. (메모리 할당이 "잠재적으로"수행되는 경우는 그렇지 않습니다.)

하워드 하이난트 (Howard Hinnant)의 뛰어난 explanation 에서 더 자세한 내용을 참조하십시오.


대입 연산자가 값을 가지도록 정의하면 대입 연산자가 rvalue-reference를 사용하여 정의되어서는 안되며 필요도 없습니다. 그것에는 아무런 의미가 없습니다.

일반적으로 왼쪽 값과 오른쪽 값을 구별 할 필요가있을 때 rvalue-reference를 사용하여 과부하를 제공하면되지만이 경우 구현을 선택하면 해당 구별을 할 필요가 없습니다. lvalue 또는 rvalue이든 관계없이 인수를 만들고 내용을 바꿀 것입니다.

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

이 경우 컴파일러는 a.operator=(f()); 호출을 해결합니다 a.operator=(f()); 반환 값의 유일한 이유는 operator= 에 대한 인수이며 모든 복사본을 삭제한다는 것을 알게 될 것입니다. 이것은 함수를 처음에 값으로 만드는 점입니다!







copy-and-swap