如有错误,希望大家批评指正,日拱一卒,功不唐捐。
前言
最近项目的技术栈是C++,涉及到了C++中类的相关知识,其中对于类中的拷贝构造函数、移动赋值运算符、移动构造函数和移动赋值运算符等函数有点混淆,在此记录下。
# 一、C++中的基本构造函数 C++是面向对象的语言,涉及到类,一涉及到类肯定就有成员变量和程序员函数等概念。成员变量就是这个类中相关的属性,比如以人为例,身高和体重就可以作为人这个类的成员变量。 我们在实际使用类的过程中,需要在对象初始化之初,就指定属性。即用来控制对象的初始化过程,这类特殊的函数就被称为**构造函数**。
构造函数有以下几个特点:
- 构造函数的名字和类名相同,但是构造函数不存在返回值。
- 构造函数可以有多个,可以通过参数的数量进行重载。
- 构造函数不能被声明为const。
- 如果没有显式提供构造函数,编译器会自动生成。
简单的示例如下:
class A{
public:
int m_a;
int m_b;
public:
//无参构造函数
A(){
m_a = 1;
m_a = 2;
}
//带参构造函数
A(int a, int b){
m_a = a;
m_b = b;
}
};
二、拷贝构造函数
1.拷贝构造函数定义
在实际使用过程中,可以通过类的成员变量来进行对象的初始化,但是也可以通过赋值的方式,比如下面的函数也可以定义为类A的构造函数。如果一个构造函数的第一个参数是自身类型的引用,且任何额外的参数都有默认值,这样的构造函数被称为拷贝构造函数,比如下面的构造函数。
.....
A(A& a){
m_a = a.m_a;
m_b = b.m_b;
}
同基本的构造函数一样,如果没有显式定义一个拷贝构造函数,则编译器会为我们定义一个。默认的实现方式就是将其参数的成员逐个拷贝到正在创建的对象中。比如下面创建对象的方式就会调用到拷贝构造函数来初始化对象:
.....
A a;
A a1 = a; //调用拷贝构造函数
A a2(a);//调用拷贝构造函数
2.默认拷贝构造函数存在的问题
默认情况下,拷贝构造函数不会存在任何问题,但是如果类成员变量存在指针变量的时候就会存在问题。
代码示例:
class A{
public:
int m_a;
char* sp;
public:
//无参构造函数
A(){
m_a = 1;
sp = new char[3];;
}
~A(){
delete sp;
}
};
int main(void){
A a;
A a1(a);
return 0;
}
Class A中定义了一个指针变量,当调用默认构造函数的场景下,不存在问题,但如果像上面main函数中创建a1的方式来创建对象,则会导致错误。
原因是:
在class中没有显式提供复制构造函数,编译器会为我们提供默认的拷贝构造函数,但是默认的拷贝构造函数并不能判断变量的类型,因此,编译器提供的默认构造函数会如下:
A(A& a){
m_a = a.m_a;
sp= b.sp;
}
这样导致的结果就a和a1两个对象的成员变量sp会指向同一块内存地址,当整个程序执行完(即析构函数被调用的时候),会对sp指向的内存释放两次。因此会导致程序崩溃。
3.解决方式
既然知道问题所在,那要做的就是提供默认的拷贝构造函数或者是禁止编译器生成默认构造函数咯。
解决方式一:提供默认构造函数
A(A &a){
m_a = a.m_a;
//重新分配内存 并拷贝
sp = new char[3];
std::strcpy(sp, a.sp);
}
解决方式二:禁止编译器生成默认构造函数
可以通过关键字delete来禁止编译器生成默认拷贝构造函数 只需要在类的生命中构造函数后加delete关键字即可。
//禁止编译器生成默认拷贝构造函数
A(A &a)=delete;
三 总结
其实编译器在没有默认拷贝构造的时候,生成拷贝构造函数,同时还会生成拷贝赋值运算符,移动构造函数,移动赋值运算符和析构函数,在实际工作中可以根据需求进行显式的定义或者默认生成。
可以遵守以下两条规则:
- 需要析构函数的类也需要拷贝和赋值操作。
- 需要拷贝操作的类也需要赋值操作。
其实拷贝和操作我理解是类似的,拷贝是通过构造函数调用,而赋值是通过数学运算符 = 进行。
四 参考
------C primer plus