ควรสังเกตว่ามันเป็นในกรณีของ C ++ ความเข้าใจผิดทั่วไปว่า "คุณต้องจัดการหน่วยความจำด้วยตนเอง" จริงๆแล้วคุณไม่ได้จัดการหน่วยความจำในรหัสของคุณ
วัตถุขนาดคงที่ (พร้อมอายุการใช้งานขอบเขต)
ในกรณีส่วนใหญ่เมื่อคุณต้องการวัตถุวัตถุจะมีอายุการใช้งานที่กำหนดไว้ในโปรแกรมของคุณและถูกสร้างขึ้นบนสแต็ก สิ่งนี้ใช้ได้กับชนิดข้อมูลดั้งเดิมทั้งหมดที่มีอยู่แล้ว แต่สำหรับอินสแตนซ์ของคลาสและ structs:
class MyObject {
public: int x;
};
int objTest()
{
MyObject obj;
obj.x = 5;
return obj.x;
}
วัตถุสแต็กจะถูกลบโดยอัตโนมัติเมื่อฟังก์ชั่นสิ้นสุด ใน Java วัตถุจะถูกสร้างขึ้นบนฮีปเสมอและดังนั้นจึงต้องถูกลบออกโดยกลไกบางอย่างเช่นการรวบรวมขยะ นี่ไม่ใช่ปัญหาสำหรับวัตถุสแต็ก
วัตถุที่จัดการข้อมูลไดนามิก (พร้อมอายุการใช้งานขอบเขต)
การใช้พื้นที่บนสแต็กใช้กับวัตถุที่มีขนาดคงที่ เมื่อคุณต้องการพื้นที่จำนวนตัวแปรเช่นอาเรย์จะใช้วิธีการอื่น: รายการถูกห่อหุ้มในวัตถุขนาดคงที่ซึ่งจัดการหน่วยความจำแบบไดนามิกสำหรับคุณ สิ่งนี้ทำงานได้เพราะวัตถุสามารถมีฟังก์ชั่นการล้างข้อมูลพิเศษเป็นตัวทำลาย มันรับประกันว่าจะถูกเรียกเมื่อวัตถุออกนอกขอบเขตและทำตรงกันข้ามกับนวกรรมิก:
class MyList {
public:
// a fixed-size pointer to the actual memory.
int* listOfInts;
// constructor: get memory
MyList(size_t numElements) { listOfInts = new int[numElements]; }
// destructor: free memory
~MyList() { delete[] listOfInts; }
};
int listTest()
{
MyList list(1024);
list.listOfInts[200] = 5;
return list.listOfInts[200];
// When MyList goes off stack here, its destructor is called and frees the memory.
}
ไม่มีการจัดการหน่วยความจำเลยในรหัสที่ใช้หน่วยความจำ สิ่งเดียวที่เราต้องทำให้แน่ใจคือวัตถุที่เราเขียนมีตัวทำลายที่เหมาะสม ไม่ว่าเราจะออกจากขอบเขตlistTest
ไม่ว่าจะผ่านข้อยกเว้นหรือเพียงแค่กลับมาจากมันผู้ทำลายระบบ~MyList()
จะถูกเรียกใช้และเราไม่จำเป็นต้องจัดการหน่วยความจำใด ๆ
(ฉันคิดว่ามันเป็นการตัดสินใจออกแบบที่ตลกที่จะใช้ตัวดำเนินการNOT แบบไบนารี~
เพื่อบ่งบอกถึง destructor เมื่อใช้กับตัวเลขมันจะทำการสลับบิตในการเปรียบเทียบที่นี่เป็นการบ่งบอกว่าสิ่งที่ Constructor ทำนั้นคว่ำ
โดยทั่วไปวัตถุ C ++ ที่ต้องการหน่วยความจำแบบไดนามิกใช้การห่อหุ้มนี้ มันถูกเรียกว่า RAII ("การได้มาของทรัพยากรคือการเริ่มต้น") ซึ่งค่อนข้างแปลกที่จะแสดงความคิดง่ายๆที่วัตถุสนใจเกี่ยวกับเนื้อหาของตัวเอง สิ่งที่พวกเขาได้มาคือการทำความสะอาด
วัตถุ Polymorphic และอายุการใช้งานเกินขอบเขต
ตอนนี้ทั้งสองกรณีนี้มีไว้สำหรับหน่วยความจำที่มีอายุการใช้งานที่กำหนดไว้อย่างชัดเจน: อายุการใช้งานเหมือนกับขอบเขต หากเราไม่ต้องการให้วัตถุหมดอายุเมื่อเราออกจากขอบเขตมีกลไกที่สามที่สามารถจัดการหน่วยความจำสำหรับเรา: ตัวชี้สมาร์ท ตัวชี้สมาร์ทยังใช้เมื่อคุณมีอินสแตนซ์ของวัตถุที่มีชนิดแตกต่างกันในรันไทม์ แต่มีอินเทอร์เฟซทั่วไปหรือคลาสพื้นฐาน:
class MyDerivedObject : public MyObject {
public: int y;
};
std::unique_ptr<MyObject> createObject()
{
// actually creates an object of a derived class,
// but the user doesn't need to know this.
return std::make_unique<MyDerivedObject>();
}
int dynamicObjTest()
{
std::unique_ptr<MyObject> obj = createObject();
obj->x = 5;
return obj->x;
// At scope end, the unique_ptr automatically removes the object it contains,
// calling its destructor if it has one.
}
มีตัวชี้สมาร์ทอีกประเภทหนึ่งstd::shared_ptr
สำหรับการแชร์ออบเจกต์กับลูกค้าหลายราย พวกเขาจะลบวัตถุที่มีอยู่ของพวกเขาเมื่อไคลเอนต์ล่าสุดออกจากขอบเขตดังนั้นพวกเขาสามารถใช้ในสถานการณ์ที่ไม่เป็นที่รู้จักโดยสมบูรณ์จำนวนลูกค้าที่จะมีและระยะเวลาที่พวกเขาจะใช้วัตถุ
โดยสรุปเราเห็นว่าคุณไม่ได้จัดการหน่วยความจำด้วยตนเองเลย ทุกอย่างถูกห่อหุ้มและได้รับการดูแลโดยการจัดการหน่วยความจำตามขอบเขตโดยอัตโนมัติ ในกรณีที่สิ่งนี้ไม่เพียงพอตัวชี้สมาร์ทจะใช้ซึ่งห่อหุ้มหน่วยความจำดิบ
ถือว่าเป็นวิธีที่ไม่ดีอย่างยิ่งที่จะใช้พอยน์เตอร์ดิบเป็นเจ้าของทรัพยากรในรหัส C ++, การจัดสรรดิบนอกคอนสตรัคเตอร์, และการdelete
โทรดิบนอก destructors, เนื่องจากพวกเขาแทบจะไม่สามารถจัดการได้เมื่อมีข้อยกเว้นเกิดขึ้น
สิ่งที่ดีที่สุด: สิ่งนี้ใช้ได้กับทรัพยากรทุกประเภท
หนึ่งในประโยชน์ที่ใหญ่ที่สุดของ RAII คือมันไม่ได้ จำกัด อยู่แค่ในหน่วยความจำ จริงๆแล้วมันเป็นวิธีที่เป็นธรรมชาติมากในการจัดการทรัพยากรเช่นไฟล์และซ็อกเก็ต (เปิด / ปิด) และกลไกการซิงโครไนซ์เช่น mutexes (ล็อค / ปลดล็อค) โดยทั่วไปทรัพยากรทุกอย่างที่สามารถได้มาและต้องได้รับการปล่อยตัวจะได้รับการจัดการด้วยวิธีเดียวกันใน C ++ และไม่มีการจัดการใดที่เหลือให้กับผู้ใช้ มันถูกห่อหุ้มทั้งหมดในชั้นเรียนที่ได้รับในตัวสร้างและปล่อยใน destructor
ตัวอย่างเช่นฟังก์ชั่นล็อค mutex มักจะเขียนเช่นนี้ใน C ++:
void criticalSection() {
std::scoped_lock lock(myMutex); // scoped_lock locks the mutex
doSynchronizedStuff();
} // myMutex is released here automatically
ภาษาอื่นทำให้มีความซับซ้อนมากขึ้นโดยกำหนดให้คุณทำสิ่งนี้ด้วยตนเอง (เช่นในfinally
ข้อ) หรือวางกลไกพิเศษที่ช่วยแก้ปัญหานี้ แต่ไม่ใช่ในรูปแบบที่สวยงามเป็นพิเศษ (โดยปกติแล้วต่อมาในชีวิตเมื่อมีคนมากพอ รับความเดือดร้อนจากข้อบกพร่อง) กลไกดังกล่าวเป็นลองกับทรัพยากรใน Java และคำสั่งการใช้งานใน C # ซึ่งทั้งสองอย่างนี้เป็นค่าประมาณของ RAII ของ C ++
ดังนั้นเพื่อสรุปทั้งหมดนี้เป็นบัญชีที่ตื้นมากของ RAII ใน C ++ แต่ฉันหวังว่ามันจะช่วยให้ผู้อ่านเข้าใจว่าหน่วยความจำและการจัดการทรัพยากรใน C ++ นั้นไม่ใช่ "คู่มือ" แต่จริงๆแล้วเป็นไปโดยอัตโนมัติ