构造函数
- 构造函数是一种特殊的成员函数,它在对象创建时自动调用,用于初始化对象的成员变量。
C++会为每个类提供一个默认构造函数,用户也可以自己定义一些带参数的构造函数。
1 2 3 4 5 6 7
| class MyClass { public: MyClass(int val) : x(val) {}; private: int x; };
|
构造函数可以是虚函数吗?
- 构造函数不能是虚函数!
- 虚函数的机制依赖于虚函数表,而虚表对象的建立需要在调用构造函数之后才能完成。构造函数是用来初始化对象的,而在对象的初始化阶段虚表对象还没有被建立,如果构造函数是虚函数会导致对象初始化和多态机制的矛盾。
浅拷贝和深拷贝?
浅拷贝只是简单地复制对象的值,而不复制对象锁拥有的资源或内存。
深拷贝不仅复制对象的值,还会新分配内存并复制对象所拥有的资源。
拷贝构造函数
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
| #include <cstring>
class MyClass { private: char* data; public: MyClass(const char* inputData) { data = new char[std::strlen(inputData) + 1]; std::strcpy(data, inputData); }
MyClass(const MyClass& other) { data = new char[std::strlen(other.data) + 1]; std::strcpy(data, other.data); }
~MyClass() { delete[] data; } };
|
赋值构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class MyClass {
public: MyClass& operator=(const MyClass& other) { if (this != &other) { delete[] data; data = new char[std::strlen(other.data) + 1]; std::strcpy(data, other.data); } return *this; } };
|
深拷贝示例
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| #include <cstring> #include <iostream>
class MyClass { private: char* data; public: MyClass(const char* inputData) { std::cout << "MyClass(const char* inputData)" << std::endl; data = new char[std::strlen(inputData) + 1]; std::strcpy(data, inputData); }
MyClass(const MyClass& other) { std::cout << "MyClass(const MyClass& other)" << std::endl; data = new char[std::strlen(other.data) + 1]; std::strcpy(data, other.data); }
MyClass& operator=(const MyClass& other) { std::cout << "MyClass& operator=(const MyClass& other)" << std::endl; if (this != &other) { delete[] data; data = new char[std::strlen(other.data) + 1]; std::strcpy(data, other.data); } return *this; }
~MyClass() { std::cout << "~MyClass()" << std::endl; delete[] data; } };
int main() { MyClass obj1("Hello");
MyClass obj2 = obj1; MyClass obj3("World");
obj3 = obj2;
return 0; }
|
移动构造和移动赋值运算符
移动构造函数和移动赋值运算符主要搭配移动语义来提高性能,特别是在需要转移大对象资源的时候,具体场景包括:
- 当函数返回一个对象时,用移动构造函数可以避免返回值拷贝;
- 当函数传递参数时,使用右值 + 移动构造函数可以避免参数拷贝。
- 当需要将一个大对象从一个容器移动到另一个容器时,用移动赋值运算符可以避免重复的资源分配和释放!
移动构造函数和移动赋值运算符的调用过程:通过std::move
或右值引用转移资源所有权;将原对象置于有效但未指定的状态,确保其析构函数能够安全地调用!
右值引用和std::move
右值引用:&&
符号表示,在函数声明中,若参数是右值引用,那么它表示这个参数可以接受右值而非左值,即只能绑定到临时对象或是要被销毁的对象。
例如:void foo(std::string&& str);
函数foo
只能接受右值std::string
。
std::move
:将一个左值显示转换成右值引用,即可顺利调用移动构造函数或移动赋值运算符。
1 2
| std::string s1 = "Hello"; std::string s2 = std::move(s1);
|
使用示例
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
| #include <cstring> #include <iostream>
class MyClass { private: int* ptr; public: MyClass(int value) { std::cout << "MyClass(int value)" << std::endl; ptr = new int(value); }
MyClass(const MyClass& other) { std::cout << "MyClass(const MyClass& other)" << std::endl; ptr = new int(*other.ptr); }
MyClass& operator=(const MyClass& other) { std::cout << "MyClass& operator=(const MyClass& other)" << std::endl; if (this != &other) { delete ptr; ptr = new int(*other.ptr); } return *this; }
MyClass(MyClass&& other) noexcept { std::cout << "MyClass(MyClass&& other)" << std::endl; this->ptr = other.ptr; other.ptr = nullptr; }
MyClass& operator=(MyClass&& other) noexcept { std::cout << "MyClass& operator=(MyClass&& other)" << std::endl; if (this != &other) { delete this->ptr; this->ptr = other.ptr; other.ptr = nullptr; } return *this; }
~MyClass() { std::cout << "~MyClass()" << std::endl; delete ptr; }
void printValue() { std::cout << *ptr << std::endl; } };
int main() { MyClass obj1(13);
MyClass obj2 = std::move(obj1);
MyClass obj3(23);
obj2.printValue(); obj3.printValue();
obj3 = std::move(obj2);
obj3.printValue();
return 0; }
|
注意事项
std::move()
只有一个作用:无论输入参数是左值还是右值,都利用static_cast
强制转化成右值。
- 右值可以触发移动构造函数或者移动赋值函数,一般默认原对象不再继续使用,以高效移动减少内存占用!
- 如果原对象仍需继续使用,亦可以在移动构造函数或移动赋值函数保留原对象!
析构函数
析构函数主要用于释放资源,防止内存泄露。
析构函数要声明为虚函数吗?
如果一个类可能被继承,并且需要在删除指向派生类对象的基类指针时确保正确调用派生类的析构函数,那么基类的析构函数必须是虚函数,否则,可能导致资源泄露!
虚函数的机制:当一个类的成员函数声明为虚函数时,C++会为该类生成一个虚函数表,存储指向虚函数的指针。
在运行时,基于当前对象的实际类型,虚函数表指针用于动态绑定,调用正确的函数版本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <cstring> #include <iostream>
class Base { public: virtual ~Base() { std::cout << "Base destructor" << std::endl; } };
class Derived : public Base { public: ~Derived() { std::cout << "Derived destructor" << std::endl; } };
int main() { Base *obj = new Derived(); delete obj;
return 0; }
|
- 如果基类的析构函数不是虚函数,当通过基类指针删除派生类对象时,只会调用基类的析构函数,而不会调用派生类的析构函数!