RAII และตัวชี้สมาร์ทใน C ++


193

ในทางปฏิบัติกับ C ++ RAIIคืออะไรพอยน์เตอร์อัจฉริยะคืออะไรสิ่งเหล่านี้นำไปใช้ในโปรแกรมได้อย่างไรและประโยชน์ของการใช้ RAII กับพอยน์เตอร์อัจฉริยะคืออะไร

คำตอบ:


317

ตัวอย่างง่ายๆ (และอาจใช้มากเกินไป) ของ RAII คือคลาสไฟล์ หากไม่มี RAII รหัสอาจมีลักษณะเช่นนี้:

File file("/path/to/file");
// Do stuff with file
file.close();

กล่าวอีกนัยหนึ่งเราต้องทำให้แน่ใจว่าเราปิดไฟล์เมื่อเราทำเสร็จแล้ว สิ่งนี้มีข้อเสียสองประการประการแรกไม่ว่าเราจะใช้ไฟล์ที่ไหนเราจะต้องเรียกว่าไฟล์ :: ปิด () - หากเราลืมทำสิ่งนี้เราจะถือไฟล์ไว้นานกว่าที่เราต้องการ ปัญหาที่สองคือเกิดอะไรขึ้นถ้ามีข้อผิดพลาดเกิดขึ้นก่อนที่เราจะปิดไฟล์

Java แก้ปัญหาที่สองโดยใช้ประโยคสุดท้าย:

try {
    File file = new File("/path/to/file");
    // Do stuff with file
} finally {
    file.close();
}

หรือตั้งแต่ Java 7 คำสั่งลองกับทรัพยากร:

try (File file = new File("/path/to/file")) {
   // Do stuff with file
}

C ++ แก้ปัญหาทั้งสองอย่างโดยใช้ RAII - นั่นคือปิดไฟล์ในตัวทำลายของไฟล์ ตราบใดที่วัตถุไฟล์ถูกทำลายในเวลาที่เหมาะสม (ซึ่งควรจะเป็นต่อไป) การปิดไฟล์จะได้รับการดูแลจากเรา ดังนั้นรหัสของเราตอนนี้ดูเหมือน:

File file("/path/to/file");
// Do stuff with file
// No need to close it - destructor will do that for us

สิ่งนี้ไม่สามารถทำได้ใน Java เนื่องจากไม่มีการรับประกันเมื่อวัตถุจะถูกทำลายดังนั้นเราจึงไม่สามารถรับประกันได้ว่าเมื่อใดที่ทรัพยากรเช่นไฟล์จะถูกปลดปล่อย

บนพอยน์เตอร์อัจฉริยะ - บ่อยครั้งที่เราเพิ่งสร้างวัตถุบนสแต็ก เช่น (และขโมยตัวอย่างจากคำตอบอื่น):

void foo() {
    std::string str;
    // Do cool things to or using str
}

มันใช้งานได้ดี - แต่ถ้าเราต้องการคืนค่า STR? เราสามารถเขียนสิ่งนี้:

std::string foo() {
    std::string str;
    // Do cool things to or using str
    return str;
}

ดังนั้นมีอะไรผิดปกติกับที่? ประเภทการส่งคืนคือ std :: string - ดังนั้นจึงหมายความว่าเราคืนค่า ซึ่งหมายความว่าเราคัดลอก str และคืนค่าสำเนาจริง สิ่งนี้อาจมีราคาแพงและเราอาจต้องการหลีกเลี่ยงค่าใช้จ่ายในการคัดลอก ดังนั้นเราอาจเกิดความคิดในการกลับมาโดยการอ้างอิงหรือโดยตัวชี้

std::string* foo() {
    std::string str;
    // Do cool things to or using str
    return &str;
}

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

ดังนั้นทางออกคืออะไร? เราสามารถสร้าง str บน heap โดยใช้ new - วิธีนี้เมื่อ foo () เสร็จสมบูรณ์ str จะไม่ถูกทำลาย

std::string* foo() {
    std::string* str = new std::string();
    // Do cool things to or using str
    return str;
}

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

