C++進(jìn)階異常處理與智能指針實(shí)戰(zhàn)指南
C++ 進(jìn)階:異常處理與智能指針實(shí)戰(zhàn)指南
在 C++ 開(kāi)發(fā)中,“錯(cuò)誤處理” 與 “資源管理” 是兩大核心痛點(diǎn)。傳統(tǒng) C 語(yǔ)言的錯(cuò)誤處理方式繁瑣且脆弱,而手動(dòng)管理內(nèi)存又容易因異常導(dǎo)致泄漏。本文將從異常機(jī)制入手,逐步過(guò)渡到智能指針,帶你掌握 C++ 中可靠編程的關(guān)鍵技術(shù),解決 “如何優(yōu)雅處理錯(cuò)誤” 與 “如何安全管理資源” 兩大難題。
一、C++ 異常:告別錯(cuò)誤碼的優(yōu)雅解決方案
在 C 語(yǔ)言中,我們習(xí)慣用assert終止程序或返回錯(cuò)誤碼處理問(wèn)題,但這些方式存在明顯局限。C++ 異常機(jī)制的出現(xiàn),讓錯(cuò)誤處理更靈活、信息更完整。
1.1 先看 C 語(yǔ)言錯(cuò)誤處理的痛點(diǎn)
C 語(yǔ)言處理錯(cuò)誤的兩種核心方式,都有難以規(guī)避的缺陷:
- 終止程序(如 assert):過(guò)于粗暴,用戶無(wú)法接受(比如內(nèi)存錯(cuò)誤直接崩潰);
- 返回錯(cuò)誤碼:需要手動(dòng)檢查每個(gè)函數(shù)返回值,深層調(diào)用鏈中需 “層層傳遞” 錯(cuò)誤,代碼冗余且易遺漏。
舉個(gè)例子,若ConnectSql函數(shù)返回錯(cuò)誤碼,調(diào)用鏈需逐層傳遞才能讓外層處理:
// C語(yǔ)言風(fēng)格:錯(cuò)誤碼層層傳遞的冗余
int ConnectSql() {
if (權(quán)限不足) return 1;
if (連接失敗) return 2;
return 0;
}
int ServerStart() {
int ret = ConnectSql();
if (ret != 0) return ret; // 手動(dòng)傳遞錯(cuò)誤碼
int fd = socket();
if (fd < 0) return errno; // 再傳遞系統(tǒng)錯(cuò)誤碼
}
int main() {
int ret = ServerStart();
if (ret != 0) {
// 還需根據(jù)錯(cuò)誤碼查表判斷具體問(wèn)題
printf("錯(cuò)誤碼:%d\n", ret);
}
return 0;
}1.2 異常的基本用法:try/throw/catch
C++ 通過(guò)try(保護(hù)代碼)、throw(拋出異常)、catch(捕獲異常)三段式處理錯(cuò)誤,核心邏輯是 “哪里出錯(cuò)拋哪里,哪里能處理哪里接”。
語(yǔ)法框架
try {
// 可能拋出異常的“保護(hù)代碼”
函數(shù)調(diào)用或危險(xiǎn)操作;
} catch (異常類型1 e1) {
// 處理類型1的異常
} catch (異常類型2 e2) {
// 處理類型2的異常
} catch (...) {
// 捕獲所有未匹配的異常(兜底,避免程序崩潰)
}實(shí)戰(zhàn)示例:除 0 錯(cuò)誤處理
用異常重構(gòu) “除 0 錯(cuò)誤”,無(wú)需層層傳遞錯(cuò)誤碼:
#include <iostream>
using namespace std;
// 發(fā)生除0時(shí)拋出異常
double Division(int a, int b) {
if (b == 0) {
// 拋出字符串異常(也可拋?zhàn)远x對(duì)象)
throw "Division by zero condition!";
}
return (double)a / b;
}
void Func() {
int len, time;
cin >> len >> time;
// 若Division拋異常,直接跳轉(zhuǎn)到catch
cout << Division(len, time) << endl;
}
int main() {
try {
Func();
} catch (const char* errmsg) {
// 捕獲字符串類型異常,打印錯(cuò)誤信息
cout << "錯(cuò)誤:" << errmsg << endl;
} catch (...) {
// 兜底:捕獲所有其他類型異常
cout << "未知異常" << endl;
}
return 0;
}1.3 異常的核心規(guī)則:必須掌握的細(xì)節(jié)
異常的拋出與捕獲并非 “隨便匹配”,需遵守以下規(guī)則,否則易導(dǎo)致程序崩潰:
(1)匹配原則:類型決定捕獲邏輯
- 異常對(duì)象的類型決定了哪個(gè)
catch會(huì)被激活; - 允許 “派生類對(duì)象拋,基類捕獲”(實(shí)戰(zhàn)中常用此特性設(shè)計(jì)異常體系);
catch(...)是 “萬(wàn)能捕獲”,但無(wú)法獲取異常具體信息,需謹(jǐn)慎使用。
(2)棧展開(kāi):從拋出點(diǎn)找捕獲點(diǎn)
若throw不在try內(nèi),或try后無(wú)匹配的catch,會(huì)觸發(fā) “棧展開(kāi)”:
- 退出當(dāng)前函數(shù)棧,回到調(diào)用者的棧幀;
- 重復(fù)檢查調(diào)用者的
try/catch,直到找到匹配的catch; - 若一直找到
main函數(shù)仍無(wú)匹配,程序直接終止。
(3)異常重新拋出:部分處理后移交外層
若單個(gè)catch無(wú)法完全處理異常(比如僅釋放資源),可通過(guò)throw;重新拋出,讓外層處理:
void Func() {
// 申請(qǐng)資源(若拋異常需釋放)
int* array = new int[10];
try {
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
} catch (...) {
// 先釋放資源,再重新拋出異常
cout << "釋放array:" << array << endl;
delete[] array;
throw; // 移交外層處理
}
// 正常執(zhí)行時(shí)釋放資源
delete[] array;
}
1.4 異常安全:那些不能踩的坑
異常雖好,但若使用不當(dāng),會(huì)導(dǎo)致資源泄漏、對(duì)象不完整等問(wèn)題,核心注意兩點(diǎn):
(1)構(gòu)造函數(shù):盡量不拋異常
構(gòu)造函數(shù)負(fù)責(zé)對(duì)象初始化,若中途拋異常,對(duì)象可能處于 “半初始化” 狀態(tài)(部分成員已創(chuàng)建,部分未創(chuàng)建),導(dǎo)致資源泄漏。
(2)析構(gòu)函數(shù):絕對(duì)不拋異常
析構(gòu)函數(shù)負(fù)責(zé)資源清理(如delete、關(guān)閉文件),若拋異常:
- 若已有一個(gè)異常在處理中,會(huì)直接終止程序;
- 可能導(dǎo)致資源未釋放(如
delete未執(zhí)行)。
1.5 實(shí)戰(zhàn):自定義異常體系(企業(yè)級(jí)規(guī)范)
實(shí)際項(xiàng)目中,若隨意拋int、string等零散類型,外層無(wú)法統(tǒng)一處理。規(guī)范做法是設(shè)計(jì)繼承式異常體系:定義一個(gè)基類,所有具體異常繼承自它,外層只需捕獲基類即可。
企業(yè)級(jí)異常體系示例
#include <string>
using namespace std;
// 異?;?
class Exception {
public:
Exception(const string& errmsg, int id)
: _errmsg(errmsg), _id(id) {}
// 虛函數(shù):支持多態(tài),子類重寫錯(cuò)誤信息
virtual string what() const {
return _errmsg;
}
protected:
string _errmsg; // 錯(cuò)誤描述
int _id; // 錯(cuò)誤編號(hào)(便于定位問(wèn)題)
};
// SQL異常(派生類)
class SqlException : public Exception {
public:
SqlException(const string& errmsg, int id, const string& sql)
: Exception(errmsg, id), _sql(sql) {}
// 重寫what,添加SQL信息
virtual string what() const override {
string str = "SqlException: ";
str += _errmsg;
str += " (SQL: ";
str += _sql;
str += ")";
return str;
}
private:
string _sql; // 出問(wèn)題的SQL語(yǔ)句
};
// 緩存異常(派生類)
class CacheException : public Exception {
public:
CacheException(const string& errmsg, int id)
: Exception(errmsg, id) {}
virtual string what() const override {
return "CacheException: " + _errmsg;
}
};
// 模擬SQL操作:隨機(jī)拋異常
void SQLMgr() {
srand(time(0));
if (rand() % 7 == 0) {
throw SqlException("權(quán)限不足", 100, "select * from user where name='張三'");
}
}
// 模擬緩存操作:隨機(jī)拋異常
void CacheMgr() {
srand(time(0));
if (rand() % 5 == 0) {
throw CacheException("數(shù)據(jù)不存在", 101);
}
SQLMgr(); // 調(diào)用SQL操作
}
int main() {
while (1) {
this_thread::sleep_for(chrono::seconds(1));
try {
CacheMgr();
cout << "操作成功" << endl;
} catch (const Exception& e) {
// 捕獲基類,統(tǒng)一處理所有異常(多態(tài)生效)
cout << "捕獲異常:" << e.what() << endl;
} catch (...) {
cout << "未知異常" << endl;
}
}
return 0;
}優(yōu)勢(shì)
- 外層只需一個(gè)
catch(const Exception& e),即可處理所有派生類異常; - 錯(cuò)誤信息結(jié)構(gòu)化(含錯(cuò)誤編號(hào)、SQL 語(yǔ)句等),便于定位問(wèn)題;
- 擴(kuò)展性強(qiáng):新增異常類型只需繼承基類,無(wú)需修改外層捕獲邏輯。
1.6 標(biāo)準(zhǔn)庫(kù)異常體系:了解即可
C++ 標(biāo)準(zhǔn)庫(kù)定義了一套異常體系(頭文件<exception>),所有異常繼承自std::exception,但實(shí)際中很少直接使用 —— 因?yàn)樵O(shè)計(jì)較簡(jiǎn)單,無(wú)法滿足復(fù)雜業(yè)務(wù)需求(如無(wú)法攜帶錯(cuò)誤編號(hào)、SQL 語(yǔ)句等)。
核心繼承關(guān)系:
std::exception ├─ std::bad_alloc(new失敗時(shí)拋) ├─ std::bad_cast(dynamic_cast失敗時(shí)拋) ├─ std::logic_error(邏輯錯(cuò)誤,如參數(shù)無(wú)效) │ ├─ std::invalid_argument(無(wú)效參數(shù)) │ └─ std::out_of_range(越界,如vector::at) └─ std::runtime_error(運(yùn)行時(shí)錯(cuò)誤) └─ std::overflow_error(算術(shù)溢出)
使用示例(捕獲vector越界異常):
#include <vector>
#include <exception>
int main() {
try {
vector<int> v(10);
v.at(10) = 100; // 越界,拋out_of_range
} catch (const exception& e) {
// 調(diào)用what()獲取錯(cuò)誤信息
cout << e.what() << endl; // 輸出:vector::_M_range_check: __n (which is 10) >= this->size() (which is 10)
}
return 0;
}1.7 異常的優(yōu)缺點(diǎn)總結(jié)
| 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|
| 錯(cuò)誤信息完整(可攜帶上下文,如 SQL 語(yǔ)句) | 執(zhí)行流跳轉(zhuǎn)混亂,調(diào)試難度增加 |
| 無(wú)需層層傳遞錯(cuò)誤碼,代碼更簡(jiǎn)潔 | 存在輕微性能開(kāi)銷(現(xiàn)代硬件可忽略) |
| 支持構(gòu)造函數(shù) / 運(yùn)算符重載等無(wú)返回值場(chǎng)景 | 易導(dǎo)致資源泄漏(需配合智能指針解決) |
| 兼容第三方庫(kù)(如 boost、gtest) | 標(biāo)準(zhǔn)庫(kù)異常體系不實(shí)用,需自定義 |
結(jié)論:異常利大于弊,是 C++ 錯(cuò)誤處理的主流方案,關(guān)鍵是配合智能指針解決資源泄漏問(wèn)題。
二、智能指針:解決異常資源泄漏的 “神器”
異常會(huì)導(dǎo)致代碼執(zhí)行流跳變,若new后拋異常,delete可能無(wú)法執(zhí)行,進(jìn)而引發(fā)內(nèi)存泄漏。智能指針的出現(xiàn),正是通過(guò)RAII 思想,讓資源自動(dòng)釋放。
2.1 先看異常引發(fā)的隱患:內(nèi)存泄漏
以下代碼中,若Func()拋異常,delete p會(huì)被跳過(guò),導(dǎo)致內(nèi)存泄漏:
void Func() {
throw "模擬異常"; // 拋出異常
}
void Test() {
int* p = new int; // 申請(qǐng)內(nèi)存
Func(); // 拋異常,執(zhí)行流跳走
delete p; // 永遠(yuǎn)不會(huì)執(zhí)行,內(nèi)存泄漏
}內(nèi)存泄漏定義:程序分配內(nèi)存后,因設(shè)計(jì)錯(cuò)誤失去對(duì)該內(nèi)存的控制,導(dǎo)致內(nèi)存無(wú)法復(fù)用,長(zhǎng)期運(yùn)行會(huì)使程序響應(yīng)變慢甚至卡死。
2.2 RAII 思想:智能指針的基石
RAII(Resource Acquisition Is Initialization),即 “資源獲取即初始化”,核心邏輯是:
- 構(gòu)造時(shí)獲取資源:將資源(如內(nèi)存、文件句柄)綁定到對(duì)象的生命周期;
- 析構(gòu)時(shí)釋放資源:對(duì)象銷毀時(shí),析構(gòu)函數(shù)自動(dòng)釋放資源,無(wú)需手動(dòng)調(diào)用。
基于 RAII 的簡(jiǎn)單智能指針
template<class T>
class SmartPtr {
public:
// 構(gòu)造:獲取資源(內(nèi)存)
SmartPtr(T* ptr = nullptr) : _ptr(ptr) {}
// 析構(gòu):釋放資源(內(nèi)存)
~SmartPtr() {
if (_ptr) {
delete _ptr;
cout << "釋放內(nèi)存:" << _ptr << endl;
}
}
// 重載*和->,讓SmartPtr像普通指針一樣使用
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
private:
T* _ptr; // 管理的資源(內(nèi)存地址)
};
// 測(cè)試:即使拋異常,內(nèi)存也會(huì)自動(dòng)釋放
void Test() {
SmartPtr<int> sp(new int); // 構(gòu)造:獲取內(nèi)存
*sp = 10; // 像普通指針一樣使用
throw "模擬異常"; // 拋異常,sp對(duì)象銷毀時(shí)調(diào)用析構(gòu)
// 無(wú)需手動(dòng)delete,析構(gòu)自動(dòng)釋放
}核心優(yōu)勢(shì)
- 無(wú)需顯式釋放資源,避免人為遺漏;
- 即使發(fā)生異常,對(duì)象也會(huì)被銷毀(棧對(duì)象在棧展開(kāi)時(shí)自動(dòng)析構(gòu)),資源必然釋放。
三、C++ 智能指針全家桶:原理與實(shí)戰(zhàn)
C++ 標(biāo)準(zhǔn)庫(kù)提供了 4 種智能指針,分別應(yīng)對(duì)不同場(chǎng)景,其中auto_ptr已被淘汰,重點(diǎn)掌握unique_ptr、shared_ptr和weak_ptr。
3.1 auto_ptr:失敗的早期嘗試(避坑)
auto_ptr是 C++98 提供的第一個(gè)智能指針,采用 “管理權(quán)轉(zhuǎn)移” 機(jī)制,但存在嚴(yán)重缺陷,企業(yè)中已明確禁止使用。
缺陷:管理權(quán)轉(zhuǎn)移導(dǎo)致懸空指針
當(dāng)auto_ptr對(duì)象拷貝或賦值時(shí),會(huì)轉(zhuǎn)移資源的管理權(quán),原對(duì)象變?yōu)?“懸空指針”(指向 nullptr),訪問(wèn)原對(duì)象會(huì)崩潰:
#include <memory> // auto_ptr所在頭文件
int main() {
auto_ptr<int> sp1(new int(10));
auto_ptr<int> sp2 = sp1; // 管理權(quán)轉(zhuǎn)移:sp1失去資源,sp2擁有資源
*sp2 = 20; // 正常:sp2擁有資源
*sp1 = 30; // 崩潰:sp1已懸空(指向nullptr)
return 0;
}結(jié)論:永遠(yuǎn)不要使用auto_ptr,改用unique_ptr。
3.2 unique_ptr:獨(dú)占所有權(quán)的高效選擇
unique_ptr是 C++11 替代auto_ptr的方案,核心是獨(dú)占資源所有權(quán)—— 同一時(shí)間,只有一個(gè)unique_ptr能管理資源,禁止拷貝和賦值(直接刪除拷貝構(gòu)造和賦值運(yùn)算符)。
核心特性
- 禁止拷貝:
unique_ptr(const unique_ptr&) = delete; - 禁止賦值:
unique_ptr& operator=(const unique_ptr&) = delete; - 支持移動(dòng)語(yǔ)義:可通過(guò)
std::move轉(zhuǎn)移所有權(quán)(轉(zhuǎn)移后原對(duì)象懸空)。
實(shí)戰(zhàn)示例
#include <memory>
int main() {
// 1. 基本使用
unique_ptr<int> sp1(new int(10));
cout << *sp1 << endl; // 10
// 2. 禁止拷貝和賦值(編譯報(bào)錯(cuò))
// unique_ptr<int> sp2 = sp1; // 錯(cuò)誤:拷貝構(gòu)造已刪除
// sp1 = sp2; // 錯(cuò)誤:賦值運(yùn)算符已刪除
// 3. 移動(dòng)語(yǔ)義:轉(zhuǎn)移所有權(quán)
unique_ptr<int> sp2 = std::move(sp1); // 轉(zhuǎn)移后sp1懸空
cout << *sp2 << endl; // 10
// cout << *sp1 << endl; // 崩潰:sp1已懸空
// 4. 管理數(shù)組(需指定刪除器,或用unique_ptr<int[]>)
unique_ptr<int[]> sp3(new int[5]); // 專門用于數(shù)組,析構(gòu)時(shí)調(diào)用delete[]
sp3[0] = 1;
sp3[1] = 2;
return 0;
}適用場(chǎng)景
- 資源僅需一個(gè)所有者(如局部變量、函數(shù)返回值);
- 追求高效(無(wú)引用計(jì)數(shù)開(kāi)銷,性能接近普通指針)。
3.3 shared_ptr:共享所有權(quán)的靈活方案
unique_ptr不支持拷貝,無(wú)法滿足 “多對(duì)象共享資源” 的場(chǎng)景(如多線程共享數(shù)據(jù))。shared_ptr通過(guò)引用計(jì)數(shù)實(shí)現(xiàn)共享所有權(quán),核心是 “記錄資源被多少對(duì)象引用,最后一個(gè)對(duì)象銷毀時(shí)釋放資源”。
核心原理
- 每個(gè)
shared_ptr管理一個(gè)資源和一個(gè) “引用計(jì)數(shù)”(記錄共享該資源的shared_ptr數(shù)量); - 拷貝
shared_ptr時(shí),引用計(jì)數(shù) + 1; shared_ptr銷毀時(shí),引用計(jì)數(shù) - 1;- 若引用計(jì)數(shù)變?yōu)?0,釋放資源。
模擬實(shí)現(xiàn)核心代碼
template<class T>
class shared_ptr {
public:
// 構(gòu)造:資源+引用計(jì)數(shù)(初始為1)
shared_ptr(T* ptr = nullptr)
: _ptr(ptr), _pRefCount(new int(1)), _pmtx(new mutex) {}
// 拷貝構(gòu)造:引用計(jì)數(shù)+1
shared_ptr(const shared_ptr<T>& sp)
: _ptr(sp._ptr), _pRefCount(sp._pRefCount), _pmtx(sp._pmtx) {
AddRef(); // 引用計(jì)數(shù)+1(加鎖保證線程安全)
}
// 賦值運(yùn)算符:釋放當(dāng)前資源,引用新資源
shared_ptr<T>& operator=(const shared_ptr<T>& sp) {
if (_ptr != sp._ptr) { // 避免自賦值
Release(); // 釋放當(dāng)前資源(引用計(jì)數(shù)-1,為0則刪除)
_ptr = sp._ptr;
_pRefCount = sp._pRefCount;
_pmtx = sp._pmtx;
AddRef(); // 新資源引用計(jì)數(shù)+1
}
return *this;
}
// 析構(gòu):引用計(jì)數(shù)-1,為0則釋放資源
~shared_ptr() {
Release();
}
// 重載*和->
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
// 獲取引用計(jì)數(shù)
int use_count() const { return *_pRefCount; }
private:
// 引用計(jì)數(shù)+1(加鎖,線程安全)
void AddRef() {
_pmtx->lock();
(*_pRefCount)++;
_pmtx->unlock();
}
// 引用計(jì)數(shù)-1,為0則釋放資源
void Release() {
_pmtx->lock();
bool needDelete = false;
if (--(*_pRefCount) == 0) {
delete _ptr;
delete _pRefCount;
needDelete = true;
}
_pmtx->unlock();
if (needDelete) {
delete _pmtx; // 最后一個(gè)對(duì)象銷毀時(shí),刪除鎖
}
}
private:
T* _ptr; // 管理的資源
int* _pRefCount; // 引用計(jì)數(shù)(指針:所有共享對(duì)象共享同一計(jì)數(shù))
mutex* _pmtx; // 互斥鎖:保證引用計(jì)數(shù)操作線程安全
};實(shí)戰(zhàn)要點(diǎn)
- 線程安全:
- 引用計(jì)數(shù)的加減是線程安全的(內(nèi)部加鎖);
- 資源本身的訪問(wèn)不是線程安全的(需用戶手動(dòng)加鎖)。
- 自定義刪除器:
shared_ptr默認(rèn)用delete釋放資源,若資源是malloc分配的、數(shù)組或文件句柄,需自定義刪除器:
#include <cstdlib> // malloc/free
#include <cstdio> // FILE/fclose
// 1. 管理malloc分配的內(nèi)存(自定義刪除器)
void FreeFunc(int* ptr) {
free(ptr);
cout << "free內(nèi)存:" << ptr << endl;
}
shared_ptr<int> sp1((int*)malloc(4), FreeFunc);
// 2. 管理數(shù)組(用lambda作為刪除器)
shared_ptr<int> sp2(new int[5], [](int* ptr) {
delete[] ptr;
cout << "delete[]數(shù)組:" << ptr << endl;
});
// 3. 管理文件句柄
shared_ptr<FILE> sp3(fopen("test.txt", "w"), [](FILE* ptr) {
fclose(ptr);
cout << "關(guān)閉文件:" << ptr << endl;
});3.4 weak_ptr:破解 shared_ptr 循環(huán)引用
shared_ptr存在一個(gè)致命問(wèn)題:循環(huán)引用—— 兩個(gè)shared_ptr互相引用,導(dǎo)致引用計(jì)數(shù)無(wú)法歸零,資源永遠(yuǎn)無(wú)法釋放。
問(wèn)題示例:雙向鏈表節(jié)點(diǎn)
struct ListNode {
int _data;
shared_ptr<ListNode> _prev; // 指向前驅(qū)節(jié)點(diǎn)
shared_ptr<ListNode> _next; // 指向后繼節(jié)點(diǎn)
~ListNode() { cout << "~ListNode()" << endl; }
};
int main() {
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
cout << node1.use_count() << endl; // 1
cout << node2.use_count() << endl; // 1
node1->_next = node2; // node1的_next引用node2,node2計(jì)數(shù)變?yōu)?
node2->_prev = node1; // node2的_prev引用node1,node1計(jì)數(shù)變?yōu)?
// 析構(gòu)node1和node2:計(jì)數(shù)各減1,變?yōu)?(非0),資源不釋放
return 0;
}循環(huán)引用分析
node1和node2析構(gòu)時(shí),引用計(jì)數(shù)從 2 減到 1(因_next和_prev仍互相引用);- 只有
_next和_prev析構(gòu)時(shí),計(jì)數(shù)才會(huì)減到 0,但_next屬于node1,node1不釋放則_next不析構(gòu); - 最終形成 “死鎖”,資源永遠(yuǎn)無(wú)法釋放。
解決方案:用 weak_ptr 打破循環(huán)
weak_ptr是 “弱引用” 智能指針,特點(diǎn)是:
- 不增加引用計(jì)數(shù),僅觀察資源;
- 無(wú)法直接訪問(wèn)資源(需先通過(guò)
lock()轉(zhuǎn)為shared_ptr)。
修改鏈表節(jié)點(diǎn),將_prev和_next改為weak_ptr:
#include <memory>
struct ListNode {
int _data;
weak_ptr<ListNode> _prev; // 弱引用:不增加計(jì)數(shù)
weak_ptr<ListNode> _next; // 弱引用:不增加計(jì)數(shù)
~ListNode() { cout << "~ListNode()" << endl; }
};
int main() {
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
cout << node1.use_count() << endl; // 1
cout << node2.use_count() << endl; // 1
node1->_next = node2; // weak_ptr賦值,node2計(jì)數(shù)仍為1
node2->_prev = node1; // weak_ptr賦值,node1計(jì)數(shù)仍為1
// 析構(gòu)node1和node2:計(jì)數(shù)減到0,資源釋放(打印~ListNode())
return 0;
}weak_ptr 的使用場(chǎng)景
- 打破
shared_ptr的循環(huán)引用(如雙向鏈表、樹(shù)結(jié)構(gòu)); - 觀察資源是否存在(通過(guò)
lock()判斷:若資源存在,返回非空shared_ptr;否則返回空)。
四、C++11 與 boost 智能指針的淵源
C++11 的智能指針并非憑空出現(xiàn),而是借鑒了boost庫(kù)的設(shè)計(jì):
- C++98:僅提供
auto_ptr,設(shè)計(jì)缺陷明顯; - boost 庫(kù):提出
scoped_ptr(獨(dú)占)、shared_ptr(共享)、weak_ptr(弱引用),解決了auto_ptr的問(wèn)題; - C++ TR1:引入
shared_ptr,但非標(biāo)準(zhǔn); - C++11:正式納入
unique_ptr(對(duì)應(yīng)boost::scoped_ptr)、shared_ptr、weak_ptr,并優(yōu)化實(shí)現(xiàn)。
結(jié)論:C++11 智能指針是boost智能指針的 “標(biāo)準(zhǔn)化版本”,兼容性更好,無(wú)需額外依賴boost庫(kù)。
五、總結(jié):異常與智能指針的最佳實(shí)踐
- 異常使用規(guī)范:
- 定義繼承式異常體系,所有異常繼承自同一基類;
- 構(gòu)造函數(shù)盡量不拋異常,析構(gòu)函數(shù)絕對(duì)不拋異常;
- 外層用
catch(...)兜底,避免程序崩潰。
- 智能指針選擇優(yōu)先級(jí):
- 優(yōu)先用
unique_ptr(獨(dú)占資源,高效無(wú)開(kāi)銷); - 需共享資源時(shí)用
shared_ptr(注意循環(huán)引用,用weak_ptr解決); - 永遠(yuǎn)不用
auto_ptr。
- 資源管理原則:
- 內(nèi)存、文件句柄等資源,優(yōu)先用智能指針管理;
- 自定義資源(如網(wǎng)絡(luò)連接),用 RAII 思想封裝成類,讓資源自動(dòng)釋放。
通過(guò) “異常處理錯(cuò)誤”+“智能指針管理資源”,可大幅提升 C++ 程序的可靠性和可維護(hù)性,這也是企業(yè)級(jí) C++ 開(kāi)發(fā)的核心技術(shù)之一。
到此這篇關(guān)于C++異常處理與智能指針實(shí)戰(zhàn)指南的文章就介紹到這了,更多相關(guān)C++異常與智能指針內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
數(shù)據(jù)結(jié)構(gòu) 數(shù)組順序存儲(chǔ)詳細(xì)介紹
這篇文章主要介紹了數(shù)據(jù)結(jié)構(gòu) 數(shù)組順序存儲(chǔ)詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2017-05-05
Cocos2d-x UI開(kāi)發(fā)之CCControlSwitch控件類使用實(shí)例
這篇文章主要介紹了Cocos2d-x UI開(kāi)發(fā)之CCControlSwitch控件類使用實(shí)例,本文代碼中含大量注釋講解了CCControlSwitch控件類的使用,需要的朋友可以參考下2014-09-09
vs2019+win10配置boost庫(kù)的詳細(xì)教程
這篇文章主要介紹了vs2019+win10配置boost庫(kù),本文通過(guò)圖文實(shí)例相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06

