关注泷羽Sec-静安 公众号,后台回复 找书+ C++Primer 获取C++相关电子书
对象的初始化和清理
在C++中,对象的初始化和清理是确保资源安全管理和程序健壮性的核心机制。以下是详细解释和示例:
一、为什么需要初始化?
-
避免未定义行为
classStudent {public:int score; // 未初始化时值为随机数voidshow(){ cout << score; }};intmain(){ Student s; // score是随机值 s.show(); // 输出不可预测的值}
-
资源正确获取
classDatabaseConn {public: DatabaseConn() { connect(); // 构造函数中建立数据库连接 }voidconnect(){ /* 实际连接操作 */ }};// 使用对象时自动完成初始化DatabaseConn db; // 创建即连接
二、为什么需要清理?
-
防止内存泄漏
classMemoryPool {private:int* data;public: MemoryPool(int size) { data = newint[size]; } ~MemoryPool() { delete[] data; } // 必须清理};voidfunc(){MemoryPool pool(100); // 离开作用域时自动调用析构函数释放内存}
-
确保资源释放
classFileHandler { FILE* file;public: FileHandler(constchar* name) { file = fopen(name, "r"); } ~FileHandler() { if(file) fclose(file); // 保证文件关闭 }};// 即使后续代码抛出异常,文件也会关闭
三、初始化和清理的黄金组合:RAII
资源获取即初始化(Resource Acquisition Is Initialization):
classMutexLock { mutex mtx;public: MutexLock() { mtx.lock(); } // 获取资源=初始化 ~MutexLock() { mtx.unlock(); } // 释放资源=清理};voidsafeOperation(){ MutexLock lock; // 构造时加锁// 临界区操作...} // 离开作用域自动解锁
四、特殊场景说明
-
异常安全性
classTransaction { Database& db;public: Transaction(Database& d) : db(d) { db.begin(); } ~Transaction() { if(!std::uncaught_exceptions()) { // 无异常时提交 db.commit(); } else { // 有异常时回滚 db.rollback(); } }};
-
深拷贝问题
classBadArray {int* arr;public: BadArray(int size) { arr = newint[size]; }// 错误:没有拷贝构造函数和operator= ~BadArray() { delete[] arr; }};voidproblem(){BadArray a1(10); BadArray a2 = a1; // 浅拷贝,两个对象指向同一内存} // 析构时同一内存被释放两次(崩溃!)
五、最佳实践建议
-
遵循3/5/0法则
-
需要自定义析构函数时,通常也需要自定义拷贝/移动操作 -
使用智能指针
classSafeResource {unique_ptr<Resource> res; // 自动管理资源public: SafeResource() : res(new Resource) {}// 无需显式定义析构函数};
-
初始化列表优先
classProperInit {constint id;string name;public: ProperInit(int i, string n) : id(i), // const成员必须初始化列表 name(n) {} // 比构造函数内赋值更高效};
总结必要性
|
|
|
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
通过构造函数和析构函数实现的RAII机制,是C++区别于其他语言的核心特性之一,它能有效保证:
-
资源安全性:100%避免资源泄漏 -
异常安全性:即使程序出错也能正确释放资源 -
代码简洁性:自动管理替代手动 new/delete
构造函数和析构函数
在C++中,构造函数(Constructor) 和 析构函数(Destructor) 是类的特殊成员函数,分别负责对象的初始化和清理工作。它们是面向对象编程中资源管理的核心机制。
一、构造函数(Constructor)
1. 核心作用
-
对象初始化:在对象创建时自动调用,如果指定了就不再提供。创建函数默认就有默认构造函数,默认拷贝函数。 -
分配资源:为对象的成员变量赋予初始值 -
强制初始化:确保对象使用时处于有效状态
2. 关键特性
-
函数名与类名完全相同 -
没有返回类型(连 void
也没有) -
可以有多个重载版本(参数不同) -
可以包含初始化列表(更高效的初始化方式),自动调用且调用一次
3. 代码示例
#include<iostream>usingnamespacestd;classPerseon{public: Perseon() {cout << "Perseon()调用" << endl; };private:};intmain(){ Perseon p;return0;}---Perseon()调用
二、析构函数(Destructor)
1. 核心作用
-
对象清理:在对象销毁时自动调用 -
释放资源:回收动态分配的内存、关闭文件等 -
防止泄漏:确保资源不会永久滞留
2. 关键特性
-
函数名为 ** ~类名
**(如~SmartArray
) -
没有参数和返回类型 -
每个类只能有一个析构函数,默认函数用完会销毁 -
通常声明为 virtual
(基类必备)
3. 代码示例
classSmartArray {// ... (其他成员同前)public:// 析构函数 ~SmartArray() {delete[] data; // 释放动态内存cout << "析构完成,释放" << size << "个int" << endl; }};voidtest(){SmartArray temp(5); // 构造//... 使用temp对象} // 离开作用域时自动调用析构函数
但是函数没走完是不会释放的。
带参构造函数和拷贝构造函数
#include<iostream>usingnamespacestd;classPerson{public:int PubId;//默认构造函数 Person() {cout << "Person()调用" << endl; };//带参数的构造函数 Person(int id, string name, string phone, string address) {cout << "Person(int id, string name, string phone, string address)调用" << endl; m_id = id; m_name = name; m_phone = phone; m_address = address; PubId = id;cout << "id:" << id << " name:" << name << " phone:" << phone << " address:" << address << endl; };//拷贝构造函数 Person(const Person& p) {cout << "Person(const Person& p)调用" << endl; PubId = p.PubId;cout << "id:" << p.m_id << " name:" << p.m_name << " phone:" << p.m_phone << " address:" << p.m_address << endl; };private:int m_id;string m_name;string m_phone;string m_address;};intmain(){ Person p1; //用默认构造时候不要加()Person p2(1, "张三", "123456", "北京"); // 括号法表达//相当于Person p2=Person(1, "张三", "123456", "北京") 显式法cout << "p2.PubId:" << p2.PubId << endl;Person p3(p2); // 也可以写为 Person p3=p2 隐式法cout << "p3.PubId:" << p3.PubId << endl;return0;}___Person()调用Person(int id, string name, string phone, string address)调用id:1 name:张三 phone:123456 address:北京p2.PubId:1Person(const Person& p)调用id:1 name:张三 phone:123456 address:北京p3.PubId:1
单单一个Person(10)
是匿名对象,当前行执行结束后自动回收。
三、调用时机对比
|
|
|
---|---|---|
|
|
|
|
new
|
delete
|
|
|
|
|
vector.push_back ) |
|
#include<iostream>usingnamespacestd;//拷贝构造函数的调用时机//1.使用一个对象去初始化另一个对象//2.值传递的方式给函数参数传值//3.值方式返回局部对象classPerson{public: Person() {cout << "Person的默认构造函数调用" << endl; } Person(int age) { m_Age = age;cout << "Person的有参构造函数调用" << endl; } Person(const Person& p) { m_Age = p.m_Age;cout << "Person的拷贝构造函数调用" << endl;cout << "age" << m_Age << endl; } ~Person() {cout << "Person的析构函数调用" << endl; }int m_Age =10 ;};voidtest01()//1.使用一个对象去初始一个对象{Person p1(20);Person p2(p1);};voiddowork(Person p){cout << "dowork p age " << p.m_Age << endl;};voidtest02()//2.值传递的方式给函数参数传值{ Person p; dowork(p);};Person dowork2()//Person 类型的函数名{ Person p1;cout << (int*)&p1 << endl;return p1;};voidtest03()//3.值方式返回局部对象{ Person p = dowork2();cout << (int*)&p << endl;};intmain(){cout << "1.使用一个对象去初始一个对象" << endl; test01();cout << "----------------------" << endl;cout << "2.值传递的方式给函数参数传值" << endl; test02();cout << "----------------------" << endl;cout << "3.值方式返回局部对象" << endl; test03();cout << "----------------------" << endl;return0;};---1.使用一个对象去初始一个对象Person的有参构造函数调用Person的拷贝构造函数调用age20Person的析构函数调用Person的析构函数调用----------------------2.值传递的方式给函数参数传值Person的默认构造函数调用Person的拷贝构造函数调用age10dowork p age 10Person的析构函数调用Person的析构函数调用----------------------3.值方式返回局部对象Person的默认构造函数调用000000000014FC44000000000014FC44Person的析构函数调用----------------------
四、典型应用场景
1. 文件自动管理
classAutoFile { FILE* file;public: AutoFile(constchar* name) { file = fopen(name, "r"); if(!file) throw runtime_error("文件打开失败"); } ~AutoFile() { if(file) fclose(file); }};// 使用示例:{AutoFile config("settings.cfg"); // 构造时打开文件// 使用文件...} // 离开作用域自动关闭文件(即使发生异常也会调用析构)
2. 线程锁自动释放
classMutexGuard { mutex& mtx;public: MutexGuard(mutex& m) : mtx(m) { mtx.lock(); } // 构造即加锁 ~MutexGuard() { mtx.unlock(); } // 析构即解锁};// 使用示例:mutex criticalMutex;voidsafeOperation(){MutexGuard lock(criticalMutex); // 进入作用域加锁// 临界区操作...} // 离开作用域自动解锁
五、必须注意的陷阱
1. 异常安全
classProblemClass {int* res1; FILE* res2;public: ProblemClass() { res1 = newint[100]; // 分配资源1 res2 = fopen("data"); // 分配资源2(可能失败)// 若此处抛出异常,res1内存泄漏! } ~ProblemClass() {delete[] res1;if(res2) fclose(res2); }};
改进方案:使用成员类管理独立资源(RAII):
classSafeIntArray {/* 智能管理int数组 */ };classSafeFile {/* 智能管理文件 */ };classSafeClass { SafeIntArray res1; // 成员对象自动管理资源 SafeFile res2;public: SafeClass() : res1(100), res2("data") {} // 异常安全};
2. 虚析构函数问题
classBase {public: ~Base() { cout << "Base析构"; } // 非虚析构函数};classDerived :public Base {int* data;public: Derived(int size) { data = newint[size]; } ~Derived() { delete[] data; cout << "Derived析构"; }};// 错误用法:Base* obj = new Derived(100);delete obj; // 只调用Base的析构函数 → 内存泄漏!
正确做法:
classBase {public:virtual ~Base() { ... } // 虚析构函数};
六、深拷贝和浅拷贝
1. 核心概念对比
|
|
|
---|---|---|
复制内容 |
|
|
内存关系 |
|
|
修改影响 |
|
|
资源释放 |
|
|
性能消耗 |
|
|
2. 浅拷贝问题演示*
代码示例
#include<iostream>#include<cstring>usingnamespacestd;classMyArray {private:int* data;int size;public:// 构造函数 MyArray(int s) : size(s) { data = newint[size];for (int i = 0; i < size; ++i) { data[i] = i; } }// 浅拷贝构造函数 MyArray(const MyArray& other) : data(other.data), size(other.size) {// 浅拷贝:仅复制指针和大小 }// 深拷贝构造函数MyArray deepCopy()const{MyArray copy(size);for (int i = 0; i < size; ++i) { copy.data[i] = data[i]; }return copy; }// 析构函数 ~MyArray() {delete[] data; }// 获取数据int* getData()const{return data; }// 获取大小intgetSize()const{return size; }// 打印数组voidprint()const{for (int i = 0; i < size; ++i) {cout << data[i] << " "; }cout << endl; }};voiddemonstrateShallowCopy(){MyArray arr1(5); MyArray arr2 = arr1; // 浅拷贝cout << "Original array (arr1): "; arr1.print();cout << "Shallow copy (arr2): "; arr2.print();// 修改浅拷贝后的数组 arr2.getData()[0] = 100;cout << "After modification:" << endl;cout << "Original array (arr1): "; arr1.print();cout << "Shallow copy (arr2): "; arr2.print();}voiddemonstrateDeepCopy(){MyArray arr1(5); MyArray arr2 = arr1.deepCopy(); // 深拷贝cout << "Original array (arr1): "; arr1.print();cout << "Deep copy (arr2): "; arr2.print();// 修改深拷贝后的数组 arr2.getData()[0] = 100;cout << "After modification:" << endl;cout << "Original array (arr1): "; arr1.print();cout << "Deep copy (arr2): "; arr2.print();}intmain(){cout << "Demonstrating Shallow Copy:" << endl; demonstrateShallowCopy();cout << endl;cout << "Demonstrating Deep Copy:" << endl; demonstrateDeepCopy();cout << endl;return0;}
问题分析
-
内存共享: arr1
和arr2
的data
指针指向同一内存地址 -
数据污染:修改任意对象的 data
会影响另一个对象 -
崩溃风险:双重 delete[]
导致内存错误
3. 深拷贝解决方案
关键实现点
-
拷贝构造函数:
new
分配新内存 + 数据逐项复制// 拷贝构造函数(深拷贝) MyArray(const MyArray& other) : size(other.size) { data = newint[size];for (int i = 0; i < size; ++i) { data[i] = other.data[i]; } }
-
赋值运算符:
-
检查自赋值 ( if(this != &other)
) -
先释放旧内存 ( delete[] data
) -
重新分配内存并复制数据 -
析构函数:安全释放对象自身持有的内存
4. 开发建议
-
三法则 (Rule of Three)如果类需要以下任一成员,通常需要全部显式定义:
-
析构函数 -
拷贝构造函数 -
拷贝赋值运算符 -
使用智能指针
#include<memory>classSafeArray {unique_ptr<int[]> data; // 自动管理内存int size;public: SafeArray(int sz) : size(sz), data(newint[sz]) {}// 不需要显式定义拷贝/析构函数!};
-
禁用拷贝对于不可复制的资源(如网络连接),明确禁止拷贝:
classNonCopyable { NonCopyable(const NonCopyable&) = delete; NonCopyable& operator=(const NonCopyable&) = delete;};
5. 实际应用场景
|
|
|
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
七、总结:构造函数 vs 析构函数
|
|
|
---|---|---|
调用时机 |
|
|
主要任务 |
|
|
重载 |
|
|
虚函数 |
|
|
默认生成 |
|
|
设计原则:
-
每个类必须至少有一个构造函数(可由编译器隐式生成) -
若类管理资源(内存/文件/网络连接等),必须显式定义析构函数 -
遵循RAII原则:资源获取即初始化,资源释放即析构
🔔 想要获取更多网络安全与编程技术干货?
关注 泷羽Sec-静安 公众号,与你一起探索前沿技术,分享实用的学习资源与工具。我们专注于深入分析,拒绝浮躁,只做最实用的技术分享!💻
扫描下方二维码,马上加入我们,共同成长!🌟
👉 长按或扫描二维码关注公众号
或者直接回复文章中的关键词,获取更多技术资料与书单推荐!📚
原文始发于微信公众号(泷羽Sec-静安):13x2类和对象-2-对象特性
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论