การได้รับทรัพยากรหมายถึงการเริ่มต้น (RAII) คืออะไร?


283

การได้รับทรัพยากรหมายถึงการเริ่มต้น (RAII) คืออะไร?



13
นี่คือสิ่งที่ทำให้ฉันเป็นบ้าน stroustrup.com/bs_faq2.html#finally
Hal Canary

2
การอ้างอิงของ Microsoft พร้อม 3 ประโยคและ 2 ตัวอย่างยังชัดเจนมาก! msdn.microsoft.com/en-us/library/hh438480.aspx
Gab 是好人

คำตอบ:


374

มันเป็นชื่อที่แย่มากสำหรับแนวคิดที่ทรงพลังอย่างเหลือเชื่อและอาจเป็นหนึ่งในสิ่งที่ 1 ที่ผู้พัฒนา C ++ คิดถึงเมื่อพวกเขาเปลี่ยนไปใช้ภาษาอื่น มีการเคลื่อนไหวเล็กน้อยที่จะพยายามเปลี่ยนชื่อแนวคิดนี้เป็นขอบเขตการจัดการทรัพยากรขอบเขตแม้ว่ามันจะดูเหมือนจะยังไม่ติดก็ตาม

เมื่อเราพูดว่า 'ทรัพยากร' เราไม่ได้หมายถึงหน่วยความจำเท่านั้น - อาจเป็นตัวจัดการไฟล์ซ็อกเก็ตเครือข่ายตัวจัดการฐานข้อมูลวัตถุ GDI ... ในระยะสั้นสิ่งที่เรามีจำนวน จำกัด ดังนั้นเราจึงจำเป็นต้องสามารถ ควบคุมการใช้งาน ขอบเขต 'ขอบเขตที่ จำกัด ' หมายถึงอายุการใช้งานของวัตถุที่ถูกผูกไว้กับขอบเขตของตัวแปรดังนั้นเมื่อตัวแปรออกจากขอบเขตแล้วตัวทำลายจะปล่อยทรัพยากร คุณสมบัติที่มีประโยชน์มากของสิ่งนี้คือมันทำให้มีข้อยกเว้นด้านความปลอดภัยที่มากขึ้น ตัวอย่างเช่นเปรียบเทียบสิ่งนี้:

RawResourceHandle* handle=createNewResource();
handle->performInvalidOperation();  // Oops, throws exception
...
deleteResource(handle); // oh dear, never gets called so the resource leaks

ด้วย RAII หนึ่ง

class ManagedResourceHandle {
public:
   ManagedResourceHandle(RawResourceHandle* rawHandle_) : rawHandle(rawHandle_) {};
   ~ManagedResourceHandle() {delete rawHandle; }
   ... // omitted operator*, etc
private:
   RawResourceHandle* rawHandle;
};

ManagedResourceHandle handle(createNewResource());
handle->performInvalidOperation();

ในกรณีหลังนี้เมื่อมีการโยนข้อยกเว้นและสแต็กไม่คลายออกตัวแปรท้องถิ่นจะถูกทำลายซึ่งทำให้มั่นใจได้ว่าทรัพยากรของเราจะถูกล้างและไม่รั่วไหล


2
@the_mandrill: ฉันลอง ideone.com/1Jjzuc โปรแกรมนี้ แต่ไม่มีการโทรแบบทำลายล้าง tomdalling.com/blog/software-design/…บอกว่า C ++ รับประกันว่า destructor ของวัตถุบนสแต็กจะถูกเรียกแม้ว่าจะมีข้อยกเว้นเกิดขึ้นก็ตาม เหตุใด destructor จึงไม่ทำงานที่นี่ ทรัพยากรของฉันมีการรั่วไหลหรือจะไม่ถูกปล่อยออกมาหรือไม่?
Destructor

8
มีการโยนข้อยกเว้น แต่คุณไม่ได้จับมันดังนั้นแอปพลิเคชั่นจึงถูกยกเลิก หากคุณใช้ wraps ด้วยการลอง {} catch () {} มันทำงานได้ตามที่คาดไว้: ideone.com/xm2GR9
the_mandrill

