移动语义和右值引用

102 阅读2分钟

移动语义和右值引用

案例:

vector<string> allcps(const vector<string>& vs)
{
    vector<string> temp;
	...//存储vs中的所有的值
    return temp;
}
vector<string> vstr;
vector<string> vstr_copy1(vstr);//#1
vector<string> vstr_copy2(allcps(vstr));//#2

vstr_copy2会先调用allcps函数,首先产生一个temp变量(allcps内部),然后生成一个临时返回对象并删除temp变量,再调用拷贝构造,最后再删除临时返回对象。

如果不删除原来的字符,仅仅是修改一下指向,这种方法叫做移动语义

要实现移动语义,就要让编译器知道何时需要复制何时不需要,所以就要使用右值引用

移动构造函数

class Test
{
private:
    char* pc;
public:
    Test();
    Test(const Test & t);
    Test(Test && t);//移动构造函数。因为要更改t,所以不能加上const
    ~Test();
    Test operator+(const Test& t)const;
}

Test::Test()
{
    pc = new char[100];
    for(int i =0;i<100;++i){pc[i]=i;}
}
Test::Test(const Test& t)
{
    pc = new char[100];
    for(int i=0;i<100;++i){pc[i]=t.pc[i];}
}
Test::Test(Test&& t)
{
    pc=t.pc;
    t.pc=nullptr;//pc和t.pc指向同一块内存,调用析构时可能会出现问题,不能对同一块地址delete两次
    				//所以将t.pc设置为空地址,因为delete空指针是可以的
}
Test::~Test()
{
    delete [] pc;
}
Test Test::operator+(const Test& t) const
{
	Test temp ;
	for (int i = 0; i < 100; ++i)
	{
		temp.pc[i] = this->pc[i]+t.pc[i];
	}
	return temp;
}

int main()
{
    Test one;
    Test two = one;//调用普通的拷贝构造。因为one是左值
	Test three;
    Test four(one+three);//调用移动构造函数。因为one+three为右值
}
//在c++98没有引入右值引用时,如果实参为右值,const引用形参将执行一个临时变量

赋值

适用于构造函数的移动语义也适用于赋值运算符

Test& Test::operator=(const Test& t) 
{
    if(this == &t)
        return *this;
    delete [] pc;
    pc = new char[100];
    for(int i=0;i<100;i++)
    {
        pc[i]=t.pc[i];
    }
    return *this;
}

Test& Test::operator=(Test&& t) //同样没有const修饰参数,因为要改动
{
     if(this == &t)
        return *this;
    delete [] pc;
    pc = t.pc;
    t.pc = nullptr;
    
    return *this;
}

强制移动

移动构造函数和移动赋值运算符号都是使用右值,如果只有左值怎么办?

  1. 可以使用static_cast<>将对象的类型强行转为Test &&
  2. 使用utility头文件中的std::move()

Test four = std::move(one)

std::move并非一定会使用移动操作,例如:

Chunk one;
...
Chunk two;
two = std::move(one);//如果定义了移动赋值运算符,这样没有问题
//如果没有定义移动赋值运算符,将使用复制赋值运算符,如果也没有定义,那将不允许