นี่คือที่มาของสมาร์ทพอยน์เตอร์ตัวอย่างต่อไปนี้ใช้ shared_ptr - ฉันแนะนำให้คุณดูพอยน์เตอร์สมาร์ทประเภทต่าง ๆ เพื่อเรียนรู้สิ่งที่คุณต้องการใช้จริง

shared_ptr<std::string> foo() {
    shared_ptr<std::string> str = new std::string();
    // Do cool things to or using str
    return str;
}

ตอนนี้ shared_ptr จะนับจำนวนการอ้างอิงถึง str ตัวอย่างเช่น

shared_ptr<std::string> str = foo();
shared_ptr<std::string> str2 = str;

ตอนนี้มีสองการอ้างอิงถึงสตริงเดียวกัน เมื่อไม่มีการอ้างอิงไปยัง str แล้วจะถูกลบทิ้ง คุณไม่ต้องกังวลเกี่ยวกับการลบด้วยตัวเองอีกต่อไป

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

ดังนั้นลองทำตัวอย่างอื่นโดยใช้คลาสไฟล์ของเรา

สมมติว่าเราต้องการใช้ไฟล์เป็นบันทึก หมายความว่าเราต้องการเปิดไฟล์ของเราในโหมดต่อท้ายเท่านั้น:

File file("/path/to/file", File::append);
// The exact semantics of this aren't really important,
// just that we've got a file to be used as a log

ตอนนี้ให้ตั้งไฟล์ของเราเป็นบันทึกสำหรับวัตถุอื่นสองสามอย่าง:

void setLog(const Foo & foo, const Bar & bar) {
    File file("/path/to/file", File::append);
    foo.setLogFile(file);
    bar.setLogFile(file);
}

น่าเสียดายที่ตัวอย่างนี้จบลงอย่างน่ากลัว - ไฟล์จะถูกปิดทันทีที่วิธีนี้สิ้นสุดซึ่งหมายความว่าตอนนี้ foo และ bar มีไฟล์บันทึกที่ไม่ถูกต้อง เราสามารถสร้างไฟล์บนฮีปและส่งต่อตัวชี้ไปยังไฟล์ไปที่ foo และ bar:

void setLog(const Foo & foo, const Bar & bar) {
    File* file = new File("/path/to/file", File::append);
    foo.setLogFile(file);
    bar.setLogFile(file);
}

แต่ใครจะเป็นผู้รับผิดชอบในการลบไฟล์? หากไม่ลบไฟล์เราก็มีทั้งหน่วยความจำและทรัพยากรรั่วไหล เราไม่ทราบว่า foo หรือ bar จะเสร็จสิ้นไฟล์ก่อนดังนั้นเราจึงไม่สามารถคาดหวังว่าจะลบไฟล์เอง ตัวอย่างเช่นหาก foo ลบไฟล์ก่อนที่แถบจะเสร็จสิ้นแถบตอนนี้จะมีตัวชี้ที่ไม่ถูกต้อง

ดังนั้นตามที่คุณอาจเดาได้เราสามารถใช้ตัวชี้อัจฉริยะเพื่อช่วยเราออก

void setLog(const Foo & foo, const Bar & bar) {
    shared_ptr<File> file = new File("/path/to/file", File::append);
    foo.setLogFile(file);
    bar.setLogFile(file);
}

ตอนนี้ไม่มีใครต้องกังวลเกี่ยวกับการลบไฟล์ - เมื่อทั้ง foo และ bar เสร็จสิ้นแล้วและไม่มีการอ้างอิงถึงไฟล์อีกต่อไป (อาจเป็นเพราะ foo และ bar ถูกทำลาย) ไฟล์จะถูกลบโดยอัตโนมัติ


7
ควรสังเกตว่ามีการใช้งานสตริงจำนวนมากในแง่ของตัวชี้นับการอ้างอิง ความหมายของ copy-on-write เหล่านี้ทำให้การส่งคืนสตริงโดยค่าไม่แพงจริงๆ

7
แม้สำหรับผู้ที่ไม่ได้คอมไพเลอร์จำนวนมากยังใช้การเพิ่มประสิทธิภาพ NRV ซึ่งจะดูแลค่าใช้จ่าย โดยทั่วไปแล้วฉันพบว่า shared_ptr ไม่ค่อยมีประโยชน์ - แค่ติดกับ RAII และหลีกเลี่ยงการเป็นเจ้าของที่ใช้ร่วมกัน
Nemanja Trifunovic