2
ไม่แน่ใจว่าScope-Boundเป็นตัวเลือกชื่อที่ดีที่สุดที่นี่หรือไม่เนื่องจากตัวระบุคลาสหน่วยเก็บข้อมูลพร้อมขอบเขตจะกำหนดระยะเวลาการเก็บข้อมูลของเอนทิตี การ จำกัด ขอบเขตให้แคบลงอาจเป็นเรื่องง่าย แต่ก็ไม่แม่นยำ 100%
SebNag

125

นี่คือสำนวนการเขียนโปรแกรมซึ่งสั้น ๆ หมายความว่าคุณ

  • แค็ปซูลทรัพยากรให้เป็นคลาส (ซึ่งโดยปกติแล้วคอนสตรัคเตอร์ - แต่ไม่จำเป็นเสมอไป - จะได้รับทรัพยากรมาและ destructor จะปล่อยออกมาเสมอ)
  • ใช้ทรัพยากรผ่านอินสแตนซ์ท้องถิ่นของคลาส *
  • ทรัพยากรจะถูกปลดปล่อยโดยอัตโนมัติเมื่อวัตถุไม่อยู่ในขอบเขต

สิ่งนี้รับประกันได้ว่าสิ่งใดก็ตามที่เกิดขึ้นในขณะที่ใช้งานทรัพยากรมันจะได้รับการปลดปล่อยในที่สุด (ไม่ว่าจะเกิดจากการส่งคืนตามปกติการทำลายของวัตถุที่บรรจุหรือการโยนทิ้งยกเว้น)

มันเป็นวิธีปฏิบัติที่ดีที่ใช้กันอย่างแพร่หลายใน C ++ เพราะนอกจากจะเป็นวิธีที่ปลอดภัยในการจัดการกับทรัพยากรแล้วมันยังทำให้โค้ดของคุณสะอาดยิ่งขึ้นเนื่องจากคุณไม่จำเป็นต้องผสมข้อผิดพลาดในการจัดการรหัสกับฟังก์ชั่นหลัก

* ปรับปรุง: "ท้องถิ่น" อาจหมายถึงตัวแปรท้องถิ่นหรือตัวแปรสมาชิกไม่คงที่ของชั้นเรียน ในกรณีหลังตัวแปรสมาชิกจะเริ่มต้นและทำลายด้วยวัตถุเจ้าของ

** Update2:ตามที่ @sbi ชี้ให้เห็นว่าทรัพยากร - แม้ว่าบ่อยครั้งจะถูกจัดสรรภายในตัวสร้าง - ก็อาจถูกจัดสรรภายนอกและส่งผ่านเป็นพารามิเตอร์


1
AFAIK ตัวย่อไม่ได้หมายความว่าวัตถุจะต้องอยู่ในตัวแปร Local (stack) มันอาจเป็นตัวแปรสมาชิกของวัตถุอื่นดังนั้นเมื่อวัตถุ 'โฮลดิ้ง' ถูกทำลายวัตถุสมาชิกก็ถูกทำลายเช่นกันและปล่อยทรัพยากร ในความเป็นจริงฉันคิดว่าคำย่อหมายถึงเฉพาะที่ไม่มีopen()/ close()วิธีการเริ่มต้นและปล่อยทรัพยากรเพียงตัวสร้างและ destructor ดังนั้น 'ถือ' ของทรัพยากรเป็นเพียงอายุการใช้งานของวัตถุไม่ว่าอายุการใช้งานนั้นคือ จัดการโดยบริบท (สแต็ค) หรือ (การจัดสรรแบบไดนามิก) อย่างชัดเจน
Javier

1
จริงๆแล้วไม่มีอะไรบอกว่าทรัพยากรจะต้องได้รับในตัวสร้าง สตรีมไฟล์สตริงที่คอนเทนเนอร์อื่นทำเช่นนั้น แต่ทรัพยากรอาจถูกส่งไปยังตัวสร้างเช่นเดียวกับกรณีที่มีพอยน์เตอร์อัจฉริยะ เนื่องจากคุณเป็นคำตอบที่ได้รับการโหวตมากที่สุดคุณอาจต้องการแก้ไขปัญหานี้
sbi

