[C++面試] RAII資源獲取即初始化(重點)

一、入門

1、什么是 RAII?

RAII(Resource Acquisition Is Initialization,資源獲取即初始化)是 C++ 的核心編程范式,核心思想是 ?將資源的生命周期與對象的生命周期綁定

  • ?資源獲取:在對象構造函數中獲取資源(如內存、文件句柄、鎖等)。
  • ?資源釋放:在對象析構函數中自動釋放資源。
    這種機制通過 C++ 的作用域規則和對象析構的確定性,確保資源始終被正確管理,避免泄漏
class FileHandler {  
public:  FileHandler(const char* filename) {  file = fopen(filename, "r");  if (!file) throw std::runtime_error("文件打開失敗");  }  ~FileHandler() { if (file) fclose(file); }  
private:  FILE* file;  
};  

?當?FileHandler?對象離開作用域時,析構函數自動關閉文件,無需手動調用?fclose

// 普通代碼(手動管理)
int* raw_ptr = new int(10);  // 手動
// ...(可能忘記 delete 導致內存泄漏)// RAII 代碼(自動管理)
std::unique_ptr<int> smart_ptr = std::make_unique<int>(10);  // 自動

二、進階

1、?為什么 RAII 能解決異常安全問題?

RAII 通過析構函數的確定性釋放資源,即使在異常發生時也能保證資源釋放,從而滿足異常安全的三級標準:

  • ?基本保證:程序狀態合法,無資源泄漏(通過 RAII 自動釋放)。
  • ?強保證:操作要么完全成功,要么狀態不變(通過“拷貝-交換”慣用法實現)。
