拷贝构造函数和移动构造函数

0x81 构造函数

这篇文章是继续C++学习的内容,由于最近事情很多,一直没机会静下心来学习,能做的只是晚上睡前抽点时间重新读一读《程序员的自我修养这本书》,不得不说好书每次重新读都有新的收获。牢骚就发到这,现在进入正题,聊聊构造函数。构造函数是面向对象编程最常见不过的东西了,一般情况下构造方法的名字通常与类名相同且声明时没有返回值,下面我们讨论一下C++中不同的构造函数。

0x82 默认构造函数

最常见的构造函数之一,无论是Java还是C++,当我们声明一个对象但不传入任何初始化参数时,这个构造函数就会调用,如果我们可以声明复写默认构造函数,来进行一些内部参数的初始化操作。但是这里有一个十分关键的地方,就是如果我们声明了含参构造函数,那编译器就不会主动为我们插入默认构造函数,也就是说不能使用默认构造方法声明对象,要想使用还需要手动声明无参构造函数。

0x83 析构函数

析构函数与构造函数对应,它们通常成对出现,构造函数用于使用对象时申请内存,而析构函数则用于回收对象时进行清理操作。我是在学习C++时才了解到析构函数,由于像Java等面向对象语言都是自带垃圾回收器,通常不需要我们手动清理内存,所以一般情况下不需要使用类似析构函数的东西来清理内存,但是C++能够非常直接的对内存进行操作,可以“随意”的申请某一块内存使用,为避免内存泄露我们通常需要回收使用的内存,而析构函数刚好可以hook到这个时机,它有点类似Android中Activity的onDestroy回调。

0x84 赋值构造函数

我们先来看一下头文件定义:

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
27
28
29
30
 1 #ifndef __TYPE_CLASS_H__
2 #define __TYPE_CLASS_H__
3 namespace type_class {
4 void test();
5 class Base {
6 public:
7 Base();
8 Base(int a);
9 Base(const Base& other);
10 Base& operator= (const Base& other);
11
12 Base(Base&& other);
13 Base& operator= (Base&& other);
14 ~Base();
15
16 protected:
17 int getMemberB() {
18 return memberB;
19 }
20 int deleteC(int a ,int b = 100,bool test=true);
21
22 private:
23 static const int size = 512;
24 int memberA;
25 int memberB;
26 int* pMemberC;
27 };
28 };
29
30 #endif

我们可以使用以下这种方式为类里的成员变量赋初值:

1
2
3
4
5
6
7
Base::Base() : memberA(0), memberB(100), pMemberC(new int[size]) {
cout << "In default constructor" << endl;
}

Base::Base(int a) : memberA{a}, memberB{100}, pMemberC{new int[Base::size]} {
cout << "In normal constructor" << endl;
}

C++中的成员变量会生成对应的赋值函数用于在构造方法调用时赋值,这种写法比Java的构造方法中=式赋值要更加条理清晰。

0x85 拷贝构造函数

拷贝构造函数的调用发生在当某个对象需要“背靠背”的时候,比如使用一个对象初始化另一个对象,或者以值传递的方式传递参数和返回值,都会调用拷贝构造函数。我们通常需要自行实现拷贝构造函数和拷贝赋值符,因为编译器默认生成的拷贝是浅拷贝且效率不高,而浅拷贝很容易带来莫名其妙的问题。简单的写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Base::Base(const Base& other) : memberA{other.memberA},memberB{other.memberB},pMemberC{nullptr}{
cout << "In copy constructor" << endl;
if (other.pMemberC != nullptr) {
pMemberC = new int[size];
memcpy(pMemberC, other.pMemberC, size);
}
}

Base& Base::operator=(const Base& other) {
this->memberA = other.memberA;
(*this).memberB = other.memberB;
if (pMemberC != nullptr) {
delete[] pMemberC;
pMemberC = nullptr;
}
if (other.pMemberC != nullptr) {
pMemberC = new int[size];
memcpy(pMemberC, other.pMemberC, size);
}
return *this;
}

我们使用new和memcpy进行数据拷贝来完成深拷贝。

0x86 移动构造函数

最后说一下这个移动构造函数,它的声明方式和拷贝构造函数类似,只是没有const关键字并多了一个&取址符,与拷贝构造不同的是它并不是进行拷贝而是真的进行转移,执行完毕后源对象转移到新对象,而源对象将丢失其原有的内容,这种情况只发生在目的对象是匿名对象。这个语法是我在学习C++11时新遇到的,之前大学时期学的C++98应该是不支持的,并且实际上这种方式能带来更高效的内存管理,因为它不涉及新内存的申请:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Base::Base(Base&& other) : memberA(other.memberA), memberB(other.memberB), pMemberC(other.pMemberC) {
cout << "In move constructor" << endl;
other.pMemberC = nullptr;
}

Base& Base::operator=(Base&& other) {
memberA = other.memberA;
memberB = other.memberB;
if (pMemberC != nullptr) {
delete[] pMemberC;
pMemberC = nullptr;
}
pMemberC = other.pMemberC;
other.pMemberC = nullptr;
}

虽然我定义了移动构造函数并且打印了log,但是经过实验并不会触发。经过查阅,目前的编译器已经用一种返回值优化的方式优化了大多数可能调用的情况,所以它并不会被调用。

C++11的内容非常的多,并不是几个字就能说的明白,还希望自己做的笔记能为自己起到一定的备忘的作用,哪怕完全将这些抛于脑后,依托自己写的笔记仍然可以快速的拿回所需的知识。

技术的道路还有很长要走,能不能坚持不懈地走下去还要看自己的毅力和是否能一直持有磨灭不掉的兴趣,自己虽然有时也会疲惫,但只要不倦就会一直搞下去。