C ++使用具有整数成员的对象移动语义

时间:2017-06-09 09:52:26

标签: c++ c++11 constructor move semantics

#include<iostream>
#include<stdio.h>

using namespace std;
class Test
{    
   public:
       string n;
       Test():n("test") {}  
};

int main()
{
    Test t1;  
    std::cout<<"before move"<<"\n";
    std::cout<<"t1.n=" << t1.n<<"\n";
    Test t2=std::move(t1);

    std::cout<<"after move"<<"\n";
    std::cout<<"t1.n="<<t1.n<<"\n";
    std::cout<<"t2.n="<<t2.n<<"\n"; 

    return 0;
}

上述程序的输出产生以下结果

移动前

t1.n =测试 搬家后 t1.n = t2.n =测试

理解在将对象t1移动到t2之后,t2.n的值导致为空字符串

但是相同的概念移动概念不适用于整数。

#include<iostream>
#include<stdio.h>

using namespace std;

class Test
{

    public:
        int n;
        Test():n(5) {}  

};

int main()
{
    Test t1;  
     std::cout<<"before move"<<"\n";
     std::cout<<"t1.n=" << t1.n<<"\n";
     Test t2=std::move(t1);

     std::cout<<"after move"<<"\n";
     std::cout<<"t1.n="<<t1.n<<"\n";
     std::cout<<"t2.n="<<t2.n<<"\n"; 

     return 0;
}

上述程序的输出产生以下结果

移动前

t1.n = 5 搬家后 t1.n = 5 t2.n = 5

在将对象t1移动到t2之后,我预期t2.n的值为0但旧值仍然存在。

有人可以解释这种行为背后的概念。

4 个答案:

答案 0 :(得分:3)

在C ++中移动是其他语言中所谓的浅拷贝

复制:如果对象将数据存储在动态分配的内存中,则(深)复制意味着(1)分配等效的内存块,(2)将所有元素从一个块复制到其他。这将保留复制的对象并创建一个完全独立的新副本。

移动:如果移动了同一个对象,则只复制与对象实际存储的数据(而不是存储在动态分配的内存中的数据)(实际上,它们被移过,即这是递归的,即指针变量保存内存块的地址和有关大小的信息。同时,移动的对象被“清空”,即进入(有效)状态,该状态在破坏时不会影响移动的对象。这意味着 必须将指向移动对象的内存块的指针重置为nullptr,并将指向内存大小的变量重置为零,您调用的进程清空

现在,对于不使用动态分配内存的对象,特别是所有内置类型(例如int),移动和复制之间没有区别。特别是,将移动的变量保持在其原始状态,即制作副本,就可以了,并且实际上是标准所要求的。 (标准可能没有指定或要求重置为默认值,即0,但事情并非如此。)

另请参阅here了解移动语义的详细说明。

答案 1 :(得分:2)

通常,移动对象可以具有对其类型有效的任何值。例如,移动的std::string可能会变空,或者可能是完全不同的东西。它有时可能是空的,有时不是。它可以是什么没有限制,不应该依赖确切的值。

由于移动的对象可以处于任何有效状态,我们可以看到复制对象是移动它的有效方式。实际上,对于没有定义移动构造函数的任何类型,在移动对象时将使用复制构造函数。 std::move不要求移动的对象变为空(我们不一定要为所有类型定义的概念)。当然,复制可能不是移动对象的最有效方法,但它是允许的。原始类型基本上都是基于此,因此移动基本类型相当于复制它。

我想再强调一下:不要(通常)依赖于移动对象的值。它通常不是指定的值。 不要假设移动的对象与默认构造的对象相同。 不要假设它是任何其他值。某些特定类型(如int或标准智能指针类型)可以指定移动的值,但这些是特殊情况,并不定义一般规则。在将已知值复制到其中之前,通常不使用移动的对象。

答案 2 :(得分:1)

如果您通常需要将内置类型设置为0(例如int,float,pointers),因为您可能会在销毁时依赖它们,例如用户定义的API指针,您需要显式编写一个move运算符以将这些成员设置为零。

class MyClass
{
    std::string str_member_; // auto moved and emptied
    int a_value_;            // need to manually set to 0
    void* api_handle_;       // ''
public:

    // boilerplate code
    MyClass(MyClass&& rhd)
    {
        *this = std::move(rhd);
    }
    MyClass& operator=(MyClass&& rhd)
    {
        if (this == &rhd)
            return *this;

        str_member_ = std::move(rhd.str_member_);
        a_value_ = rhd.a_value_;
        api_handle_ = rhd.api_handle_;

        rhd.a_value_ = 0;
        rhd.api_handle_ = 0;

        return *this;
    }
};

我通常不喜欢这样,因为当新成员添加到类中时,它容易出错。他们需要添加到样板代码中。相反,您可以使用小型帮助程序类,该类在使用默认移动语义进行移动时将特定成员设置为0。

template<class T>
class ZeroOnMove
{
    T val_;
public:
    ZeroOnMove() = default;
    ZeroOnMove(ZeroOnMove&& val) = default;
    ZeroOnMove(const T& val) : val_(val) {}

    ZeroOnMove& operator=(ZeroOnMove&& rhd)
    {
        if (this == &rhd)
            return *this;
        val_ = rhd.val_;
        rhd.val_ = 0;
        return *this;
    }
    operator T() 
    {
        return  val_;
    }
};

在此之前的课程仅是:

class MyClass
{
public:
    std::string str_member_;
    ZeroOnMove<int> a_value_;
    ZeroOnMove<void*> api_handle_;
};

也许这也有助于更多地了解移动语义。

答案 3 :(得分:0)

隐式Move构造函数不适用于成员非类类型 但是,如果您使用显式的Move构造函数,则在这种情况下,可以将交换函数用于非类类型。

#include <utility>
std::exchange(old_object, default_value) //explicit move of a member of non-class type

下面是示例

#include<iostream>
#include<string>
#include <utility>

struct A
{
    std::string name;
    int age;
    A(){
        std::cout << "Default ctor. ";
    }

    //explicit
    A(std::string const& s, int x):name(s), age(x){
        std::cout << "Ctor. ";
    }

    A(A const& a):name(a.name),age(a.age){
        std::cout << "Copy ctor. ";
    }

    A(A && a) noexcept :name(std::move(a.name)),age(std::exchange(a.age,0)){
        std::cout << "Move ctor. ";
    }

    A& operator=(A const& a){
        std::cout << "Copy assign. ";
        name = a.name;
        age = a.age;
        return *this;
    }

    A& operator=(A && a) noexcept {
        std::cout << "Move assign. ";
        name = std::move(a.name);
        age = std::move(a.age);
        return *this;
    }

    void printInfo()
    {
        std::cout<<name<<"   "<<age<<std::endl;
    }

    ~A() noexcept {
        std::cout << "Dtor. ";
    }
};
int main()
{
    A a("StackOverflow ", 12);
    a.printInfo();
    A b = std::move(a);
    b.printInfo();
    a.printInfo();
    return 0;
}

了解更多信息 https://en.cppreference.com/w/cpp/language/move_constructor