27
การส่งคืนสตริงไม่ใช่เหตุผลที่ดีในการใช้พอยน์เตอร์อัจฉริยะ การเพิ่มประสิทธิภาพของค่าตอบแทนสามารถเพิ่มประสิทธิภาพการคืนได้อย่างง่ายดายและ c ++ 1x ซีแมนทิกส์การย้ายจะกำจัดสำเนาทั้งหมด (เมื่อใช้อย่างถูกต้อง) แสดงตัวอย่างโลกแห่งความจริง (ตัวอย่างเช่นเมื่อเราแบ่งปันทรัพยากรเดียวกัน) แทน :)
Johannes Schaub - litb

1
ฉันคิดว่าข้อสรุปของคุณในช่วงต้นเกี่ยวกับสาเหตุที่ Java ไม่สามารถทำสิ่งนี้ขาดความชัดเจน วิธีที่ง่ายที่สุดในการอธิบายข้อ จำกัด นี้ใน Java หรือ C # คือเนื่องจากไม่มีวิธีการจัดสรรบนสแต็ก C # ช่วยให้การจัดสรรสแต็คผ่านคำหลักพิเศษอย่างไรก็ตามคุณจะสูญเสียความปลอดภัยในการพิมพ์
ApplePieIsGood

4
@Nemanja Trifunovic: โดย RAII ในบริบทนี้คุณหมายถึงการส่งคืนสำเนา / การสร้างวัตถุในกองหรือไม่ มันไม่ทำงานถ้าคุณมีวัตถุประเภทที่สามารถส่งคืน / รับประเภท subclassed จากนั้นคุณต้องใช้ตัวชี้เพื่อหลีกเลี่ยงการแบ่งวัตถุและฉันขอยืนยันว่าตัวชี้สมาร์ทมักจะดีกว่าตัวชี้แบบดิบในกรณีเหล่านั้น
Frank Osterfeld

141

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

เพียงแค่ทำตัวอย่างโดยไม่ใช้ SBRM:

void o_really() {
     resource * r = allocate_resource();
     try {
         // something, which could throw. ...
     } catch(...) {
         deallocate_resource(r);
         throw;
     }
     if(...) { return; } // oops, forgot to deallocate
     deallocate_resource(r);
}

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

struct resource_holder {
    resource_holder() {
        r = allocate_resource();
    }
    ~resource_holder() {
        deallocate_resource(r);
    }
    resource * r;
};

void o_really() {
     resource_holder r;
     // something, which could throw. ...
     if(...) { return; }
}

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

shared_ptr<Entry> create_entry(Parameters p) {
    shared_ptr<Entry> e(Entry::createEntry(p), &Entry::freeEntry);
    return e;
}

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

มีตัวชี้สมาร์ทที่แตกต่างกันสำหรับวัตถุประสงค์ที่แตกต่างกัน:

unique_ptr

เป็นตัวชี้สมาร์ทซึ่งเป็นเจ้าของวัตถุโดยเฉพาะ มันไม่ได้อยู่ในการเพิ่มประสิทธิภาพ แต่มีแนวโน้มว่าจะปรากฏใน C ++ มาตรฐานถัดไป มันไม่ใช่ copyableแต่สนับสนุนการถ่ายโอนการเป็นเจ้าของ ตัวอย่างโค้ด (C ++ ถัดไป):

รหัส:

unique_ptr<plot_src> p(new plot_src); // now, p owns
unique_ptr<plot_src> u(move(p)); // now, u owns, p owns nothing.
unique_ptr<plot_src> v(u); // error, trying to copy u

vector<unique_ptr<plot_src>> pv; 
pv.emplace_back(new plot_src); 
pv.emplace_back(new plot_src);

ซึ่งแตกต่างจาก auto_ptr, unique_ptr สามารถใส่ลงในคอนเทนเนอร์ได้เนื่องจากคอนเทนเนอร์จะสามารถเก็บประเภทที่ไม่สามารถคัดลอกได้ (แต่เคลื่อนย้ายได้) เช่นสตรีมและ unique_ptr

