Move Contsructor & rvalue References

Move Contsructor & rvalue References

Problem of Temporary Objects

这篇文章的主要目的是研究如何使用move语义去降低内存中临时对象的负载。每次从函数中返回一个对象时,都会有一个临时对象被创建出来,然后进行拷贝。最终我们将会创建出两个对象,但实质上我们只需要一个。

举个例子,我们有一个容器类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Container {
int * m_Data;
public:
Container() {
//Allocate an array of 20 int on heap
m_Data = new int[20];

std::cout << "Constructor: Allocation 20 int" << std::endl;
}
~Container() {
if (m_Data) {
delete[] m_Data;
m_Data = NULL;
}
}
Container(const Container & obj) {
//Allocate an array of 20 int on heap
m_Data = new int[20];

//Copy the data from passed object
for (int i = 0; i < 20; i++)
m_Data[i] = obj.m_Data[i];

std::cout << "Copy Constructor: Allocation 20 int" << std::endl;
}
};

这个类中,我们每次创建一个容器对象,其默认构造器都会分配一个20个int大的数组在堆上。同理,容器类的靠背构造器也会做类似的工作,首先是分配数组,然后将传递进的数组内容拷贝到新创建出数组里。

一般来说,我们使用工厂类来创建对象:

1
2
3
4
5
Container getContainer() 
{
Container obj;
return obj;
}

假设我们创建一个容器类型的vector,每次插入一个由getContainer()返回的对象:

1
2
3
4
5
6
7
8
9
int main() {
// Create a vector of Container Type
std::vector<Container> vecOfContainers;

//Add object returned by function into the vector
vecOfContainers.push_back(getContainer());

return 0;
}

vector里的一个对象,实际上背后我们为此创建了两个对象。

  • 一个是在getContainer()使用Container类的默认函数创建出来的;
  • 一个是在加入vector中使用Container类的拷贝构造函数创建出来的;

这样每一个对象,都会带来两次在heap上创建数组。

Solving Problem of Temporary Objects using rvalue references & Move Constructor

getContainer()函数实际上是一个右值,所以可以被右值引用指向。因此为了实现这个目的,我们可以重载一个新的构造器,即move构造器:

Move Constructor

Move构造函数将右值引用作为参数,并重载该函数。在move构造函数中,我们只是将传递对象的成员变量move到新对象的成员变量中,而不是分配新内存。

1
2
3
4
5
6
7
8
9
10
Container(Container && obj)
{
// Just copy the pointer
m_Data = obj.m_Data;

// Set the passed object's member to NULL
obj.m_Data = NULL;

std::cout<<"Move Constructor"<<std::endl;
}

在移动构造函数中,我们只是复制了指针,即成员变量m_Data指向了堆上的相同内存,然后将传递进的对象的m_Data设置为NULL。所以我们并没有在该构造函数中分配新的内存,而是转移了内存的控制。

现在再将getContainer()返回的对象push到数组中,由于getContainer()是一个右值,因此会调用container类的move构造器,此时只会创建一个整数数组。