class Widget {  std::vector<int> data;  
public:  Widget& operator=(const Widget& rhs) {  Widget temp(rhs);  // 可能拋出異常的拷貝操作  swap(temp);        // 無異常交換(強保證)  return *this;  }  
};  
// 若拷貝失敗,原對象狀態不變
  • ?不拋保證:承諾不拋出異常(析構函數標記為?noexcept

2、如何用 RAII 解決?std::shared_ptr?的循環引用問題

class B;  
class A {  
public:  std::shared_ptr<B> ptrB;  
};  
class B {  
public:  std::weak_ptr<A> ptrA;  // 使用 weak_ptr 打破循環  
};  

weak_ptr?通過?lock()?獲取臨時?shared_ptr,確保安全訪問。?

循環引用指兩個對象相互持有對方的?shared_ptr,導致引用計數無法歸零。解決方案是將其中一個指針改為?std::weak_ptr(弱引用,不增加計數)

三、高階

1、RAII 如何與移動語義結合優化資源管理?

移動語義(C++11 引入)允許資源所有權的轉移,避免不必要的拷貝,提升性能:

  • ?移動構造函數:將資源從臨時對象“竊取”到新對象。
  • ?移動賦值運算符:釋放當前資源并接管新資源。
class Resource {  int* data;  
public:  Resource(Resource&& other) noexcept : data(other.data) {  other.data = nullptr;  // 原對象不再擁有資源  }  ~Resource() { delete[] data; }  
};  

2、?RAII 在并發編程中的典型應用是什么?

?RAII 廣泛用于管理鎖,確保鎖的自動釋放,避免死鎖。如lock_guard?在離開作用域時釋放鎖,即使發生異常或提前返回也能保證解鎖

std::mutex mtx;  
void threadSafeFunc() {  std::lock_guard<std::mutex> lock(mtx);  // 構造時加鎖  // 臨界區操作  
}  // 析構時自動解鎖  

3、?智能指針是 RAII 技術的典型應用場景

這個見專欄后續的智能指針博客。

四、擴展:RAII 的局限性與解決方案

?1、資源生命周期與對象作用域不一致(如全局資源)

RAII 的核心是 ?對象作用域綁定資源生命周期,但全局資源(如全局配置、日志文件、數據庫連接池)可能需要在程序運行期間持續存在,無法通過局部對象作用域管理。若強行用局部對象管理全局資源,可能導致資源被提前釋放或重復釋放。

  • ?解決方案:使用?std::shared_ptr?或單例模式管理全局資源
// 頻繁開關文件的性能損耗,若多個線程同時調用 logMessage,可能導致日志內容錯亂或丟失。因此設計成全局的最佳// 錯誤示例:局部對象管理全局資源
void logMessage(const std::string& msg) {std::ofstream logFile("app.log");  // 函數結束時文件被關閉logFile << msg << std::endl; // 若 logFile << msg 拋出異常(如磁盤滿),文件可能未正確關閉
}
// 后續函數調用 logMessage() 時,文件需重新打開,效率低且可能丟失日志

解決方案 1:std::shared_ptr?管理全局資源
通過共享指針的引用計數機制,確保資源在所有使用者退出后才釋放?

// 全局共享的日志文件
std::shared_ptr<std::ofstream> globalLog = std::make_shared<std::ofstream>("app.log");void logMessage(const std::string& msg) {if (globalLog && globalLog->is_open()) {*globalLog << msg << std::endl;}
}
// 程序退出前全局智能指針自動析構,文件關閉

解決方案 2:單例模式
通過單例封裝全局資源,確保唯一性和可控生命周期

class Logger {
public:static Logger& getInstance() {static Logger instance;  // C++11 線程安全單例return instance;}void write(const std::string& msg) {if (logFile.is_open()) logFile << msg << std::endl;}
private:std::ofstream logFile;Logger() { logFile.open("app.log"); }  // 構造函數內打開文件~Logger() { logFile.close(); }         // 程序結束時析構單例,釋放資源
};

?線程安全的核心機制:Magic Static

C++11 引入了 ?Magic Static?(魔法靜態)特性,明確規定:

??“如果一個線程正在初始化局部靜態變量,其他并發線程必須等待該初始化完成。”?

編譯器會保證instance只被初始化一次,即使多個線程同時調用getInstance也不會重復創建。

當多個線程同時調用?getInstance()?時,?只有一個線程會執行?static Logger instance?的初始化,其他線程會被阻塞,直到初始化完成。這從根本上避免了多線程下重復創建實例的問題。

延伸:傳統雙檢鎖(Double-Checked Locking)的缺陷

static Logger* getInstance() {if (!instance) {                // 第一次檢查(非線程安全)lock_guard<mutex> lock(m);  // 加鎖if (!instance) {            // 第二次檢查instance = new Logger(); // 可能因指令重排導致問題}}return instance;
}
  • 需要顯式管理鎖,代碼冗余且易出錯。
  • 存在 ?指令重排(Reordering)風險new?操作可能先返回指針再初始化對象,導致其他線程訪問未完全初始化的實例

2、構造函數中資源申請失敗的處理。

若構造函數需要申請多個資源(如內存、網絡連接、文件句柄),當某一步驟失敗時,已分配的資源可能無法釋放,導致泄漏

class DatabaseConnection {
public:DatabaseConnection() {buffer = new char[1024];          // 步驟1:分配內存if (!connectToServer()) {         // 步驟2:連接失敗?// 若此處直接返回,已分配的 buffer 內存泄漏!throw std::runtime_error("連接服務器失敗");}lockFile();                       // 步驟3:加鎖文件}~DatabaseConnection() {delete[] buffer;disconnectFromServer();unlockFile();}
private:char* buffer;
};
  • ?解決方案:構造函數內拋出異常,確保已分配資源被釋放

利用 C++ ?棧展開(Stack Unwinding)??機制,在拋出異常時,?已構造的子對象會自動調用析構函數,釋放已分配的資源

class DatabaseConnection {
public:DatabaseConnection() {buffer = new char[1024];          // 步驟1try {if (!connectToServer()) {     // 步驟2throw std::runtime_error("連接服務器失敗");}lockFile();                   // 步驟3} catch (...) {delete[] buffer;  // 顯式釋放已分配內存(析構函數未調用)throw;            // 重新拋出異常}}~DatabaseConnection() { /* 析構函數釋放所有資源 */ }
};

優化方案:將每個資源封裝為獨立的 RAII 對象,依賴析構鏈自動釋放?

class MemoryBuffer {
public:MemoryBuffer(size_t size) : ptr(new char[size]) {}~MemoryBuffer() { delete[] ptr; }  // 析構時自動釋放內存
private:char* ptr;
};class DatabaseConnection {MemoryBuffer buffer;  // 成員對象,構造順序優先于宿主類NetworkConnection conn;FileLock fileLock;
public:DatabaseConnection() : buffer(1024), conn(), fileLock() {// 若 conn 或 fileLock 構造失敗,buffer 仍會通過析構函數釋放}
};

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:
http://www.pswp.cn/pingmian/73570.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/73570.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/73570.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Unity粒子系統

目錄 一、界面參數介紹1.主模塊2.Emission 模塊3.Shape 模塊4.Velocity over Lifetime 模塊5.Noise 模塊6.Limit Velocity Over Lifetime 模塊7.Inherit Velocity 模塊8.Force Over Lifetime 模塊9.Color Over Lifetime 模塊10.Color By Speed 模塊11.Size over Lifetime 模塊1…

Docker-清理容器空間prune

docker system prune -a 是一個非常有用的命令&#xff0c;用于清理 Docker 系統中未使用的資源&#xff0c;包括停止的容器、未使用的網絡、卷以及未被任何容器引用的鏡像&#xff08;懸空鏡像和所有未使用的鏡像&#xff09;。以下是關于該命令的詳細說明&#xff1a; 命令格…

LabVIEW遠程控制通訊接口

abVIEW提供了多種遠程控制與通訊接口&#xff0c;適用于不同場景下的設備交互、數據傳輸和系統集成。這些接口涵蓋從基礎的網絡協議&#xff08;如TCP/IP、UDP&#xff09;到專用技術&#xff08;如DataSocket、遠程面板&#xff09;&#xff0c;以及工業標準協議&#xff08;如…

LeetCode hot 100—尋找重復數

題目 給定一個包含 n 1 個整數的數組 nums &#xff0c;其數字都在 [1, n] 范圍內&#xff08;包括 1 和 n&#xff09;&#xff0c;可知至少存在一個重復的整數。 假設 nums 只有 一個重復的整數 &#xff0c;返回 這個重復的數 。 你設計的解決方案必須 不修改 數組 nums…

linux - centos7 部署 redis6.0.5

事先說明 本篇文章只解決在部署redis中出現的問題&#xff0c;并沒有部署redis的全過程&#xff0c;詳細部署過程可以參考Linux安裝部署Redis(超級詳細) - 長沙大鵬 - 博客園 執行 make 命令時報錯 原因&#xff1a;是因為gcc版本太低 升級gcc版本時 出現沒有可用軟件包 devt…

31天Python入門——第15天:日志記錄

你好&#xff0c;我是安然無虞。 文章目錄 日志記錄python的日志記錄模塊創建日志處理程序并配置輸出格式將日志內容輸出到控制臺將日志寫入到文件 logging更簡單的一種使用方式 日志記錄 日志記錄是一種重要的應用程序開發和維護技術, 它用于記錄應用程序運行時的關鍵信息和…

AI Agent開發大全第八課-Stable Diffusion 3的本地安裝全步驟

前言 就像我們前面幾課所述,本系列是一門體系化的教學,它不像網上很多個別存在的單篇博客走“吃快餐”模式,而是從扎實的基礎來帶領大家一步步邁向AI開發高手。所以我們的AI課程設置是相當全面的,除了有牢固的基礎知識外還有外面互聯網上也搜不到的生產級實戰。 前面講過…

用selenium+ChromeDriver豆瓣電影 肖申克的救贖 短評爬取(pycharm 爬蟲)

一、豆瓣電影 肖申克的救贖 短評urlhttps://movie.douban.com/subject/1292052/comments 二、基本知識點講解 1. Selenium 的基本使用 Selenium 是一個用于自動化瀏覽器操作的庫&#xff0c;常用于網頁測試和爬蟲。代碼中使用了以下 Selenium 的核心功能&#xff1a; webdriv…

開源在線客服系統源碼-前端源碼加載邏輯

客服源碼是使用Golang(又稱Go)開發的&#xff0c;Go是Google公司開發的一種靜態強類型、編譯型、并發型&#xff0c;并具有垃圾回收功能的編程語言。Go 天生支持并發。好處太多就不多說了。 全源碼客服系統用戶&#xff0c;想要針對自己的業務&#xff0c;進行二次開發&#xf…

Oracle數據庫服務器地址變更與監聽配置修改完整指南

一、前言 在企業IT運維中&#xff0c;Oracle數據庫服務器地址變更是常見的運維操作。本文將詳細介紹如何安全、高效地完成Oracle數據庫服務器地址變更及相關的監聽配置修改工作&#xff0c;確保數據庫服務在遷移后能夠正常運行。 二、準備工作 1. 環境檢查 確認新舊服務器I…

g對象在flask中主要是用來實現什么

在Flask中&#xff0c;g對象&#xff08;全稱flask.g&#xff09;是一個線程局部&#xff08;thread-local&#xff09;的臨時存儲對象&#xff0c;主要用于在單個請求的上下文&#xff08;request context&#xff09;中共享數據。它的核心作用是為同一請求的不同處理階段&…

工具介紹《WireShark》

Wireshark 過濾命令中符號含義詳解 一、比較運算符 Wireshark 支持兩種比較運算符語法&#xff1a;英文縮寫&#xff08;如 eq&#xff09;和 C語言風格符號&#xff08;如 &#xff09;&#xff0c;兩者功能等價。 符號&#xff08;英文縮寫&#xff09;C語言風格符號含義示…

JavaScrip-模版字符串的詳解

1.模版字符串的詳解 1.1 模版字符串的使用方法 在ES6之前&#xff0c;如果我們想要將字符串和一些動態的變量&#xff08;標識符&#xff09;拼接到一起&#xff0c;是非常丑陋的&#xff08;ugly) ES6允許我們使用模版字符串來嵌入變量或者表達式來進行拼接 首先&#xff0c;…

STM32C011 進入停止模式和待機模式

對于STM32C011J4M3微控制器&#xff0c;你可以使用HAL庫來實現進入停止模式&#xff08;Stop Mode&#xff09;和待機模式&#xff08;Standby Mode&#xff09;。下面是進入停止模式和待機模式的示例代碼&#xff1a; 進入停止模式代碼示例&#xff1a; #include "stm3…

海康設備http監聽接收報警事件數據

http監聽接收報警事件數據 海康獲取設備報警事件數據兩種方式&#xff1a; 1、sdk 布防監聽報警事件數據&#xff08;前面文章有示例&#xff09; 2、http監聽接收報警事件數據 http監聽接收報警事件數據&#xff0c;服務端可以使用netty通過端口來監聽獲取事件數據。 WEB 端…

FastAPI 全面指南:功能解析與應用場景實踐

FastAPI 全面指南&#xff1a;功能解析與應用場景實踐 FastAPI 是一個現代、快速&#xff08;高性能&#xff09;的 Python Web 框架&#xff0c;用于構建 API。它基于標準 Python 類型提示&#xff0c;使用 Starlette 和 Pydantic 構建&#xff0c;提供了極高的性能并簡化了開…

【STM32】編寫程序控制開發板的RGB LED燈

目錄 1、原理圖2、文件結構3、使用寄存器模式點亮3.1、什么是寄存器3.2、寄存器開發的本質3.3、寄存器開發步驟3.4、主要源碼3.4.1、main.c3.4.2、drv_gpio.h3.4.3、drv_gpio.c3.4.4、使用BSRR和BRR影子寄存器優化drv_gpio.c3.4.5、效果演示 4、使用標準庫模式點亮4.1、使用標準…

MyBatis-Plus 的加載及初始化

在 Spring Boot 啟動過程中&#xff0c;MyBatis-Plus 的加載和初始化涉及多個階段的工作。這些工作包括 MyBatis-Plus 自身的配置解析、Mapper 接口的掃描與注冊、SQL 語句的動態注入以及底層 MyBatis 的初始化等。以下是對整個過程的詳細分析&#xff1a; 1. Spring Boot 啟動…

SpringBoot中安全的設置阿里云日志SLS的accessKey

眾所周知,阿里云的服務都是基于accesskeyId和accesskeySecret來進行身份鑒權的,但唯獨日志因為需要寫入到.xml文件里對于accesskeyId和accesskeySecret需要進行一定程度的改進,尤其是使用了jasypt進行加密的參數傳遞進去logback.xml更是會遇到需要對參數進行解密的問題,而官網只…

關于解決Ubuntu終端及系統字體大小的問題

在Ubuntu中調整終端和系統字體大小可以通過以下方法&#xff08;可能不僅僅只是這幾種&#xff09;實現&#xff1a; 1. 調整系統字體大小 打開終端并輸入以下命令&#xff0c;安裝GNOME Tweaks&#xff0c;等待安裝完成&#xff1a; sudo apt install gnome-tweaks 接著進行…