มันไม่ใช่ตัวย่อมันเป็นตัวย่อ IIRC คนส่วนใหญ่ออกเสียงว่า "เท่อายใช่" ดังนั้นมันจึงไม่ผ่านเกณฑ์ตัวย่อเช่น DARPA ซึ่งออกเสียง DARPA แทนการสะกดคำ นอกจากนี้ฉันจะบอกว่า RAII เป็นกระบวนทัศน์มากกว่าแค่สำนวน
dtech

@Peter Torok: ฉันพยายามideone.com/1Jjzucโปรแกรมนี้ แต่ไม่มีการโทรแบบทำลายล้าง tomdalling.com/blog/software-design/...บอกว่า C ++ รับประกันว่า destructor ของวัตถุบนสแต็คจะถูกเรียกว่าแม้ว่ายกเว้นจะโยน เหตุใด destructor จึงไม่ทำงานที่นี่ ทรัพยากรของฉันมีการรั่วไหลหรือจะไม่ถูกปล่อยออกมาหรือไม่?
Destructor

50

"RAII" ย่อมาจาก "การได้มาซึ่งทรัพยากรคือการเริ่มต้น" และเป็นจริงค่อนข้างเรียกชื่อผิดเพราะมันไม่ได้เป็นทรัพยากรการเข้าซื้อกิจการ (และการเริ่มต้นของวัตถุ) ก็เป็นห่วงด้วย แต่ปล่อยทรัพยากร (โดยวิธีการของการทำลายของวัตถุ )
แต่ RAII เป็นชื่อที่เราได้รับและมันก็ติดอยู่

ที่เป็นหัวใจสำคัญของมันสำนวนคุณสมบัติ encapsulating ทรัพยากร (chunks ของหน่วยความจำเปิดไฟล์ปลดล็อค mutexes คุณชื่อมัน) ในท้องถิ่นวัตถุอัตโนมัติและมี destructor ของวัตถุนั้นปล่อยทรัพยากรเมื่อวัตถุถูกทำลายที่ จุดสิ้นสุดของขอบเขต:

{
  raii obj(acquire_resource());
  // ...
} // obj's dtor will call release_resource()

แน่นอนว่าวัตถุไม่ได้อยู่ในระบบของวัตถุอัตโนมัติ พวกเขาอาจเป็นสมาชิกของคลาสได้เช่นกัน:

class something {
private:
  raii obj_;  // will live and die with instances of the class
  // ... 
};

หากวัตถุดังกล่าวจัดการหน่วยความจำพวกเขามักจะเรียกว่า "ตัวชี้สมาร์ท"

มีหลายรูปแบบของสิ่งนี้ objยกตัวอย่างเช่นในรหัสแรกโค้ดคำถามที่เกิดขึ้นจะเกิดอะไรขึ้นถ้ามีคนต้องการที่จะคัดลอก วิธีที่ง่ายที่สุดคือไม่อนุญาตให้คัดลอกเพียงอย่างเดียว std::unique_ptr<>ตัวชี้สมาร์ทที่จะเป็นส่วนหนึ่งของไลบรารีมาตรฐานตามคุณลักษณะของมาตรฐาน C ++ ถัดไปทำเช่นนี้
ตัวชี้สมาร์ทอีกตัวหนึ่งนั้นstd::shared_ptrมี "การเป็นเจ้าของร่วม" ของทรัพยากร (วัตถุที่จัดสรรแบบไดนามิก) ที่เก็บไว้ นั่นคือมันสามารถคัดลอกได้อย่างอิสระและสำเนาทั้งหมดอ้างถึงวัตถุเดียวกัน ตัวชี้สมาร์ทติดตามจำนวนสำเนาที่อ้างถึงวัตถุเดียวกันและจะลบเมื่อสำเนาล่าสุดถูกทำลาย
ตัวแปรที่สามมีคุณลักษณะโดยstd::auto_ptr ซึ่งดำเนินการย้ายความหมายชนิด: วัตถุเป็นเจ้าของโดยตัวชี้เดียวเท่านั้นและความพยายามที่จะคัดลอกวัตถุจะส่งผล (ผ่านการแฮ็กไวยากรณ์) ในการถ่ายโอนความเป็นเจ้าของวัตถุไปยังเป้าหมายของการคัดลอก


4
std::auto_ptrstd::unique_ptrเป็นรุ่นที่ล้าสมัยของ std::auto_ptrชนิดซีแมนทิกส์ของการจำลองแบบจำลองให้มากที่สุดเท่าที่จะเป็นไปได้ใน C ++ 98 std::unique_ptrใช้ซีแมนติกของการย้ายแบบใหม่ของ C ++ 11 คลาสใหม่ที่ถูกสร้างขึ้นเพราะความหมายของการย้าย C ++ 11 เป็นที่ชัดเจนมากขึ้น (ต้องstd::moveยกเว้นชั่วคราว) ในขณะที่มันถูกผิดนัดสำเนาใด ๆ จากการไม่ const std::auto_ptrใน
Jan Hudec

@JiahaoCai: ครั้งหนึ่งเมื่อหลายปีก่อน (บน Usenet) Stroustrup พูดด้วยตัวเอง
sbi

21

อายุการใช้งานของวัตถุถูกกำหนดโดยขอบเขตของวัตถุ อย่างไรก็ตามบางครั้งเราต้องการหรือมันมีประโยชน์ในการสร้างวัตถุที่อาศัยอยู่อย่างอิสระจากขอบเขตที่มันถูกสร้างขึ้น ใน C ++ ผู้ประกอบการnewจะใช้ในการสร้างวัตถุดังกล่าว และเพื่อทำลายวัตถุตัวดำเนินการdeleteสามารถใช้งานได้ วัตถุที่สร้างโดยผู้ปฏิบัติงานnewจะได้รับการจัดสรรแบบไดนามิกเช่นการจัดสรรในหน่วยความจำแบบไดนามิก (หรือที่เรียกว่าฮีปหรือที่เก็บสินค้าฟรี ) ดังนั้นวัตถุที่ถูกสร้างขึ้นโดยจะยังคงอยู่จนกว่าจะมีการทำลายอย่างชัดเจนโดยใช้newdelete

ข้อผิดพลาดบางอย่างที่อาจเกิดขึ้นเมื่อใช้งานnewและdeleteคือ:

  • วัตถุ Leaked (หรือหน่วยความจำ): ใช้newเพื่อจัดสรรวัตถุและลืมdeleteวัตถุ
  • ลบก่อนวัยอันควร (หรือห้อยต่องแต่งอ้างอิง ): ถือตัวชี้อีกตัวกับวัตถุdeleteวัตถุแล้วใช้ตัวชี้อื่น ๆ
  • ลบdeleteสองครั้ง: พยายามที่วัตถุสองครั้ง

โดยทั่วไปแล้วจะต้องการตัวแปรที่มีขอบเขต อย่างไรก็ตาม RAII สามารถใช้เป็นทางเลือกแทนnewและdeleteทำให้วัตถุนั้นเป็นอิสระจากขอบเขตของมัน เทคนิคดังกล่าวประกอบด้วยการชี้ไปยังวัตถุที่ได้รับการจัดสรรในกองและวางไว้ในที่จับวัตถุ / ผู้จัดการ หลังมี destructor ที่จะดูแลการทำลายวัตถุ สิ่งนี้จะรับประกันว่าวัตถุนั้นพร้อมใช้งานสำหรับฟังก์ชันใด ๆ ที่ต้องการเข้าถึงและวัตถุนั้นถูกทำลายเมื่ออายุการใช้งานของวัตถุที่จับสิ้นสุดลงโดยไม่จำเป็นต้องล้างข้อมูลอย่างชัดเจน

ตัวอย่างจากห้องสมุดมาตรฐาน c ++ ว่าการใช้ RAII มีและstd::stringstd::vector

พิจารณารหัสชิ้นนี้:

void fn(const std::string& str)
{
    std::vector<char> vec;
    for (auto c : str)
        vec.push_back(c);
    // do something
}

เมื่อคุณสร้างเวกเตอร์และคุณผลักองค์ประกอบไปคุณจะไม่สนใจการจัดสรรและยกเลิกการจัดสรรองค์ประกอบดังกล่าว เวกเตอร์ใช้newเพื่อจัดสรรพื้นที่สำหรับองค์ประกอบบนฮีปและdeleteเพื่อเพิ่มพื้นที่ว่าง คุณในฐานะผู้ใช้เวกเตอร์คุณไม่สนใจรายละเอียดการใช้งานและจะวางใจเวกเตอร์ไม่ให้รั่วไหล ในกรณีนี้เวกเตอร์เป็นวัตถุจัดการขององค์ประกอบ

