การได้รับทรัพยากรหมายถึงการเริ่มต้น (RAII) คืออะไร?
การได้รับทรัพยากรหมายถึงการเริ่มต้น (RAII) คืออะไร?
คำตอบ:
มันเป็นชื่อที่แย่มากสำหรับแนวคิดที่ทรงพลังอย่างเหลือเชื่อและอาจเป็นหนึ่งในสิ่งที่ 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();
ในกรณีหลังนี้เมื่อมีการโยนข้อยกเว้นและสแต็กไม่คลายออกตัวแปรท้องถิ่นจะถูกทำลายซึ่งทำให้มั่นใจได้ว่าทรัพยากรของเราจะถูกล้างและไม่รั่วไหล
Scope-Bound
เป็นตัวเลือกชื่อที่ดีที่สุดที่นี่หรือไม่เนื่องจากตัวระบุคลาสหน่วยเก็บข้อมูลพร้อมขอบเขตจะกำหนดระยะเวลาการเก็บข้อมูลของเอนทิตี การ จำกัด ขอบเขตให้แคบลงอาจเป็นเรื่องง่าย แต่ก็ไม่แม่นยำ 100%
นี่คือสำนวนการเขียนโปรแกรมซึ่งสั้น ๆ หมายความว่าคุณ
สิ่งนี้รับประกันได้ว่าสิ่งใดก็ตามที่เกิดขึ้นในขณะที่ใช้งานทรัพยากรมันจะได้รับการปลดปล่อยในที่สุด (ไม่ว่าจะเกิดจากการส่งคืนตามปกติการทำลายของวัตถุที่บรรจุหรือการโยนทิ้งยกเว้น)
มันเป็นวิธีปฏิบัติที่ดีที่ใช้กันอย่างแพร่หลายใน C ++ เพราะนอกจากจะเป็นวิธีที่ปลอดภัยในการจัดการกับทรัพยากรแล้วมันยังทำให้โค้ดของคุณสะอาดยิ่งขึ้นเนื่องจากคุณไม่จำเป็นต้องผสมข้อผิดพลาดในการจัดการรหัสกับฟังก์ชั่นหลัก
*
ปรับปรุง: "ท้องถิ่น" อาจหมายถึงตัวแปรท้องถิ่นหรือตัวแปรสมาชิกไม่คงที่ของชั้นเรียน ในกรณีหลังตัวแปรสมาชิกจะเริ่มต้นและทำลายด้วยวัตถุเจ้าของ
**
Update2:ตามที่ @sbi ชี้ให้เห็นว่าทรัพยากร - แม้ว่าบ่อยครั้งจะถูกจัดสรรภายในตัวสร้าง - ก็อาจถูกจัดสรรภายนอกและส่งผ่านเป็นพารามิเตอร์
open()
/ close()
วิธีการเริ่มต้นและปล่อยทรัพยากรเพียงตัวสร้างและ destructor ดังนั้น 'ถือ' ของทรัพยากรเป็นเพียงอายุการใช้งานของวัตถุไม่ว่าอายุการใช้งานนั้นคือ จัดการโดยบริบท (สแต็ค) หรือ (การจัดสรรแบบไดนามิก) อย่างชัดเจน
"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
ซึ่งดำเนินการย้ายความหมายชนิด: วัตถุเป็นเจ้าของโดยตัวชี้เดียวเท่านั้นและความพยายามที่จะคัดลอกวัตถุจะส่งผล (ผ่านการแฮ็กไวยากรณ์) ในการถ่ายโอนความเป็นเจ้าของวัตถุไปยังเป้าหมายของการคัดลอก
std::auto_ptr
std::unique_ptr
เป็นรุ่นที่ล้าสมัยของ std::auto_ptr
ชนิดซีแมนทิกส์ของการจำลองแบบจำลองให้มากที่สุดเท่าที่จะเป็นไปได้ใน C ++ 98 std::unique_ptr
ใช้ซีแมนติกของการย้ายแบบใหม่ของ C ++ 11 คลาสใหม่ที่ถูกสร้างขึ้นเพราะความหมายของการย้าย C ++ 11 เป็นที่ชัดเจนมากขึ้น (ต้องstd::move
ยกเว้นชั่วคราว) ในขณะที่มันถูกผิดนัดสำเนาใด ๆ จากการไม่ const std::auto_ptr
ใน
อายุการใช้งานของวัตถุถูกกำหนดโดยขอบเขตของวัตถุ อย่างไรก็ตามบางครั้งเราต้องการหรือมันมีประโยชน์ในการสร้างวัตถุที่อาศัยอยู่อย่างอิสระจากขอบเขตที่มันถูกสร้างขึ้น ใน C ++ ผู้ประกอบการnew
จะใช้ในการสร้างวัตถุดังกล่าว และเพื่อทำลายวัตถุตัวดำเนินการdelete
สามารถใช้งานได้ วัตถุที่สร้างโดยผู้ปฏิบัติงานnew
จะได้รับการจัดสรรแบบไดนามิกเช่นการจัดสรรในหน่วยความจำแบบไดนามิก (หรือที่เรียกว่าฮีปหรือที่เก็บสินค้าฟรี ) ดังนั้นวัตถุที่ถูกสร้างขึ้นโดยจะยังคงอยู่จนกว่าจะมีการทำลายอย่างชัดเจนโดยใช้new
delete
ข้อผิดพลาดบางอย่างที่อาจเกิดขึ้นเมื่อใช้งานnew
และdelete
คือ:
new
เพื่อจัดสรรวัตถุและลืมdelete
วัตถุdelete
วัตถุแล้วใช้ตัวชี้อื่น ๆdelete
สองครั้ง: พยายามที่วัตถุสองครั้งโดยทั่วไปแล้วจะต้องการตัวแปรที่มีขอบเขต อย่างไรก็ตาม RAII สามารถใช้เป็นทางเลือกแทนnew
และdelete
ทำให้วัตถุนั้นเป็นอิสระจากขอบเขตของมัน เทคนิคดังกล่าวประกอบด้วยการชี้ไปยังวัตถุที่ได้รับการจัดสรรในกองและวางไว้ในที่จับวัตถุ / ผู้จัดการ หลังมี destructor ที่จะดูแลการทำลายวัตถุ สิ่งนี้จะรับประกันว่าวัตถุนั้นพร้อมใช้งานสำหรับฟังก์ชันใด ๆ ที่ต้องการเข้าถึงและวัตถุนั้นถูกทำลายเมื่ออายุการใช้งานของวัตถุที่จับสิ้นสุดลงโดยไม่จำเป็นต้องล้างข้อมูลอย่างชัดเจน
ตัวอย่างจากห้องสมุดมาตรฐาน c ++ ว่าการใช้ RAII มีและstd::string
std::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_ptr
std::lock_guard
ชื่อสำหรับเทคนิคนี้ก็คือSBRMสั้นสำหรับขอบเขต-Bound การจัดการทรัพยากร
หนังสือการเขียนโปรแกรม C ++ พร้อมรูปแบบการออกแบบเปิดเผยคำอธิบาย RAII เป็น:
ที่ไหน
ทรัพยากรถูกนำไปใช้เป็นคลาสและพอยน์เตอร์ทั้งหมดมีคลาสล้อมรอบพวกเขา (ทำให้เป็นพอยน์เตอร์อัจฉริยะ)
ทรัพยากรได้มาจากการสร้างสิ่งก่อสร้างและปล่อยออกมาโดยปริยาย
มีสามส่วนในคลาส RAII:
RAII ย่อมาจาก "Resource Acquisition is initialization" ส่วน "การรับทรัพยากร" ของ RAII เป็นที่ที่คุณเริ่มต้นบางสิ่งที่ต้องสิ้นสุดในภายหลังเช่น:
ส่วน "คือการเริ่มต้น" หมายถึงการได้มาซึ่งเกิดขึ้นภายในตัวสร้างของคลาส
การจัดการหน่วยความจำด้วยตนเองเป็นฝันร้ายที่โปรแกรมเมอร์ได้คิดค้นวิธีที่จะหลีกเลี่ยงตั้งแต่การประดิษฐ์คอมไพเลอร์ ภาษาโปรแกรมที่มีตัวรวบรวมขยะทำให้ชีวิตง่ายขึ้น แต่ในราคาที่คุ้มค่า ในบทความนี้ - การกำจัดตัวเก็บขยะ: วิธี RAII , วิศวกร Toptal Peter Goodspeed-Niklaus ให้เราดูประวัติของนักสะสมขยะและอธิบายว่าแนวคิดเรื่องความเป็นเจ้าของและการยืมสามารถช่วยกำจัดตัวเก็บขยะโดยไม่กระทบต่อความปลอดภัย