复制构造和移动构造

复制构造是这样的:
在对象被复制后临时对象和复制构造的对象各自占有不同的同样大小的堆内存,就是一个副本。

移动构造是这样的:
就是让这个临时对象它原本控制的内存的空间转移给构造出来的对象,这样就相当于把它移动过去了。

复制构造和移动构造的差别:
这种情况下,我们觉得这个临时对象完成了复制构造后,就不需要它了,我们就没有必要去首先产生一个副本,然后析构这个临时对象,这样费两遍事,又占用内存空间,干脆将临时对象原本的堆内存直接转给构造的对象就行了。 当临时对象在被复制后,就不再被利用了。我们完全可以把临时对象的资源直接移动,这样就避免了多余的复制构造。

什么时候该触发移动构造呢?
如果临时对象即将消亡,并且它里面的资源是需要被再利用的,这个时候我们就可以触发移动构造。

std::move
std::move函数可以以非常简单的方式将左值转换为右值引用。
通过std::move,可以避免不必要的拷贝操作。
std::move是为性能而生。

int main()
{
string str = "Hello";//这里假设我们只需要将str的内容放到vector中,完成以后永远都不需要再用到str
vector<string> v;
//调用常规的拷贝构造函数,新建字符数组,拷贝数据
v.push_back(str);
cout << "After copy, str is :" << str << endl;
//先把str转为右值引用,然后调用移动构造函数转交所有权
v.push_back(move(str));
cout << "After move, str is:" << str << endl;
cout << "The contents of the vector are:{" << v[0]
<< "," << v[1] << "}"<<endl;

system("pause");
return 0;
}


string类的数据成员简单的来说其实最关键的就是一个char*(至于其他的东西不重要),指向动态内存分配空间的首地址。这里可以看我的文章:string类的简单实现



再看一个例子:

int main()
{
string s1 = "apple";
string s2 = "banana";

s1 = move(s2); 
// s1=="banana"
cout << s2 << endl;

vector<int> v1;
vector<int> v2 = { 1, 2, 3, 4 };

v1 = move(v2);  //从v2转移到v1
//v1=={1, 2, 3, 4}
system("pause");
return 0;

}


可以看到A的构造函数调用一次,拷贝构造函数调用了一次,构造函数和拷贝构造函数是消耗比较大的,这里是否可以避免拷贝构造?C++11做到了这一点。(注意,上面这个例子中,虽然getA()是一个右值,但是由于没有自定义move constructor,所以调用了默认的copy constructor。如果对象中有堆内存管理,必须定义move constructor。)

2.自定义A的移动构造函数,代码如下:

class A
{
public:
A() { cout << "Constructor" << endl; }
A(const A&) { cout << "Copy Constructor" << endl; }
A(const A&&) { cout << "Move Constructor" << endl; }
~A() {}
};

A getA()
{
A a;
return a;
}

int main()
{
A a = getA();// getA()是一个右值
system("pause");
return 0;
}
调用拷贝构造函数,而是调用移动构造。这里并没有看到移动构造的优点。

3.修改代码,给A类添加一个成员变量如下:

class B
{
public:
B() {}
B(const B&) { cout << "B Copy Constructor" << endl; }
};

class A
{
public:
A() : pb(new B()) { cout << "A Constructor" << endl; }
A(const A& src) :pb(new B(*(src.pb)))//深拷贝
{
cout << "A Copy Constructor" << endl;
}
A(A&& src) :pb(src.pb)
{
src.pb = nullptr;//这里是关键,这样以后,当src.pb被delete时,由于其为空指针,并不会释放原来的堆内存
cout << "A Move Constructor" << endl;
}
~A() { delete pb; }

private:
B* pb;
};

static A getA()
{
A a;
cout << "================================================" << endl;
return a;
}

int main()
{
A a = getA();
cout << "================================================" << endl;
A a1(a);
system("pause");
return 0;
}


A a = getA();调用的是A的移动构造,A a1(a); 调用的是A的拷贝构造。A的拷贝构造需要对成员变量B进行深拷贝,而A的移动构造不需要,很明显,A的移动构造效率高。

4.std::move语句可以将左值变为右值而避免拷贝构造,修改代码如下:

class B
{
public:
B() {}
B(const B&) { cout << "B Copy Constructor" << endl; }
};

class A
{
public:
A() : pb(new B()) { cout << "A Constructor" << endl; }
A(const A& src) :pb(new B(*(src.pb)))//深拷贝
{
cout << "A Copy Constructor" << endl;
}
A(A&& src) :pb(src.pb)
{
src.pb = nullptr;
cout << "A Move Constructor" << endl;
}
~A() { delete pb; }

private:
B* pb;
};

static A getA()
{
A a;
cout << "================================================" << endl;
return a;
}

int main()
{
A a = getA();
cout << "================================================" << endl;
A a1(a);
cout << "================================================" << endl;
A a2(move(a));
system("pause");
return 0;
}


A a2(std::move(a));将a转换为右值,因此a2调用的是移动构造而不是拷贝构造。

5.赋值操作符也可以是移动赋值。

class B
{
public:
B() {}
B(const B&) { cout << "B Copy Constructor" << endl; }
};

class A
{
public:
A() : pb(new B()) { cout << "A Constructor" << endl; }
A(const A& src) :pb(new B(*(src.pb)))//深拷贝
{
cout << "A Copy Constructor" << endl;
}
A(A&& src) :pb(src.pb)
{
src.pb = nullptr;
cout << "A Move Constructor" << endl;
}


A& operator=(const A& src) noexcept//深拷贝
{
if (this == &src)
return *this;

delete pb;
pb = new B(*(src.pb));
cout << "operator=(const A& src)" << endl;
return *this;
}
A& operator=(A&& src) noexcept
{
if (this == &src)
return *this;

delete pb;
pb = src.pb;
src.pb = nullptr;
cout << "operator=(const A&& src)" << endl;
return *this;
}
~A() { delete pb; }

private:
B* pb;
};

static A getA()
{
A a;
cout << "================================================" << endl;
return a;
}

int main()
{
A a = getA();//移动构造
cout << "================================================" << endl;
A a1(a);//拷贝构造
cout << "================================================" << endl;
A a2(move(a));//移动构造
cout << "================================================" << endl;
a2 = getA();//移动赋值(因为getA()是右值)
cout << "================================================" << endl;
a2 = a1;//拷贝赋值(因为a1是左值)
system("pause");
return 0;
}

总之尽量给类添加移动构造和移动赋值函数,而减少拷贝构造和拷贝赋值的消耗。 移动构造,移动赋值要加上noexcept,用于通知标准库不抛出异常。

使用move函数转交unique_ptr的所有权
#include <iostream>
#include <memory>
using namespace std;

int main(int argc, char *argv[])
{
int*p1 = new int(56);
unique_ptr<int> up_int1(p1);
unique_ptr<int> up_int2= move(up_int1);//转交所有权(法1)
//unique_ptr<int> p2(p1.release()); //转交所有权(法2)

string* p2 = new string[2];//对象数组
p2[0] = "apple";
p2[1] = "banana";
unique_ptr<string[]> up_str1(p2);
unique_ptr<string[]> up_str2;
up_str2 = move(up_str1);

system("pause");
return 0;
}

没有评论: