成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

C++慣用法之資源獲取即初始化方法(RAII)

開(kāi)發(fā) 后端
在C語(yǔ)言中,有三種類(lèi)型的內(nèi)存分配:靜態(tài)、自動(dòng)和動(dòng)態(tài)。靜態(tài)變量是嵌入在源文件中的常數(shù),因?yàn)樗鼈冇幸阎拇笮〔⑶覐牟桓淖儯运鼈儾⒉荒敲从腥ぁ?/div>

[[404671]]

本文轉(zhuǎn)載自微信公眾號(hào)「光城」,作者 lightcity 。轉(zhuǎn)載本文請(qǐng)聯(lián)系光城公眾號(hào)。

0.導(dǎo)語(yǔ)

在C語(yǔ)言中,有三種類(lèi)型的內(nèi)存分配:靜態(tài)、自動(dòng)和動(dòng)態(tài)。靜態(tài)變量是嵌入在源文件中的常數(shù),因?yàn)樗鼈冇幸阎拇笮〔⑶覐牟桓淖儯运鼈儾⒉荒敲从腥ぁW詣?dòng)分配可以被認(rèn)為是堆棧分配——當(dāng)一個(gè)詞法塊進(jìn)入時(shí)分配空間,當(dāng)該塊退出時(shí)釋放空間。它最重要的特征與此直接相關(guān)。在C99之前,自動(dòng)分配的變量需要在編譯時(shí)知道它們的大小。這意味著任何字符串、列表、映射以及從這些派生的任何結(jié)構(gòu)都必須存在于堆中的動(dòng)態(tài)內(nèi)存中。

程序員使用四個(gè)基本操作明確地分配和釋放動(dòng)態(tài)內(nèi)存:malloc、realloc、calloc和free。前兩個(gè)不執(zhí)行任何初始化,內(nèi)存可能包含碎片。除了自由,他們都可能失敗。在這種情況下,它們返回一個(gè)空指針,其訪(fǎng)問(wèn)是未定義的行為;在最好的情況下,你的程序會(huì)崩潰。在最壞的情況下,你的程序看起來(lái)會(huì)工作一段時(shí)間,在崩潰前處理垃圾數(shù)據(jù)。

例如:

  1. int main() { 
  2.    char *str = (char *) malloc(7);  
  3.    strcpy(str, "toptal"); 
  4.    printf("char array = \"%s\" @ %u\n", str, str); 
  5.  
  6.    str = (char *) realloc(str, 11); 
  7.    strcat(str, ".com"); 
  8.    printf("char array = \"%s\" @ %u\n", str, str); 
  9.  
  10.    free(str); 
  11.     
  12.    return(0); 

輸出:

  1. char array = "toptal" @ 2762894960 
  2. char array = "toptal.com" @ 2762894960 

盡管代碼很簡(jiǎn)單,但它已經(jīng)包含了一個(gè)反模式和一個(gè)有問(wèn)題的決定。在現(xiàn)實(shí)生活中,你不應(yīng)該直接寫(xiě)字節(jié)數(shù),而應(yīng)該使用sizeof函數(shù)。類(lèi)似地,我們將char *數(shù)組精確地分配給我們需要的字符串大小的兩倍(比字符串長(zhǎng)度多一倍,以說(shuō)明空終止),這是一個(gè)相當(dāng)昂貴的操作。一個(gè)更復(fù)雜的程序可能會(huì)構(gòu)建一個(gè)更大的字符串緩沖區(qū),允許字符串大小增長(zhǎng)。

1.RAII的發(fā)明:新希望

至少可以說(shuō),所有手動(dòng)管理都是令人不快的。在80年代中期,Bjarne Stroustrup為他的全新語(yǔ)言C ++發(fā)明了一種新的范例。他將其稱(chēng)為“資源獲取就是初始化”,其基本見(jiàn)解如下:可以指定對(duì)象具有構(gòu)造函數(shù)和析構(gòu)函數(shù),這些構(gòu)造函數(shù)和析構(gòu)函數(shù)在適當(dāng)?shù)臅r(shí)候由編譯器自動(dòng)調(diào)用,這為管理給定對(duì)象的內(nèi)存提供了更為方便的方法。 需要,并且該技術(shù)對(duì)于不是內(nèi)存的資源也很有用。

意味著上面的例子在c++中更簡(jiǎn)潔:

  1. int main() { 
  2.    std::string str = std::string ("toptal"); 
  3.    std::cout << "string object: " << str << " @ " << &str << "\n"
  4.     
  5.    str += ".com"
  6.    std::cout << "string object: " << str << " @ " << &str << "\n"
  7.     
  8.    return(0); 

輸出:

  1. string object: toptal @ 0x7fffa67b9400 
  2. string object: toptal.com @ 0x7fffa67b9400 

在上述例子中,我們沒(méi)有手動(dòng)內(nèi)存管理!構(gòu)造string對(duì)象,調(diào)用重載方法,并在函數(shù)退出時(shí)自動(dòng)銷(xiāo)毀。不幸的是,同樣的簡(jiǎn)單也會(huì)導(dǎo)致其他問(wèn)題。讓我們?cè)敿?xì)地看一個(gè)例子:

  1. vector<string> read_lines_from_file(string &file_name) { 
  2.  vector<string> lines; 
  3.  string line; 
  4.   
  5.  ifstream file_handle (file_name.c_str()); 
  6.  while (file_handle.good() && !file_handle.eof()) { 
  7.   getline(file_handle, line); 
  8.   lines.push_back(line); 
  9.  } 
  10.   
  11.  file_handle.close(); 
  12.   
  13.  return lines; 
  14.  
  15. int main(int argc, char* argv[]) { 
  16.  // get file name from the first argument 
  17.  string file_name (argv[1]); 
  18.  int count = read_lines_from_file(file_name).size(); 
  19.  cout << "File " << file_name << " contains " << count << " lines."
  20.   
  21.  return 0; 

輸出:

  1. File makefile contains 38 lines. 

這看起來(lái)很簡(jiǎn)單。vector被填滿(mǎn)、返回和調(diào)用。然而,作為關(guān)心性能的高效程序員,這方面的一些問(wèn)題困擾著我們:在return語(yǔ)句中,由于使用了值語(yǔ)義,vector在銷(xiāo)毀之前不久就被復(fù)制到一個(gè)新vector中。

在現(xiàn)代C ++中,這不再是嚴(yán)格的要求了。C ++ 11引入了移動(dòng)語(yǔ)義的概念,其中將原點(diǎn)保留在有效狀態(tài)(以便仍然可以正確銷(xiāo)毀)但未指定狀態(tài)。對(duì)于編譯器而言,返回調(diào)用是最容易優(yōu)化以?xún)?yōu)化語(yǔ)義移動(dòng)的情況,因?yàn)樗涝谶M(jìn)行任何進(jìn)一步訪(fǎng)問(wèn)之前不久將銷(xiāo)毀源。但是,該示例的目的是說(shuō)明為什么人們?cè)?0年代末和90年代初發(fā)明了一大堆垃圾收集的語(yǔ)言,而在那個(gè)時(shí)候C ++ move語(yǔ)義不可用。

對(duì)于數(shù)據(jù)量比較大的文件,這可能會(huì)變得昂貴。讓我們對(duì)其進(jìn)行優(yōu)化,只返回一個(gè)指針。語(yǔ)法進(jìn)行了一些更改,但其他代碼相同:

  1. vector<string> * read_lines_from_file(string &file_name) { 
  2.  vector<string> * lines; 
  3.  string line; 
  4.   
  5.  ifstream file_handle (file_name.c_str()); 
  6.  while (file_handle.good() && !file_handle.eof()) { 
  7.   getline(file_handle, line); 
  8.   lines->push_back(line); 
  9.  } 
  10.   
  11.  file_handle.close(); 
  12.   
  13.  return lines; 
  14. int main(int argc, char* argv[]) { 
  15.  // get file name from the first argument 
  16.  string file_name (argv[1]); 
  17.  int count = read_lines_from_file(file_name).size(); 
  18.  cout << "File " << file_name << " contains " << count << " lines."
  19.   
  20.  return 0; 

輸出:

  1. Segmentation fault (core dumped) 

程序崩潰!我們只需要將上述的lines進(jìn)行內(nèi)存分配:

  1. vector<string> * lines = new vector<string>; 

這樣就可以運(yùn)行了!

不幸的是,盡管這看起來(lái)很完美,但它仍然有一個(gè)缺陷:它會(huì)泄露內(nèi)存。在C++中,指向堆的指針在不再需要后必須手動(dòng)刪除;否則,一旦最后一個(gè)指針超出范圍,該內(nèi)存將變得不可用,并且直到進(jìn)程結(jié)束時(shí)操作系統(tǒng)對(duì)其進(jìn)行管理后才會(huì)恢復(fù)。慣用的現(xiàn)代C++將在這里使用unique_ptr,它實(shí)現(xiàn)了期望的行為。它刪除指針超出范圍時(shí)指向的對(duì)象。然而,這種行為直到C++11才成為語(yǔ)言的一部分。

在這里,可以直接使用C++11之前的語(yǔ)法,只是把main中改一下即可:

  1.  ifstream file_handle (file_name.c_str()); 
  2.  while (file_handle.good() && !file_handle.eof()) { 
  3.   getline(file_handle, line); 
  4.   lines->push_back(line); 
  5.  } 
  6.   
  7.  file_handle.close(); 
  8.   
  9.  return lines; 
  10.  
  11. int main(int argc, char* argv[]) { 
  12.  // get file name from the first argument 
  13.  string file_name (argv[1]); 
  14.  vector<string> * file_lines = read_lines_from_file(file_name); 
  15.  int count = file_lines->size(); 
  16.  delete file_lines; 
  17.  cout << "File " << file_name << " contains " << count << " lines."
  18.   
  19.  return 0; 

手動(dòng)去分配內(nèi)存與釋放內(nèi)存。

不幸的是,隨著程序擴(kuò)展到上述范圍之外,很快就變得更加難以推理指針應(yīng)該在何時(shí)何地被刪除。當(dāng)一個(gè)函數(shù)返回指針時(shí),你現(xiàn)在擁有它嗎?您應(yīng)該在完成后自己刪除它,還是它屬于某個(gè)稍后將被一次性釋放的數(shù)據(jù)結(jié)構(gòu)?一方面出錯(cuò),內(nèi)存泄漏,另一方面出錯(cuò),你已經(jīng)破壞了正在討論的數(shù)據(jù)結(jié)構(gòu)和其他可能的數(shù)據(jù)結(jié)構(gòu),因?yàn)樗鼈冊(cè)噲D取消引用現(xiàn)在不再有效的指針。

2.“使用垃圾收集器,flyboy!”

垃圾收集器不是一項(xiàng)新技術(shù)。它們由John McCarthy在1959年為L(zhǎng)isp發(fā)明。1980年,隨著Smalltalk-80的出現(xiàn),垃圾收集開(kāi)始成為主流。但是,1990年代代表了該技術(shù)的真正發(fā)芽:在1990年至2000年之間,發(fā)布了多種語(yǔ)言,所有語(yǔ)言都使用一種或另一種垃圾回收:Haskell,Python,Lua,Java,JavaScript,Ruby,OCaml 和C#是最著名的。

什么是垃圾收集?簡(jiǎn)而言之,這是一組用于自動(dòng)執(zhí)行手動(dòng)內(nèi)存管理的技術(shù)。它通常作為具有手動(dòng)內(nèi)存管理的語(yǔ)言(例如C和C ++)的庫(kù)提供,但在需要它的語(yǔ)言中更常用。最大的優(yōu)點(diǎn)是程序員根本不需要考慮內(nèi)存。都被抽象了。例如,相當(dāng)于我們上面的文件讀取代碼的Python就是這樣:

  1. def read_lines_from_file(file_name): 
  2.  lines = [] 
  3.  with open(file_name) as fp:  
  4.   for line in fp: 
  5.    lines.append(line) 
  6.  return lines 
  7.   
  8. if __name__ == '__main__'
  9.  import sys 
  10.  file_name = sys.argv[1] 
  11.  count = len(read_lines_from_file(file_name)) 
  12.  print("File {} contains {} lines.".format(file_name, count)) 

行數(shù)組是在第一次分配給它時(shí)出現(xiàn)的,并且不復(fù)制到調(diào)用范圍就返回。由于時(shí)間不確定,它會(huì)在超出該范圍后的某個(gè)時(shí)間被垃圾收集器清理。有趣的是,在Python中,用于非內(nèi)存資源的RAII不是慣用語(yǔ)言。允許-我們可以簡(jiǎn)單地編寫(xiě)fp = open(file_name)而不是使用with塊,然后讓GC清理。但是建議的模式是在可能的情況下使用上下文管理器,以便可以在確定的時(shí)間釋放它們。

盡管簡(jiǎn)化了內(nèi)存管理,但要付出很大的代價(jià)。在引用計(jì)數(shù)垃圾回收中,所有變量賦值和作用域出口都會(huì)獲得少量成本來(lái)更新引用。在標(biāo)記清除系統(tǒng)中,在GC清除內(nèi)存的同時(shí),所有程序的執(zhí)行都以不可預(yù)測(cè)的時(shí)間間隔暫停。這通常稱(chēng)為世界停止事件。同時(shí)使用這兩種系統(tǒng)的Python之類(lèi)的實(shí)現(xiàn)都會(huì)受到兩種懲罰。這些問(wèn)題降低了垃圾收集語(yǔ)言在性能至關(guān)重要或需要實(shí)時(shí)應(yīng)用程序的情況下的適用性。即使在以下玩具程序上,也可以看到實(shí)際的性能下降:

  1. $ make cpp && time ./c++ makefile 
  2. g++ -o c++ c++.cpp 
  3. File makefile contains 38 lines. 
  4. real    0m0.016s 
  5. user    0m0.000s 
  6. sys     0m0.015s 
  7.  
  8. time python3 python3.py makefile 
  9. File makefile contains 38 lines. 
  10.  
  11. real    0m0.041s 
  12. user    0m0.015s 
  13. sys     0m0.015s 

Python版本的實(shí)時(shí)時(shí)間幾乎是C ++版本的三倍。盡管并非所有這些差異都可以歸因于垃圾收集,但它仍然是可觀(guān)的。

3.所有權(quán):RAII覺(jué)醒

我們知道對(duì)象的生存期由其范圍決定。但是,有時(shí)我們需要?jiǎng)?chuàng)建一個(gè)對(duì)象,該對(duì)象與創(chuàng)建對(duì)象的作用域無(wú)關(guān),這是有用的,或者很有用。在C ++中,運(yùn)算符new用于創(chuàng)建這樣的對(duì)象。為了銷(xiāo)毀對(duì)象,可以使用運(yùn)算符delete。由new操作員創(chuàng)建的對(duì)象是動(dòng)態(tài)分配的,即在動(dòng)態(tài)內(nèi)存(也稱(chēng)為堆或空閑存儲(chǔ))中分配。因此,由new創(chuàng)建的對(duì)象將繼續(xù)存在,直到使用delete將其明確銷(xiāo)毀為止。