scoped_ptr

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

รหัส:

void do_something() {
    scoped_ptr<pipe> sp(new pipe);
    // do something here...
} // when going out of scope, sp will delete the pointer automatically. 

shared_ptr

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

รหัส:

shared_ptr<plot_src> p(new plot_src(&fx));
plot1->add(p)->setColor("#00FF00");
plot2->add(p)->setColor("#FF0000");
// if p now goes out of scope, the src won't be freed, as both plot1 and 
// plot2 both still have references. 

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


เท่าที่ฉันรู้ว่าวัตถุที่ไม่สามารถคัดลอกได้นั้นไม่ดีในการใช้งานในคอนเทนเนอร์ stl เพราะพวกเขาพึ่งพา semantics ที่มีคุณค่า - จะเกิดอะไรขึ้นถ้าคุณต้องการเรียงลำดับคอนเทนเนอร์นั้น? การจัดเรียงองค์ประกอบไม่สำเนา ...
fmuecke

คอนเทนเนอร์ C ++ 0x จะถูกเปลี่ยนเพื่อให้เป็นไปตามประเภทของการย้ายอย่างเดียวunique_ptrและsortจะถูกเปลี่ยนเช่นกัน
Johannes Schaub - litb

คุณจำที่ที่คุณได้ยินคำว่า SBRM เป็นครั้งแรกหรือไม่ เจมส์พยายามติดตามมัน
GManNickG

ฉันควรรวมส่วนหัวหรือไลบรารีใดไว้เพื่อใช้สิ่งเหล่านี้ อ่านเพิ่มเติมเกี่ยวกับเรื่องนี้?
atoMerz

คำแนะนำหนึ่งที่นี่: หากมีคำตอบสำหรับคำถาม C ++ โดย @litb มันเป็นคำตอบที่ถูกต้อง (ไม่ว่าคะแนนโหวตหรือคำตอบจะถูกตั้งค่าสถานะเป็น "ถูกต้อง") ...
fnl

32

แนวคิดและเหตุผลนั้นเรียบง่ายในแนวคิด

RAII เป็นกระบวนทัศน์การออกแบบเพื่อให้แน่ใจว่าตัวแปรจัดการกับการเริ่มต้นทั้งหมดที่จำเป็นในการก่อสร้างของพวกเขาและการทำความสะอาดที่จำเป็นใน destructors ของพวกเขาทั้งหมด ซึ่งจะช่วยลดการเริ่มต้นและการล้างข้อมูลทั้งหมดในขั้นตอนเดียว

C ++ ไม่ต้องการ RAII แต่เป็นที่ยอมรับมากขึ้นว่าการใช้วิธี RAII จะสร้างรหัสที่มีประสิทธิภาพมากขึ้น

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

ด้วยการคาดเดาการเริ่มต้นและการล้างข้อมูลให้กับกลไกเหล่านี้คุณจะมั่นใจได้ว่า C ++ จะดูแลงานนี้ให้คุณเช่นกัน

การพูดเกี่ยวกับ RAII ใน C ++ มักจะนำไปสู่การอภิปรายของพอยน์เตอร์อัจฉริยะเนื่องจากพอยน์เตอร์นั้นบอบบางโดยเฉพาะเมื่อทำความสะอาด เมื่อจัดการหน่วยความจำที่จัดสรรฮีปที่ได้รับจาก malloc หรือใหม่โดยทั่วไปแล้วมันเป็นความรับผิดชอบของโปรแกรมเมอร์ที่จะทำให้หน่วยความจำว่างหรือลบหน่วยความจำก่อนที่ตัวชี้จะถูกทำลาย พอยน์เตอร์อัจฉริยะจะใช้ปรัชญา RAII เพื่อให้แน่ใจว่าวัตถุที่จัดสรรฮีปถูกทำลายเมื่อใดก็ตามที่ตัวแปรตัวชี้ถูกทำลาย


นอกจากนี้ - พอยน์เตอร์เป็นแอปพลิเคชั่นที่พบได้บ่อยที่สุดของ RAII - คุณอาจจัดสรรพอยน์เตอร์ได้มากกว่าทรัพยากรอื่น ๆ นับพันเท่า
Eclipse

