移动拷贝构造函数
1.什么是构造函数
“写在前面:文章中用的编译环境是windows下的vs 2019”
概念引入
有时在我们写代码的时候会忘记对数据进行初始化,在c++的类中,构造函数是一种特殊的成员函数,在每次创建创建一个类的时候会默认调用构造函数进行初始化工作。
构造函数用来完成一些必要的初始化工作,有了构造函数之后,就无需再单独写初始化函数,并且也不必担心忘记调用初始化函数,因为当你的代码中写了构造函数但是在创建对象的时候没有给构造函数传参编译器就会报错提示“类XXXX不存在默然构造函数”。
概念
1.名字与类名相同,可以有参数,但是不能有返回值(void也不行)
2.作用是对对象进行初始化工作,如给成员变量赋值等。
3.如果定义类时没有写构造函数,系统会生成一个默认的无参构造函数,默认构造函数没有参数,不做任何工作。
4.如果定义了构造函数,系统不再生成默认的无参构造函数
5.对象生成时构造函数自动调用,对象一旦生成,不能在其上再次执行构造函数
6.一个类也可以有多个构造函数,为重载关系
例子
#include<iostream>
using namespace std;
class Myclass
{
public:
Myclass(int num)
{
m_num = num;
}
int getNum()
{
return m_num;
}
private:
int m_num;
};
int main()
{
Myclass class1(100);
cout<<class1.getNum();
return 0;
}
上述例子定义了一个Myclass()构造函数来设置m_num的值,这样在每次定义对象的时候都必须初始化,解决了用户在写代码的时候忘记初始化的情况。
2.拷贝构造函数
首先对于普通类型的对象来说,它们之间的复制是很简单的,例如:
int a = 100;
int b = a;
而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量。
下面看一个类对象拷贝的简单例子。
#include<iostream>
using namespace std;
class Myclass
{
public:
Myclass(int num)
{
m_num = num;
}
int getNum()
{
return m_num;
}
private:
int m_num;
};
int main()
{
Myclass class1(100);
Myclass class2(class1);
cout << class1.getNum() << endl;
cout << class2.getNum()<< endl;
return 0;
}
可以看到他打印了两次100,系统为对象class2分配了内存并完成了与对象class1的复制过程。就类对象而言,相同类型的类对象是通过拷贝构造函数来完成整个复制过程的。
3.默认构造函数
如果用户没有定义构造函数,那么编译器会给类提供一个默认的构造函数,但是只要用户自定义了任意一个构造函数,那么编译器就不会提供默认的构造函数。
每一次定义对象系统都会默认的生成一个无参的默认构造函数,如下。
class Myclass
{
public:
Myclass()//系统会默认生成一个不带参数的构造函数
{
cout<<"hello world"<<endl;
}
};
int main()
{
Myclass class;
}
在定义对象的时候,系统会自动生成一个无带参数的构造函数但是这里我显示的写出来了,此时运行代码输出 hello world 证明在定义对象的时候确实调用了无参的构造函数,其实不写出来他也会隐式的调用一个无参的构造函数,但是他什么都不干。
那如果不想编译器自动的生成一个无参的构造函数或想要编译器使用自动生成的无参构造函数呢?
c++11引入了两个关键字:delete和default 关键字
Myclass() = default;//使用系统默认的构造函数
Myclass() = delete;//禁止使用默认构造函数和拷贝构造函数
4.移动拷贝构造函数
1.我们用对象class1初始化对象class2,对象class1我们就不在再使用,但是对象class1的空间还在(在析构之前),拷贝构造函数,就是把class1对象的内容复制一份到class2中,那么为什么我们不能直接使用class1属性所指向的空间,然后释放的时候只释放class1的空间并把他指向的那块空间的控制权交给class2,这样就避免了新的空间的分配,大大降低了构造的成本。这就是移动构造函数设计的初衷。
2.拷贝构造函数中,对于指针,我们一定要采用深拷贝,而移动构造函数中,对于指针,我们采用浅拷贝。浅拷贝之所以危险,是因为两个指针共同指向一片内存空间,若第一个指针将其释放,另一个指针的指向就不合法了,两次释放同一块地址就会产生异常。
3.移动构造函数的参数和拷贝构造函数不同,拷贝构造函数的参数是一个左值引用,但是移动构造函数的初值是一个右值引用。意味着,移动构造函数的参数是一个右值或者将亡值的引用。也就是说,只用用一个右值,或者将亡值初始化另一个对象的时候,才会调用移动构造函数。而move语句,就是将一个左值变成一个将亡值。
接下来用一个例子来展示。
#include<iostream>
using namespace std;
class student
{
public:
student(const char* name)
{
cout << "contructor student" << endl;
int len = strlen(name);
m_name = new char[len + 1];
strcpy_s(m_name, len + 1, name);
}
//拷贝构造函数
student(const student& other)
{
cout << "contructor copy student"<<endl;
//this->m_name = other.m_name;//浅拷贝,没有分配内存空间两次析构析构的是同一块空间就会有问题
int len = strlen(other.m_name);
m_name = new char[len + 1];
strcpy_s(this->m_name, len + 1, other.m_name);//深拷贝,分配内存空间
}
//移动拷贝构造函数
student(student&& other)
{
cout << "move contructor cpy student" << endl;
this->m_name = other.m_name;
cout << m_name << endl;
other.m_name = nullptr;
}
student()
{
m_name = nullptr;
}
private:
char * m_name;
};
int main()
{
student stu("zhangsan");
//拷贝构造函数
student stu3 = stu;//拷贝
student sut1(std::move(stu));//移动拷贝构造函数
return 0;
}
调试运行:
可以看到创建对象的时候调用了构造函数给m_name赋值并输出输出contructor student
student stu3 = stu调用了拷贝函数并采用深拷贝给m_name赋值并输出conctruct copy student
student stu1(std::move(stu))将左值引用改成右值引用并输出“zhangsan”。发生对象移动的前提是要移动的对象之后不再用了,这时就不会调用拷贝构造函数移交控制权给新的对象不发生值的拷贝。