使用new和delete時(shí)可能發(fā)生的一些錯(cuò)誤是:

  • 對(duì)象(或內(nèi)存)泄漏:使用new分配對(duì)象,而忘記刪除該對(duì)象。
  • 過(guò)早刪除(或懸掛引用):持有指向?qū)ο蟮牧硪粋€(gè)指針,刪除該對(duì)象,然而還有其他指針在引用它。
  • 雙重刪除:嘗試兩次刪除一個(gè)對(duì)象。

通常,范圍變量是首選。但是,RAII可以用作new和delete的替代方法,以使對(duì)象獨(dú)立于其范圍而存在。這種技術(shù)包括將指針?lè)峙涞皆诙焉戏峙涞膶?duì)象,并將其放在句柄/管理器對(duì)象中。后者具有一個(gè)析構(gòu)函數(shù),將負(fù)責(zé)銷(xiāo)毀該對(duì)象。這將確保該對(duì)象可用于任何想要訪(fǎng)問(wèn)它的函數(shù),并且該對(duì)象在句柄對(duì)象的生存期結(jié)束時(shí)將被銷(xiāo)毀,而無(wú)需進(jìn)行顯式清理。

來(lái)自C ++標(biāo)準(zhǔn)庫(kù)的使用RAII的示例為std :: string和std :: vector。

考慮這段代碼:

  1. void fn(const std::string& str) 
  2.     std::vector<char> vec; 
  3.     for (auto c : str) 
  4.         vec.push_back(c); 
  5.     // do something 

當(dāng)創(chuàng)建vector,并將元素推入vector時(shí),您不必?fù)?dān)心分配和取消分配此類(lèi)元素內(nèi)存。vector使用new為其堆上的元素分配空間,并使用delete釋放該空間。作為vector的用戶(hù),您無(wú)需關(guān)心實(shí)現(xiàn)細(xì)節(jié),并且會(huì)相信vector不會(huì)泄漏。在這種情況下,向量是其元素的句柄對(duì)象。

標(biāo)準(zhǔn)庫(kù)中使用RAII的其他示例是std :: shared_ptr,std :: unique_ptr和std :: lock_guard。

該技術(shù)的另一個(gè)名稱(chēng)是SBRM,是范圍綁定資源管理的縮寫(xiě)。