8

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

  1. เราจัดสรรหน่วยความจำก่อนที่เราจะใช้เสมอแม้ว่าเราจะไม่รู้สึกเช่นนี้ - มันยากที่จะทำวิธีอื่นด้วยตัวชี้อัจฉริยะ หากสิ่งนี้ไม่ได้เกิดขึ้นคุณจะพยายามเข้าถึงหน่วยความจำ NULL ทำให้เกิดความผิดพลาด (เจ็บปวดมาก)
  2. เราเพิ่มหน่วยความจำแม้ในขณะที่มีข้อผิดพลาด ไม่มีหน่วยความจำเหลือค้างอยู่

ตัวอย่างเช่นอีกตัวอย่างหนึ่งคือซ็อกเก็ตเครือข่าย RAII ในกรณีนี้:

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

ตอนนี้อย่างที่คุณเห็น RAII เป็นเครื่องมือที่มีประโยชน์มากในกรณีส่วนใหญ่ที่ช่วยให้ผู้คนได้รับการวาง

แหล่งที่มาของสมาร์ทพอยน์เตอร์ C ++ นั้นมีอยู่เป็นล้าน ๆ รายการทั่วโลก


2

Boost มีจำนวนของสิ่งเหล่านี้รวมถึงที่อยู่ในBoost.Interprocessสำหรับหน่วยความจำที่ใช้ร่วมกัน มันช่วยลดความยุ่งยากในการจัดการหน่วยความจำโดยเฉพาะอย่างยิ่งในสถานการณ์ที่ก่อให้เกิดอาการปวดหัวเช่นเมื่อคุณมี 5 กระบวนการที่แชร์โครงสร้างข้อมูลเดียวกัน: เมื่อทุกคนทำกับหน่วยความจำจำนวนมากคุณต้องการให้มันเป็นอิสระโดยอัตโนมัติ ผู้ที่ควรรับผิดชอบในการเรียกdeleteใช้หน่วยความจำอันธพาลว่าคุณจะจบลงด้วยการรั่วไหลของหน่วยความจำหรือตัวชี้ที่เป็นอิสระผิดสองครั้งและอาจทำให้ทั้งกองเสียหาย


0
โมฆะ foo ()
{
   std :: แถบสตริง;
   //
   // รหัสเพิ่มเติมที่นี่
   //
}

ไม่ว่าจะเกิดอะไรขึ้นแถบจะถูกลบอย่างถูกต้องเมื่อขอบเขตของฟังก์ชั่น foo () ถูกทิ้งไว้ข้างหลัง

การใช้งานสตริงภายใน :: มักจะใช้ตัวชี้นับการอ้างอิง ดังนั้นสตริงภายในจะต้องคัดลอกเมื่อหนึ่งในสำเนาของสตริงเปลี่ยนไป ดังนั้นการอ้างอิงสมาร์ทพอยน์เตอร์นับจึงเป็นไปได้ที่จะคัดลอกบางสิ่งเมื่อจำเป็น

นอกจากนี้การนับการอ้างอิงภายในทำให้เป็นไปได้ว่าหน่วยความจำจะถูกลบอย่างถูกต้องเมื่อไม่จำเป็นต้องคัดลอกสตริงภายใน


1
เป็นโมฆะ f () {Obj x; } Obj x ถูกลบโดยวิธีการสร้าง / ทำลายกองซ้อน (ไม่คลี่คลาย) ... ไม่เกี่ยวข้องกับการนับการอ้างอิง
Hernán

การนับการอ้างอิงเป็นคุณลักษณะของการนำสตริงไปใช้ภายใน RAII เป็นแนวคิดเบื้องหลังการลบวัตถุเมื่อวัตถุไม่อยู่ในขอบเขต คำถามเกี่ยวกับ RAII และตัวชี้สมาร์ท

1
"ไม่ว่าจะเกิดอะไรขึ้น" - จะเกิดอะไรขึ้นถ้ามีข้อผิดพลาดเกิดขึ้นก่อนที่ฟังก์ชันจะส่งคืน
titaniumdecoy

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