C++: RAII是什么——使用對象來管理資源
1. 什么是RAIIRAII是Resource Acquisition Is Initialization的縮寫,,即“資源獲取即初始化”,。它是C++語言的一種管理資源、避免資源泄漏的慣用法,,利用棧對象自動(dòng)銷毀的特點(diǎn)來實(shí)現(xiàn),,這一概念最早由Bjarne Stroustrup提出。因此,,我們可以通過構(gòu)造函數(shù)獲取資源,,通過析構(gòu)函數(shù)釋放資源。即: 代碼語言:javascript 復(fù)制 Object() {// acquire resource in constructor}~Object() {// release resource in destructor} RAII總結(jié)如下:
2. 為什么要使用RAII我們知道,在C++中,,通過new運(yùn)算符動(dòng)態(tài)申請內(nèi)存,,例如: 代碼語言:javascript 復(fù)制 Foo* ptr = new Foo(1);// ...delete ptr; 在這段代碼中,new運(yùn)算符在計(jì)算機(jī)內(nèi)存的堆上申請了一塊Foo類型的內(nèi)存,,然后將其地址賦值給存儲(chǔ)在棧上的指針ptr。為了能夠釋放內(nèi)存資源,,我們需要使用完new運(yùn)算符申請的內(nèi)存后,,手動(dòng)調(diào)用delete運(yùn)算符釋放內(nèi)存。 但是,,情況并不總是如此簡單,。 代碼語言:javascript 復(fù)制 Foo* ptr = new Foo(1);f(ptr); // -->① may throw exceptionif(ptr->g()) {// ... --> ② may forget to delete ptrreturn;}// ...delete ptr; 如上面這個(gè)例子,我們可能會(huì)遇到以下幾種情況:
而通過RAII這樣一種機(jī)制,,我們可以使其自動(dòng)釋放內(nèi)存,。 3. C++ STL中RAII的應(yīng)用3.1 智能指針智能指針是RAII的一種實(shí)現(xiàn),它是一種模板類,,用于管理動(dòng)態(tài)分配的對象,。智能指針的主要作用是自動(dòng)釋放內(nèi)存,從而避免內(nèi)存泄漏,。C++11中提供了三種智能指針:unique_ptr,、shared_ptr和weak_ptr。它們的詳細(xì)原理將在之后的文章中介紹,。這里我們以unique_ptr為例,,它的構(gòu)造函數(shù)如下: 代碼語言:javascript 復(fù)制 template< class T, class Deleter = std::default_delete<T> > class unique_ptr; unique_ptr的析構(gòu)函數(shù)會(huì)自動(dòng)釋放內(nèi)存,因此,,我們可以通過unique_ptr來管理動(dòng)態(tài)分配的內(nèi)存,,從而避免內(nèi)存泄漏。例如: 代碼語言:javascript 復(fù)制 std::unique_ptr<int> ptr = std::make_unique<int>(1); // release memory when ptr is out of scope 3.2 互斥鎖在多線程編程中,,std::lock_guard, std::unique_lock, std::shared_lock等也利用了RAII的原理,,用于管理互斥鎖。當(dāng)這些類的等對象創(chuàng)建時(shí),,會(huì)自動(dòng)獲取互斥鎖,;當(dāng)對象銷毀時(shí),會(huì)自動(dòng)釋放互斥鎖,。 std::lock_guard的構(gòu)造函數(shù)如下: 代碼語言:javascript 復(fù)制 template< class Mutex > class lock_guard; std::lock_guard的析構(gòu)函數(shù)會(huì)自動(dòng)釋放互斥鎖,,因此,我們可以通過std::lock_guard來管理互斥鎖,,從而避免忘記釋放互斥鎖,。例如: 代碼語言:javascript 復(fù)制 std::mutex mtx;std::lock_guard<std::mutex> lock(mtx); // unlock when lock is out of scope 不使用RAII的情況下,我們需要手動(dòng)釋放互斥鎖,,如下所示: 代碼語言:javascript 復(fù)制 std::mutex mtx;mtx.lock();// ...mtx.unlock(); 3.3 文件操作std::ifstream, std::ofstream等C++標(biāo)準(zhǔn)庫的IO操作都是RAII的實(shí)現(xiàn),。 3.4 事務(wù)處理數(shù)據(jù)庫事務(wù)處理中,如果在事務(wù)結(jié)束時(shí)沒有提交或回滾,就會(huì)導(dǎo)致數(shù)據(jù)庫連接一直被占用,,從而導(dǎo)致數(shù)據(jù)庫連接池耗盡,。因此,我們需要在事務(wù)結(jié)束時(shí)自動(dòng)提交或回滾,,從而釋放數(shù)據(jù)庫連接,。這一過程也可以通過RAII來實(shí)現(xiàn)。 3.5 其他RAII還可以用于管理其他資源,,比如網(wǎng)絡(luò)連接,、線程等。 4. RAII的編程實(shí)踐基于RAII實(shí)現(xiàn)資源池的自動(dòng)回收機(jī)制:
實(shí)現(xiàn)資源管理類需要注意的一些事項(xiàng):
代碼實(shí)現(xiàn)如下: 代碼語言:javascript 復(fù)制 #include <iostream>#include <vector>#include <deque>constexpr int kErrorId = -1;template<typename T>class ResourcePool {public:ResourcePool(int size) {for (int i = 0; i < size; ++i) {pool_.emplace_back(i);}}T getResource() {if (pool_.empty()) {std::cout << 'Resource is not available now.' << std::endl;return T();}T resource = std::move(pool_.front());pool_.pop_front();std::cout<< 'Resource ' << resource.ID() << ' is acquired.' << std::endl;return resource;}void releaseResource(T&& resource) {if (resource.ID() == kErrorId) { return;}std::cout << 'Resource ' << resource.ID() << ' is released.' << std::endl;pool_.emplace_back(std::forward<T>(resource));}private:std::deque<T> pool_;};template<typename T>class ResourceWrapper {public:ResourceWrapper(ResourcePool<T>& pool) : pool_(pool), resource_(pool_.getResource()) { if(resource_.ID() == kErrorId) {throw std::runtime_error('Resource is not available now.'); }}~ResourceWrapper() {pool_.releaseResource(std::move(resource_));}ResourceWrapper(const ResourceWrapper& other) = delete;ResourceWrapper& operator=(const ResourceWrapper& other) = delete;ResourceWrapper(ResourceWrapper&& other) noexcept : pool_(other.pool_), resource_(std::move(other.resource_)) {}ResourceWrapper& operator=(ResourceWrapper&& other) noexcept { pool_ = other.pool_; resource_ = std::move(other.resource_); return *this;}const T& GetRawResource() const noexcept{return resource_;}private:ResourcePool<T>& pool_;T resource_;};class Resource {public:constexpr explicit Resource(int id) : id_(id) { std::cout << 'Resource ' << id_ << ' is created.' << std::endl;}Resource(): id_(kErrorId) {}~Resource() = default;int ID() const {return id_;}// delete copy constructor and copy assignment operatorResource(const Resource& other) = delete;Resource& operator=(const Resource& other) = delete;Resource(Resource&& other) noexcept : id_(other.id_) { other.id_ = kErrorId;}Resource& operator=(Resource&& other) noexcept { id_ = other.id_; other.id_ = kErrorId; return *this;}private:int id_;};constexpr int kPoolSize = 3;ResourcePool<Resource> pool(kPoolSize); // Resource pool with 3 resources in global scope.void RequestRourceTest() {constexpr int kResourcesNum = 3;for (int i = 0; i < kResourcesNum; ++i) {ResourceWrapper<Resource> resource_wrapper(pool);resource_wrapper.GetRawResource();}}int main() {RequestRourceTest();return 0;} 運(yùn)行輸出結(jié)果如下: 代碼語言:javascript 復(fù)制 Resource 0 is created.Resource 1 is created.Resource 2 is created.Resource 0 is acquired.Resource 0 is released.Resource 1 is acquired.Resource 1 is released.Resource 2 is acquired.Resource 2 is released. 5. 總結(jié)在本文中,我們介紹了C++中的RAII技術(shù),,它是一種管理資源的方法,,可以幫助我們避免內(nèi)存泄漏和資源泄漏等問題。RAII技術(shù)的核心思想是將資源的獲取和釋放綁定在對象的生命周期中,,這樣可以確保資源在不再需要時(shí)被正確釋放。我們還介紹了如何使用RAII技術(shù)來管理動(dòng)態(tài)內(nèi)存,、文件句柄和互斥鎖等資源,,并提供了一些示例代碼來說明如何實(shí)現(xiàn)RAII類。最后,我們還討論了RAII技術(shù)的一些注意事項(xiàng)和最佳實(shí)踐,,以幫助開發(fā)人員編寫更安全,、更可靠的代碼。希望本文能夠幫助您更好地理解和應(yīng)用RAII技術(shù),。 在本文的編程實(shí)踐中,,還使用了std::move()、std::forward()等諸多現(xiàn)代C++技術(shù),,更多細(xì)節(jié)和不足之處將在之后的文章中進(jìn)行進(jìn)一步探討,。 參考:
你好,我是七昂,,計(jì)算機(jī)科學(xué)愛好者,,致力于分享C/C++、操作系統(tǒng)等計(jì)算機(jī)基礎(chǔ)知識,。希望我們能一起在計(jì)算機(jī)科學(xué)的世界里探索和成長,,最終能站得更高,走得更遠(yuǎn),。 |
|