移动赋值运算符,移动构造函数

时间:2016-02-09 13:00:15

标签: c++11 move-semantics move-constructor rule-of-three move-assignment-operator

我一直试图确定5的规则,但网上的大部分信息都过于复杂,示例代码也有所不同。

即使我的教科书也没有很好地涵盖这个主题。

关于移动语义:

模板,右值和左值,据我所知,移动语义就是这样:

int other     = 0;           //Initial value
int number    = 3;           //Some data

int *pointer1 = &number;     //Source pointer
int *pointer2 = &other;      //Destination pointer

*pointer2     = *pointer1;   //Both pointers now point to same data 
 pointer1     =  nullptr;    //Pointer2 now points to nothing

//The reference to 'data' has been 'moved' from pointer1 to pointer2

与复制相关,这相当于这样:

pointer1      = &number;     //Reset pointer1

int newnumber = 0;           //New address for the data

newnumber     = *pointer1;   //Address is assigned value
pointer2      =  &newnumber; //Assign pointer to new address

//The data from pointer1 has been 'copied' to pointer2, at the address 'newnumber'

不需要对右值,左值或模板进行解释,我甚至可以说这些话题是无关的。

第一个例子比第二个例子快的事实应该是给定的。我还要指出,在C ++ 11之前的任何有效代码都会这样做。

根据我的理解,这个想法是将所有这些行为捆绑在std库中的一个简洁的小运算符move()中。

编写复制构造函数和复制赋值运算符时,我只是这样做:

Text::Text(const Text& copyfrom) {
    data  = nullptr;  //The object is empty
    *this = copyfrom;

}


const Text& Text::operator=(const Text& copyfrom) {
    if (this != &copyfrom) {
        filename = copyfrom.filename;
        entries  = copyfrom.entries;

        if (copyfrom.data != nullptr) {  //If the object is not empty
            delete[] data;
        }

        data = new std::string[entries];

        for (int i = 0; i < entries; i++) {
            data[i] = copyfrom.data[i];
            //std::cout << data[i];
        }
        std::cout << "Data is assigned" << std::endl;

    }

    return *this;
}

人们会想到,等同于:

Text::Text(Text&& movefrom){
    *this = movefrom;
}

Text&& Text::operator=(Text&& movefrom) {
    if (&movefrom != this) {
        filename = movefrom.filename;
        entries  = movefrom.entries;
        data     = movefrom.data;

        if (data != nullptr) {
            delete[] data;
        }

        movefrom.data    = nullptr;
        movefrom.entries = 0;
    }
    return std::move(*this);
}

我非常肯定这不会起作用,所以我的问题是:你如何通过移动语义实现这种类型的构造函数?

1 个答案:

答案 0 :(得分:0)

我并不完全清楚你的代码示例应该证明什么 - 或者这个问题的重点是什么。

概念这句话是什么?移动语义&#39;用C ++表示?

是否&#34;如何编写移动控制器和移动赋值运算符?&#34; ?

这是我尝试介绍这个概念。如果您想查看代码示例,请查看评论中链接的任何其他SO问题。

直观地说,在C和C ++中,一个对象应该代表驻留在内存中的一段数据。出于多种原因,通常您希望将数据发送到其他地方。

通常人们可以直接将指针/引用传递给对象到需要数据的地方。然后,可以使用指针读取它。取指针并移动指针非常便宜,因此这通常非常有效。主要的缺点是你必须确保对象能够存活所需的时间,或者你得到一个悬空指针/引用和崩溃。有时这很容易确保,有时则不然。

当它不是时,一个明显的选择是制作副本并传递它(按值传递)而不是通过引用传递。当需要数据的地方有自己的数据副本时,它可以确保副本在需要时保持不变。这里的主要缺点是你必须制作一份副本,如果对象很大,这可能会很昂贵。

第三种方法是移动对象而不是复制它。移动对象时,它不会重复,而是专门在新站点中可用,而不再在旧站点中可用。只有当你在旧网站上不再需要它时,你才可以这样做,但是在这种情况下,这可以为你节省一份可以节省很多钱的副本。

当对象很简单时,所有这些概念对于实际实现和正确而言都是微不足道的。例如,当你有一个trivial对象,即一个具有简单构造/破坏的对象时,可以使用memcpy完全像在C编程语言中那样复制它。 memcpy生成一个字节块的逐字节副本。如果一个普通的对象被正确初始化,由于它的创建没有可能的副作用,而后来的破坏也没有,那么memcpy副本也会被正确初始化并产生一个有效的对象。

但是,在现代C ++中,你的许多对象都不是微不足道的 - 它们可能会拥有&#34;拥有&#34;引用堆内存,并使用RAII管理此内存,RAII将对象的生命周期与某些资源的使用联系起来。例如,如果函数中有std::string作为局部变量,则该字符串不完全是&#34;连续的&#34;对象,而是连接到内存中的两个不同位置。堆栈上有一个小的,固定大小的(sizeof(std::string)实际上)块,它包含一个指针和一些其他信息,指向堆上动态大小的缓冲区。在形式上,只有小型控制&#34; part是std::string对象,但直观​​地从程序员的角度来看,缓冲区也是&#34; part&#34;字符串,是你通常想到的部分。您无法使用std::string复制memcpy这样的对象 - 如果您有std::string s并尝试从地址复制sizeof(std::string)字节,请考虑会发生什么&s获取第二个字符串。不是两个不同的字符串对象,而是最终得到两个控制块,每个控制块指向同一个缓冲区。当第一个被销毁时,该缓冲区被删除,因此使用第二个将导致段错误,或者当第二个被销毁时,您将获得双重删除。

通常,使用memcpy复制非平凡的C ++对象是非法的,会导致未定义的行为。这是因为它与C ++的核心思想之一相冲突,即对象创建和破坏可能会产生程序员使用ctors和dtors定义的非常重要的后果。对象生存期可用于创建和实施用于推理程序的不变量。 memcpy是一个愚蠢的&#34;只是复制一些字节的低级方法 - 可能会绕过强制使程序工作的不变量的机制,这就是为什么如果使用不正确会导致未定义的行为的原因。

相反,在C ++中我们有复制构造函数,您可以使用它来安全地复制非平凡对象。您应该以保留对象所需的不变量的方式编写这些内容。三条规则是关于如何实际做到这一点的指导原则。

C ++ 11&#34;移动语义&#34; idea是一个新的核心语言特性的集合,它们被添加以扩展和优化来自C ++ 98的传统拷贝构造机制。具体来说,它是关于,我们如何移动潜在的复杂RAII对象,而不仅仅是我们已经能够移动的琐碎对象。我们如何使语言在可能的情况下自动生成移动构造函数等,类似于它对复制构造函数的作用。我们如何使用移动选项来节省时间,不会导致旧代码中的错误,或者破坏语言的核心假设。 (这就是为什么我会说你的intint *的代码示例与C ++ 11移动语义没什么关系。)

然后,五条规则是三条规则的相应扩展,它描述了当你可能还需要为给定的类实现移动ctor / move赋值运算符而不依赖于语言的默认行为时的条件