現(xiàn)在,我們將上述讀取文件例子,進(jìn)行修改:

  1. #include <iostream> 
  2. #include <vector> 
  3. #include <cstring> 
  4. #include <fstream> 
  5. #include <bits/unique_ptr.h> 
  6.  
  7. using namespace std; 
  8. unique_ptr<vector<string>> read_lines_from_file(string &file_name) { 
  9.     unique_ptr<vector<string>> lines(new vector<string>); 
  10.     string line; 
  11.  
  12.     ifstream file_handle (file_name.c_str()); 
  13.     while (file_handle.good() && !file_handle.eof()) { 
  14.         getline(file_handle, line); 
  15.         lines->push_back(line); 
  16.     } 
  17.  
  18.     file_handle.close(); 
  19.  
  20.     return lines; 
  21. int main(int argc, char* argv[]) { 
  22.  // get file name from the first argument 
  23.  string file_name (argv[1]); 
  24.  int count = read_lines_from_file(file_name).get()->size(); 
  25.     cout << "File " << file_name << " contains " << count << " lines."
  26.  
  27.  return 0; 

4.只有在最后,你才意識(shí)到RAII的真正力量。

自從編譯器發(fā)明以來(lái),手動(dòng)內(nèi)存管理是程序員一直在想辦法避免的噩夢(mèng)。RAII是一種很有前途的模式,但由于沒(méi)有一些奇怪的解決方法,它根本無(wú)法用于堆分配的對(duì)象,因此在C ++中會(huì)受到影響。因此,在90年代出現(xiàn)了垃圾收集語(yǔ)言的爆炸式增長(zhǎng),旨在使程序員生活更加愉快,即使以性能為代價(jià)。

最后,RAII總結(jié)如下:

  • 資源在析構(gòu)函數(shù)中被釋放
  • 該類(lèi)的實(shí)例是堆棧分配的
  • 資源是在構(gòu)造函數(shù)中獲取的。

RAII代表“資源獲取是初始化”。

常見(jiàn)的例子有:

  • 文件操作
  • 智能指針
  • 互斥量

5.參考文章

1.https://www.toptal.com/software/eliminating-garbage-collector#remote-developer-job

 

2.https://stackoverflow.com/questions/2321511/what-is-meant-by-resource-acquisition-is-initialization-raii

 

責(zé)任編輯:武曉燕 來(lái)源: 光城
相關(guān)推薦

2011-06-09 15:15:52

RAII

2024-02-01 00:10:21

C++PIMPL編程

2024-02-02 12:42:42

C++Policy模板

2010-02-01 14:21:24

C++初始化列表

2023-11-12 23:08:17

C++初始化

2011-06-09 15:04:22

RAII機(jī)制

2010-02-06 14:40:50

C++初始化和賦值

2009-08-19 09:57:01

C++ RAII

2010-02-05 17:16:05

C++構(gòu)造函數(shù)

2010-02-06 15:58:10

C++集合初始化

2024-03-13 08:52:43

C++初始化方式

2011-06-09 14:13:06

C++JAVA缺省初始化

2023-10-06 20:57:52

C++聚合成員

2010-02-03 11:01:18

C++類(lèi)靜態(tài)成員初始化

2024-03-04 10:53:08

RAIIC++開(kāi)發(fā)

2009-08-19 13:36:21

C++資源管理方式

2025-02-18 00:08:00

代碼C++RAII

2023-12-04 10:57:52

函數(shù)C++

2009-09-18 11:15:52

C#數(shù)組初始化

2009-07-31 17:51:27

C#對(duì)象初始化
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 能看的av网站 | 欧美日韩国产中文字幕 | 国产精品欧美一区二区 | 国产精品一区二区在线播放 | 97av视频在线观看 | 欧美一二三区 | 国产亚洲一区二区三区在线 | 中文字幕国产视频 | 女同久久另类99精品国产 | 亚洲天堂中文字幕 | 欧美激情在线一区二区三区 | 99久久久久久99国产精品免 | 男女免费在线观看视频 | 亚洲一区二区三区在线 | 国产成人精品999在线观看 | 激情一区二区三区 | 欧美高清视频 | 久久久久电影 | 国产亚洲一区二区三区 | 国产ts人妖另类 | 久久蜜桃av一区二区天堂 | 国产成人精品一区二区 | 欧美精选一区二区 | 欧美精品三区 | 91在线精品视频 | 亚洲午夜精品久久久久久app | 免费网站在线 | 亚洲一区av在线 | 久久亚洲精品国产精品紫薇 | 日韩中文字幕久久 | 久久福利电影 | 国内久久 | 久草综合在线视频 | 一二区电影 | 久久久精品一区二区 | 欧美精品成人 | 91麻豆产精品久久久久久夏晴子 | 亚洲精品视频三区 | 中文字幕一区二区三区日韩精品 | 亚洲精品一区二区三区四区高清 | 久久久成人精品 |