ตัวอย่างอื่น ๆ จากห้องสมุดมาตรฐานที่ใช้ RAII มีstd::shared_ptr, และstd::unique_ptrstd::lock_guard

ชื่อสำหรับเทคนิคนี้ก็คือSBRMสั้นสำหรับขอบเขต-Bound การจัดการทรัพยากร


1
"SBRM" ทำให้ฉันมีความรู้สึกมากขึ้น ฉันมาที่คำถามนี้เพราะฉันคิดว่าฉันเข้าใจ RAII แต่ชื่อกำลังขว้างฉันออกไปฟังมันอธิบายแทน "การจัดการทรัพยากรขอบเขต - ขอบเขต" ทำให้ฉันรู้ทันทีว่าฉันเข้าใจแนวคิดจริง ๆ
JShorthouse

ฉันไม่แน่ใจว่าทำไมนี่ไม่ได้ทำเครื่องหมายว่านี่เป็นคำตอบสำหรับคำถาม มันเป็นคำตอบที่ละเอียดมากและเป็นที่เขียนขอบคุณ @ elmiomar
Abdelrahman Shoman

13

หนังสือการเขียนโปรแกรม C ++ พร้อมรูปแบบการออกแบบเปิดเผยคำอธิบาย RAII เป็น:

  1. รับทรัพยากรทั้งหมด
  2. การใช้ทรัพยากร
  3. ปล่อยทรัพยากร

ที่ไหน

  • ทรัพยากรถูกนำไปใช้เป็นคลาสและพอยน์เตอร์ทั้งหมดมีคลาสล้อมรอบพวกเขา (ทำให้เป็นพอยน์เตอร์อัจฉริยะ)

  • ทรัพยากรได้มาจากการสร้างสิ่งก่อสร้างและปล่อยออกมาโดยปริยาย


1
@Brandin ฉันได้แก้ไขโพสต์ของฉันเพื่อให้ผู้อ่านจะเน้นเนื้อหาที่มีความสำคัญมากกว่าการโต้วาทีกฎหมายพื้นที่สีเทาของกฎหมายลิขสิทธิ์ของสิ่งที่ถือเป็นการใช้งานโดยชอบธรรม
เดนนิส

7

มีสามส่วนในคลาส RAII:

  1. ทรัพยากรถูกปล่อยออกมาใน destructor
  2. อินสแตนซ์ของคลาสจะถูกจัดสรรสแต็ก
  3. ทรัพยากรได้มาในตัวสร้าง ส่วนนี้เป็นทางเลือก แต่เป็นเรื่องปกติ

RAII ย่อมาจาก "Resource Acquisition is initialization" ส่วน "การรับทรัพยากร" ของ RAII เป็นที่ที่คุณเริ่มต้นบางสิ่งที่ต้องสิ้นสุดในภายหลังเช่น:

  1. กำลังเปิดไฟล์
  2. การจัดสรรหน่วยความจำบางส่วน
  3. การรับล็อค

ส่วน "คือการเริ่มต้น" หมายถึงการได้มาซึ่งเกิดขึ้นภายในตัวสร้างของคลาส

https://www.tomdalling.com/blog/software-design/resource-acquisition-is-initialisation-raii-explained/


5

การจัดการหน่วยความจำด้วยตนเองเป็นฝันร้ายที่โปรแกรมเมอร์ได้คิดค้นวิธีที่จะหลีกเลี่ยงตั้งแต่การประดิษฐ์คอมไพเลอร์ ภาษาโปรแกรมที่มีตัวรวบรวมขยะทำให้ชีวิตง่ายขึ้น แต่ในราคาที่คุ้มค่า ในบทความนี้ - การกำจัดตัวเก็บขยะ: วิธี RAII , วิศวกร Toptal Peter Goodspeed-Niklaus ให้เราดูประวัติของนักสะสมขยะและอธิบายว่าแนวคิดเรื่องความเป็นเจ้าของและการยืมสามารถช่วยกำจัดตัวเก็บขยะโดยไม่กระทบต่อความปลอดภัย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.