30

测试新的移动语义。

我刚刚询问了我在使用 Move Constructor 时遇到的问题。但正如评论中发现的那样,当您使用标准的“复制和交换”习语时,问题实际上是“移动赋值”运算符和“标准赋值”运算符发生冲突。

这是我正在使用的课程:

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

但这并不好,因为它会干扰编译器优化复制和交换的能力。请参阅什么是复制和交换习语?这里这里

我错过了一些不那么明显的东西吗?

4

3 回答 3

26

如果将赋值运算符定义为取值,则不应(不需要也不能)定义取右值引用的赋值运算符。这是没有意义的。

通常,当您需要区分左值和右值时,您只需要提供采用右值引用的重载,但在这种情况下,您选择的实现意味着您不需要进行区分。无论您有左值还是右值,您都将创建参数并交换内容。

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

在这种情况下,编译器会将调用解析为a.operator=(f());它会意识到返回值的唯一原因是作为参数operator=并将省略任何副本——这是使函数首先取值的关键!

于 2013-11-07T16:46:54.403 回答
11

其他答案建议只有一个重载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 中的内存分配。例如,这是一个简单的实现(不完美:如果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);
}

除了表现点 if swapis noexcept, thenoperator =(String&&)也可以noexcept。(如果内存分配是“可能”执行的,则情况并非如此。)

在Howard Hinnant的精彩解释中查看更多细节。

于 2013-11-07T18:39:48.880 回答
3

复制和分配所需的只是:

    // 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
   }
于 2013-11-07T16:51:36.403 回答