C ++ รองรับการบล็อก 'ที่สุด' หรือไม่? (และนี่คือ 'RAII' ที่ฉันคอยฟัง?)


272

C ++ รองรับการบล็อก ' ที่สุด ' หรือไม่?

อะไรคือ สำนวน RAII ?

อะไรคือความแตกต่างระหว่างคำสั่ง RAII ของ C ++ กับการใช้คำสั่ง 'C' ?


1
โปรดดูคำตอบใน: stackoverflow.com/questions/7779652/ …
18446744073709551615

คำตอบ:


273

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

ความคิดคือว่า destructor ของวัตถุรับผิดชอบการปลดปล่อยทรัพยากร เมื่อวัตถุนั้นมีระยะเวลาการจัดเก็บอัตโนมัติตัวทำลายของวัตถุจะถูกเรียกเมื่อบล็อกที่มันถูกสร้างออก - แม้เมื่อบล็อกนั้นถูกออกในสถานะที่มีข้อยกเว้น นี่คือคำอธิบายของ Bjarne Stroustrupของหัวข้อ

การใช้งานทั่วไปสำหรับ RAII กำลังล็อค mutex:

// A class with implements RAII
class lock
{
    mutex &m_;

public:
    lock(mutex &m)
      : m_(m)
    {
        m.acquire();
    }
    ~lock()
    {
        m_.release();
    }
};

// A class which uses 'mutex' and 'lock' objects
class foo
{
    mutex mutex_; // mutex for locking 'foo' object
public:
    void bar()
    {
        lock scopeLock(mutex_); // lock object.

        foobar(); // an operation which may throw an exception

        // scopeLock will be destructed even if an exception
        // occurs, which will release the mutex and allow
        // other functions to lock the object and run.
    }
};

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

สำหรับผู้ที่มี familliar C # หรือ VB.NET คุณอาจรู้ว่า RAII คล้ายกับ.NET ทำลายกำหนดใช้ IDisposable และ 'ใช้' งบ แน่นอนว่าทั้งสองวิธีมีความคล้ายคลึงกันมาก ข้อแตกต่างที่สำคัญคือ RAII จะปล่อยทรัพยากรประเภทใด ๆ อย่างแน่นอนรวมถึงหน่วยความจำ เมื่อใช้งาน IDisposable ใน. NET (แม้แต่ภาษา. NET C ++ / CLI) ทรัพยากรจะถูกกำหนดอย่างปลอดภัยยกเว้นหน่วยความจำ ใน. NET หน่วยความจำจะไม่ถูกกำหนดอย่างแน่นอน หน่วยความจำจะถูกปล่อยออกมาเฉพาะในระหว่างรอบการรวบรวมขยะ

 

†บางคนเชื่อว่า "การทำลายล้างคือการปลดปล่อยทรัพยากร" เป็นชื่อที่แม่นยำยิ่งขึ้นสำหรับ RAII สำนวน


18
"การทำลายคือการปลดปล่อยทรัพยากร" - DIRR ... ไม่ไม่ทำงานสำหรับฉัน = P
Erik Forbes

14
RAII ติดอยู่ - ไม่มีการเปลี่ยนแปลงเลย พยายามทำเช่นนั้นจะเป็นคนโง่ อย่างไรก็ตามคุณต้องยอมรับว่า "การได้รับทรัพยากรเป็นการเริ่มต้น" ยังคงเป็นชื่อที่น่าสงสาร
เควิน

162
SBRM == ขอบเขตการจัดการทรัพยากรที่ถูกผูกไว้
Johannes Schaub - litb

10
ทุกคนที่มีความสามารถในการสร้างวิศวกรไม่เพียง แต่ซอฟต์แวร์โดยทั่วไปเทคนิคการปรับปรุงที่ดีเพียงอย่างเดียวไม่สามารถให้ข้อแก้ตัวที่น่าสมเพชสำหรับคำย่อที่น่ากลัวได้
Hardryv

54
สิ่งนี้จะทำให้คุณติดเมื่อคุณมีบางสิ่งบางอย่างในการล้างข้อมูลที่ไม่ตรงกับอายุการใช้งานของวัตถุ C ++ ฉันเดาว่าคุณจะจบลงด้วยอายุการใช้งานเท่ากับ C ++ คลาส Liftime หรืออื่น ๆ ที่มันน่าเกลียด (LECCLEOEIGU?)
Warren P

79

ใน C ++ ในที่สุดไม่จำเป็นต้องใช้เพราะ RAII

RAII ย้ายความรับผิดชอบของข้อยกเว้นด้านความปลอดภัยจากผู้ใช้ของวัตถุไปยังนักออกแบบ (และผู้ดำเนินการ) ของวัตถุ ฉันจะโต้แย้งว่านี่เป็นสถานที่ที่ถูกต้องเพราะคุณจะต้องได้รับการยกเว้นด้านความปลอดภัยที่ถูกต้องเพียงครั้งเดียว (ในการออกแบบ / การนำไปใช้) ในการใช้งานครั้งสุดท้ายคุณจะต้องได้รับการยกเว้นด้านความปลอดภัยที่ถูกต้องทุกครั้งที่คุณใช้วัตถุ

นอกจากนี้ IMO โค้ดยังมีลักษณะที่ดูดีขึ้น

ตัวอย่าง:

วัตถุฐานข้อมูล เพื่อให้แน่ใจว่ามีการใช้การเชื่อมต่อฐานข้อมูลจะต้องเปิดและปิด โดยใช้ RAII สิ่งนี้สามารถทำได้ในตัวสร้าง / destructor

C ++ เช่น RAII

void someFunc()
{
    DB    db("DBDesciptionString");
    // Use the db object.

} // db goes out of scope and destructor closes the connection.
  // This happens even in the presence of exceptions.

การใช้ RAII ทำให้การใช้วัตถุ DB นั้นง่ายมากอย่างถูกต้อง วัตถุฐานข้อมูลจะปิดตัวเองอย่างถูกต้องโดยการใช้ destructor ไม่ว่าเราจะลองและใช้ในทางที่ผิด

Java Like ที่สุด

void someFunc()
{
    DB      db = new DB("DBDesciptionString");
    try
    {
        // Use the db object.
    }
    finally
    {
        // Can not rely on finaliser.
        // So we must explicitly close the connection.
        try
        {
            db.close();
        }
        catch(Throwable e)
        {
           /* Ignore */
           // Make sure not to throw exception if one is already propagating.
        }
    }
}

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

นี่เป็นตัวอย่างง่ายๆ
เมื่อคุณมีทรัพยากรหลายอย่างที่จำเป็นต้องได้รับการปล่อยตัวรหัสจะซับซ้อน

สามารถดูการวิเคราะห์โดยละเอียดเพิ่มเติมได้ที่นี่: http://accu.org/index.php/journals/236


16
// Make sure not to throw exception if one is already propagating.มันเป็นสิ่งสำคัญสำหรับ C + + destructors ที่จะไม่โยนข้อยกเว้นด้วยเหตุผลนี้
Cemafor

10
@Cemafor: เหตุผลสำหรับ C ++ ที่จะไม่ยกเว้นข้อยกเว้นจาก destructor นั้นแตกต่างจาก Java ใน Java มันจะทำงานได้ (คุณเพียงแค่ปล่อยข้อยกเว้นเดิม) ใน C ++ มันแย่จริงๆ แต่ประเด็นใน C ++ คือคุณต้องทำเพียงครั้งเดียว (โดยผู้ออกแบบคลาส) เมื่อเขาเขียน destructor ใน Java คุณต้องทำ ณ จุดใช้งาน ดังนั้นจึงเป็นความรับผิดชอบของผู้ใช้งานในชั้นเรียนที่จะต้องเขียนแผ่นหม้อน้ำเหมือนกันในเวลาอันรวดเร็ว
Martin York

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

1
@ ตรินิแดด: มันไม่ง่ายอย่างที่คุณคิด (ตามคำแนะนำทั้งหมดของคุณดูเหมือนจะเลือกตัวเลือกที่แย่ที่สุดเท่าที่จะเป็นไปได้) นี่คือสาเหตุที่คำถามอาจเป็นสถานที่ที่ดีกว่าในการสำรวจความคิดเห็นนี้
Martin York

1
การวิพากษ์วิจารณ์ "ไม่จำเป็นเพราะ RAII": มีหลายกรณีที่การเพิ่มโฆษณา RAII จะเป็นรหัสสำเร็จรูปมากเกินกว่าที่จะเพิ่มและท้ายที่สุดการทดลองก็เหมาะสมอย่างยิ่ง
ceztko

63

RAII มักจะดีกว่า แต่คุณสามารถมีความหมายได้ง่ายที่สุดใน C ++ ใช้รหัสจำนวนเล็กน้อย

นอกจากนี้แนวทางหลักของ C ++ ก็ให้ในที่สุด

นี่คือลิงค์สำหรับการนำGSL Microsoft ไปใช้และลิงค์ไปยังMartin Moene

Bjarne Stroustrup หลายครั้งกล่าวว่าทุกสิ่งที่อยู่ใน GSL นั้นจะต้องอยู่ในมาตรฐานในที่สุด ดังนั้นจึงควรจะเป็นวิธีที่หลักฐานในอนาคตที่จะใช้ในที่สุดในที่สุด

คุณสามารถปรับใช้เองได้อย่างง่ายดายหากต้องการอ่านต่อ

ใน C ++ 11 RAII และ lambdas อนุญาตให้สร้าง general ในที่สุด:

namespace detail { //adapt to your "private" namespace
template <typename F>
struct FinalAction {
    FinalAction(F f) : clean_{f} {}
   ~FinalAction() { if(enabled_) clean_(); }
    void disable() { enabled_ = false; };
  private:
    F clean_;
    bool enabled_{true}; }; }

template <typename F>
detail::FinalAction<F> finally(F f) {
    return detail::FinalAction<F>(f); }

ตัวอย่างการใช้งาน:

#include <iostream>
int main() {
    int* a = new int;
    auto delete_a = finally([a] { delete a; std::cout << "leaving the block, deleting a!\n"; });
    std::cout << "doing something ...\n"; }

ผลลัพธ์จะเป็น:

doing something...
leaving the block, deleting a!

โดยส่วนตัวฉันใช้สองสามครั้งนี้เพื่อให้แน่ใจว่าได้ปิด POSIX file descriptor ในโปรแกรม C ++

การมีคลาสจริงที่จัดการทรัพยากรและหลีกเลี่ยงการรั่วไหลใด ๆ มักจะดีกว่า แต่ในที่สุดสิ่งนี้มีประโยชน์ในกรณีที่การเรียนเป็นเสียงเหมือน overkill

นอกจากนี้ฉันชอบมันดีกว่าภาษาอื่น ๆในที่สุดเพราะถ้าใช้ตามปกติคุณเขียนรหัสปิดใกล้เคียงกับรหัสเปิด (ในตัวอย่างของฉันใหม่และลบ ) และการทำลายดังต่อไปนี้การก่อสร้างตามลำดับ LIFO ตามปกติใน C ++ ข้อเสียเพียงอย่างเดียวคือคุณได้รับตัวแปรอัตโนมัติที่คุณไม่ได้ใช้จริงและไวยากรณ์แลมบ์ดาทำให้มันมีเสียงดังนิดหน่อย (ในตัวอย่างของฉันในบรรทัดที่สี่คำสุดท้ายเท่านั้นและ {} -block ทางด้านขวามีความหมาย ส่วนที่เหลือเป็นเสียงรบกวนเป็นหลัก)

ตัวอย่างอื่น:

 [...]
 auto precision = std::cout.precision();
 auto set_precision_back = finally( [precision, &std::cout]() { std::cout << std::setprecision(precision); } );
 std::cout << std::setprecision(3);

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

ปิดการใช้งานตัวอย่าง:

//strong guarantee
void copy_to_all(BIGobj const& a) {
    first_.push_back(a);
    auto undo_first_push = finally([first_&] { first_.pop_back(); });

    second_.push_back(a);
    auto undo_second_push = finally([second_&] { second_.pop_back(); });

    third_.push_back(a);
    //no necessary, put just to make easier to add containers in the future
    auto undo_third_push = finally([third_&] { third_.pop_back(); });

    undo_first_push.disable();
    undo_second_push.disable();
    undo_third_push.disable(); }

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

#include <iostream>
int main() {
    int* a = new int;

    struct Delete_a_t {
        Delete_a_t(int* p) : p_(p) {}
       ~Delete_a_t() { delete p_; std::cout << "leaving the block, deleting a!\n"; }
        int* p_;
    } delete_a(a);

    std::cout << "doing something ...\n"; }

อาจมีปัญหาที่เป็นไปได้: ในฟังก์ชั่น 'สุดท้าย (F f)' มันจะส่งคืนวัตถุของ FinalAction ดังนั้น Deconstructor อาจถูกเรียกก่อนที่จะกลับมาทำงานในที่สุด บางทีเราควรใช้ std :: function แทน template F.
user1633272

โปรดทราบว่าFinalActionโดยทั่วไปแล้วจะเหมือนกับScopeGuardสำนวนยอดนิยมเท่านั้นที่มีชื่อแตกต่างกัน
anderas

1
การเพิ่มประสิทธิภาพนี้ปลอดภัยหรือไม่
Nulano

2
@ Paolo.Bolzoni ขออภัยที่ไม่ตอบกลับเร็วกว่านี้ฉันไม่ได้รับการแจ้งเตือนสำหรับความคิดเห็นของคุณ ฉันเป็นห่วงที่สุดบล็อก (ซึ่งฉันเรียกว่าฟังก์ชั่น DLL) จะถูกเรียกก่อนที่จะสิ้นสุดขอบเขต (เพราะตัวแปรที่ไม่ได้ใช้) แต่มีตั้งแต่พบคำถามเกี่ยวกับดังนั้นที่ล้างความกังวลของฉัน ฉันจะเชื่อมโยงกับมัน แต่น่าเสียดายที่ฉันไม่สามารถหาได้อีกต่อไป
Nulano

1
ฟังก์ชั่นปิดการใช้งาน () เป็นหูดในการออกแบบที่ไม่เหมือนใครของคุณ หากคุณต้องการให้มีการเรียกในที่สุดในกรณีที่เกิดความล้มเหลวเท่านั้นทำไมไม่ใช้คำสั่ง catch นั่นไม่ได้มีไว้เพื่ออะไร
user2445507

32

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

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


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

30

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

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

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

อย่างไรก็ตาม ...

RAII ย้ายความรับผิดชอบของข้อยกเว้นด้านความปลอดภัยจากผู้ใช้ของวัตถุไปยังนักออกแบบ

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


13
แน่นอน ... RAII ดูดีจากมุมมองในอุดมคติ แต่ฉันต้องทำงานกับ C API แบบเดิมตลอดเวลา (เช่นฟังก์ชั่น C-style ใน Win32 API ... ) เป็นเรื่องปกติมากที่จะได้รับทรัพยากรที่ส่งคืน HANDLE บางประเภทซึ่งต้องใช้ฟังก์ชันบางอย่างเช่น CloseHandle (HANDLE) เพื่อล้างข้อมูล การใช้ลอง ... ในที่สุดก็เป็นวิธีที่ดีในการจัดการกับข้อยกเว้นที่เป็นไปได้ (โชคดีที่ดูเหมือนว่า shared_ptr กับนักออกแบบที่กำหนดเองและ lambdas C ++ 11 ควรมีการบรรเทาแบบอิง RAII ที่ไม่ต้องเขียนทั้งคลาสเพื่อห่อ API บางตัวที่ฉันใช้ในที่เดียวเท่านั้น)
James Johnston

7
@JamesJohnston เป็นเรื่องง่ายมากที่จะเขียนคลาส wrapper ที่มีการจัดการทุกรูปแบบและให้การทำงานของ RAII ATL ให้กลุ่มของพวกเขาเช่น ดูเหมือนว่าคุณคิดว่านี่เป็นปัญหามากเกินไป แต่ฉันไม่เห็นด้วยพวกเขามีขนาดเล็กมากและง่ายต่อการเขียน
Mark Ransom

5
ง่ายใช่เล็กไม่ ขนาดขึ้นอยู่กับความซับซ้อนของห้องสมุดที่คุณทำงานด้วย
Philip Couling

1
@ MarkRansom: มีกลไกใดบ้างที่ RAII สามารถทำสิ่งที่ชาญฉลาดได้หากมีข้อยกเว้นเกิดขึ้นระหว่างการล้างข้อมูลขณะที่มีข้อยกเว้นอื่นรอดำเนินการอยู่ ในระบบที่มีการลอง / ในที่สุดก็เป็นไปได้ - แต่ที่น่าอึดอัดใจ - CleanupFailedExceptionจะจัดให้มีสิ่งเพื่อให้ข้อยกเว้นที่ค้างอยู่และข้อยกเว้นที่เกิดขึ้นในระหว่างการทำความสะอาดทั้งสองได้รับการเก็บไว้ในที่ใหม่ มีวิธีการที่เป็นไปได้ที่จะบรรลุผลดังกล่าวโดยใช้ RAII หรือไม่?
supercat

3
@couling: มีหลายกรณีที่โปรแกรมจะเรียกใช้SomeObject.DoSomething()เมธอดและต้องการทราบว่า (1) สำเร็จหรือไม่ (2) ล้มเหลวโดยไม่มีผลข้างเคียง (3) ล้มเหลวด้วยผลข้างเคียงที่ผู้โทรเตรียมไว้เพื่อรับมือกับ หรือ (4) ล้มเหลวโดยมีผลข้างเคียงที่ผู้โทรไม่สามารถรับมือได้ มีเพียงผู้โทรเท่านั้นที่จะรู้ว่าสถานการณ์นั้นสามารถและไม่สามารถรับมือได้ สิ่งที่ผู้โทรต้องการคือวิธีการรู้ว่าสถานการณ์คืออะไร มันแย่มากที่ไม่มีกลไกมาตรฐานสำหรับการให้ข้อมูลที่สำคัญที่สุดเกี่ยวกับข้อยกเว้น
supercat

9

การจำลองแบบบล็อก "ในที่สุด" อีกอันโดยใช้ฟังก์ชันแลมบ์ดา C ++ 11

template <typename TCode, typename TFinallyCode>
inline void with_finally(const TCode &code, const TFinallyCode &finally_code)
{
    try
    {
        code();
    }
    catch (...)
    {
        try
        {
            finally_code();
        }
        catch (...) // Maybe stupid check that finally_code mustn't throw.
        {
            std::terminate();
        }
        throw;
    }
    finally_code();
}

หวังว่าคอมไพเลอร์จะปรับรหัสให้เหมาะสม

ตอนนี้เราสามารถเขียนโค้ดดังนี้:

with_finally(
    [&]()
    {
        try
        {
            // Doing some stuff that may throw an exception
        }
        catch (const exception1 &)
        {
            // Handling first class of exceptions
        }
        catch (const exception2 &)
        {
            // Handling another class of exceptions
        }
        // Some classes of exceptions can be still unhandled
    },
    [&]() // finally
    {
        // This code will be executed in all three cases:
        //   1) exception was not thrown at all
        //   2) exception was handled by one of the "catch" blocks above
        //   3) exception was not handled by any of the "catch" block above
    }
);

หากคุณต้องการคุณสามารถห่อสำนวนนี้ให้เป็นมาโคร "ลอง - ในที่สุด":

// Please never throw exception below. It is needed to avoid a compilation error
// in the case when we use "begin_try ... finally" without any "catch" block.
class never_thrown_exception {};

#define begin_try    with_finally([&](){ try
#define finally      catch(never_thrown_exception){throw;} },[&]()
#define end_try      ) // sorry for "pascalish" style :(

ตอนนี้ "ในที่สุด" บล็อกสามารถใช้ได้ใน C ++ 11:

begin_try
{
    // A code that may throw
}
catch (const some_exception &)
{
    // Handling some exceptions
}
finally
{
    // A code that is always executed
}
end_try; // Sorry again for this ugly thing

โดยส่วนตัวแล้วฉันไม่ชอบสำนวน "มาโคร" ของ "ในที่สุด" และต้องการใช้ฟังก์ชั่น pure "with_finally" แม้ว่าไวยากรณ์จะใหญ่ขึ้นในกรณีนั้น

คุณสามารถทดสอบรหัสด้านบนได้ที่นี่: http://coliru.stacked-crooked.com/a/1d88f64cb27b3813

PS

หากคุณต้องการบล็อกในที่สุดรหัสของคุณดังนั้นscoped ที่มีขอบเขตหรือON_FINALLY / ON_EXCEPTION macros น่าจะเหมาะกับความต้องการของคุณ

นี่คือตัวอย่างการใช้งานสั้น ๆ ON_FINALLY / ON_EXCEPTION:

void function(std::vector<const char*> &vector)
{
    int *arr1 = (int*)malloc(800*sizeof(int));
    if (!arr1) { throw "cannot malloc arr1"; }
    ON_FINALLY({ free(arr1); });

    int *arr2 = (int*)malloc(900*sizeof(int));
    if (!arr2) { throw "cannot malloc arr2"; }
    ON_FINALLY({ free(arr2); });

    vector.push_back("good");
    ON_EXCEPTION({ vector.pop_back(); });

    ...

1
ตัวแรกคือตัวเลือกที่อ่านได้มากที่สุดจากหน้านี้ทั้งหมด +1
Nikos

7

ขออภัยที่ขุดเธรดเก่า แต่มีข้อผิดพลาดที่สำคัญในการใช้เหตุผลต่อไปนี้:

RAII ย้ายความรับผิดชอบของข้อยกเว้นด้านความปลอดภัยจากผู้ใช้ของวัตถุไปยังนักออกแบบ (และผู้ดำเนินการ) ของวัตถุ ฉันจะโต้แย้งว่านี่เป็นสถานที่ที่ถูกต้องเพราะคุณจะต้องได้รับการยกเว้นด้านความปลอดภัยที่ถูกต้องเพียงครั้งเดียว (ในการออกแบบ / การนำไปใช้) ในการใช้งานครั้งสุดท้ายคุณจะต้องได้รับการยกเว้นด้านความปลอดภัยที่ถูกต้องทุกครั้งที่คุณใช้วัตถุ

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

void DoStuff(vector<string> input)
{
  list<Foo*> myList;

  try
  {    
    for (int i = 0; i < input.size(); ++i)
    {
      Foo* tmp = new Foo(input[i]);
      if (!tmp)
        throw;

      myList.push_back(tmp);
    }

    DoSomeStuff(myList);
  }
  finally
  {
    while (!myList.empty())
    {
      delete myList.back();
      myList.pop_back();
    }
  }
}

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

คุณต้องไปเส้นทางที่น่าเกลียดแทน:

void DoStuff(vector<string> input)
{
  list<Foo*> myList;

  try
  {    
    for (int i = 0; i < input.size(); ++i)
    {
      Foo* tmp = new Foo(input[i]);
      if (!tmp)
        throw;

      myList.push_back(tmp);
    }

    DoSomeStuff(myList);
  }
  catch(...)
  {
  }

  while (!myList.empty())
  {
    delete myList.back();
    myList.pop_back();
  }
}

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

คำแนะนำ: คุณสามารถทำอะไรกับ "ในที่สุด" มากกว่าแค่การจัดสรรคืนหน่วยความจำ


17
ภาษาที่ได้รับการจัดการต้องการบล็อกในที่สุดเนื่องจากมีการจัดการทรัพยากรประเภทเดียวเท่านั้น: หน่วยความจำ RAII หมายความว่าทรัพยากรทั้งหมดสามารถจัดการได้ในลักษณะเดียวกันดังนั้นจึงไม่จำเป็นต้องใช้ในที่สุด หากคุณใช้จริง ๆ RAII ในตัวอย่างของคุณ (โดยใช้ตัวชี้สมาร์ทในรายการของคุณแทนที่จะเป็นคนเปลือยกาย) รหัสจะง่ายกว่าตัวอย่าง "ในที่สุด" ของคุณ และง่ายยิ่งขึ้นถ้าคุณไม่ตรวจสอบค่าส่งคืนของใหม่ - ตรวจสอบว่าไม่มีจุดหมายสวยมาก
Myto

7
newไม่ส่งคืน NULL แต่จะมีข้อผิดพลาดแทน
Hasturkun

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

4
C ++ 11 ปรับปรุงสิ่งนี้และรวมถึงstd::shared_ptrและstd::unique_ptrโดยตรงใน stdlib
u0b34a0f6ae

16
เหตุผลที่ตัวอย่างของคุณดูน่าเกลียดน่ากลัวไม่ใช่เพราะ RAII มีข้อบกพร่อง แต่เป็นเพราะคุณไม่ได้ใช้งาน พอยน์เตอร์ดิบไม่ใช่ RAII
Ben Voigt

6

FWIW, Microsoft Visual C ++ ไม่สนับสนุนการลองและในที่สุดก็มีการใช้ในอดีตในแอพ MFC เป็นวิธีการจับข้อยกเว้นร้ายแรงที่อาจทำให้เกิดความผิดพลาด ตัวอย่างเช่น;

int CMyApp::Run() 
{
    __try
    {
        int i = CWinApp::Run();
        m_Exitok = MAGIC_EXIT_NO;
        return i;
    }
    __finally
    {
        if (m_Exitok != MAGIC_EXIT_NO)
            FaultHandler();
    }
}

ฉันเคยใช้สิ่งนี้ในอดีตเพื่อทำสิ่งต่าง ๆ เช่นบันทึกการสำรองข้อมูลของไฟล์ที่เปิดอยู่ก่อนที่จะออก การตั้งค่าการดีบัก JIT บางอย่างจะทำให้กลไกนี้พัง


4
จำไว้ว่าไม่ใช่ข้อยกเว้น C ++ จริงๆ แต่เป็น SEH คุณสามารถใช้ทั้งในรหัส MS C ++ SEH เป็นตัวจัดการข้อยกเว้น OS ซึ่งเป็นวิธี VB, .NET ใช้ข้อยกเว้น
gbjbaanb

และคุณสามารถใช้ SetUnhandledExceptionHandler เพื่อสร้างตัวจัดการข้อยกเว้น 'ทั่วโลก' ที่ไม่สามารถตรวจจับได้ - สำหรับข้อยกเว้น SEH
gbjbaanb

3
SEH เป็นสิ่งที่น่ากลัวและยังป้องกันไม่ให้ C + + destructors ถูกเรียก
paulm

6

ตามที่ระบุไว้ในคำตอบอื่น ๆ C ++ สามารถรองรับfinallyฟังก์ชั่นที่คล้ายกันได้ การใช้งานฟังก์ชั่นนี้ที่ใกล้เคียงที่สุดกับการเป็นส่วนหนึ่งของภาษามาตรฐานนั้นเป็นหนึ่งในแนวทางหลักของC ++ชุดแนวทางปฏิบัติที่ดีที่สุดสำหรับการใช้ C ++ ที่แก้ไขโดย Bjarne Stoustrup และ Herb Sutter การดำเนินการของfinallyเป็นส่วนหนึ่งของห้องสมุดสนับสนุนแนวทาง (GSL) ตลอดทั้งแนวทางแนะนำให้ใช้finallyเมื่อจัดการกับอินเทอร์เฟซแบบเก่าและยังมีแนวทางของตัวเองบรรดาศักดิ์ใช้วัตถุ final_action เพื่อล้างข้อมูลด่วนหากไม่มีการจัดการทรัพยากรที่เหมาะสมที่จะแสดงการทำความสะอาดหากไม่มีการจัดการทรัพยากรที่เหมาะสมสามารถใช้ได้

ดังนั้น C ++ ไม่เพียงรองรับ finallyแนะนำให้ใช้ในกรณีใช้งานทั่วไปจำนวนมาก

ตัวอย่างการใช้การนำ GSL มาใช้จะมีลักษณะดังนี้:

#include <gsl/gsl_util.h>

void example()
{
    int handle = get_some_resource();
    auto handle_clean = gsl::finally([&handle] { clean_that_resource(handle); });

    // Do a lot of stuff, return early and throw exceptions.
    // clean_that_resource will always get called.
}

การใช้และการใช้ GSL นั้นคล้ายคลึงกับคำตอบของ Paolo.Bolzoniมาก ข้อแตกต่างประการหนึ่งคือวัตถุที่สร้างขึ้นโดยgsl::finally()ขาดdisable()สาย หากคุณต้องการฟังก์ชั่นการใช้งาน (เช่นเพื่อส่งคืนทรัพยากรเมื่อมีการประกอบและไม่มีข้อยกเว้นเกิดขึ้น) คุณอาจต้องการใช้งานของเปาโล มิฉะนั้นการใช้ GSL นั้นใกล้เคียงกับการใช้คุณสมบัติมาตรฐานที่คุณจะได้รับ


3

ไม่ใช่จริง ๆ แต่คุณสามารถเลียนแบบพวกมันเพื่อขยายตัวอย่างเช่น:

int * array = new int[10000000];
try {
  // Some code that can throw exceptions
  // ...
  throw std::exception();
  // ...
} catch (...) {
  // The finally-block (if an exception is thrown)
  delete[] array;
  // re-throw the exception.
  throw; 
}
// The finally-block (if no exception was thrown)
delete[] array;

โปรดทราบว่าในที่สุดบล็อกอาจโยนข้อยกเว้นก่อนที่ข้อยกเว้นเดิมจะถูกโยนอีกครั้งดังนั้นการยกเลิกข้อยกเว้นเดิม นี่เป็นพฤติกรรมแบบเดียวกันกับในที่สุด Java-block นอกจากนี้คุณไม่สามารถใช้returnภายในบล็อคลองและจับได้


3
ฉันดีใจที่คุณพูดถึงการบล็อกในที่สุดอาจโยน; มันเป็นสิ่งที่คำตอบ "ใช้ RAII" ส่วนใหญ่ดูเหมือนจะไม่สนใจ เพื่อหลีกเลี่ยงการต้องเขียนบล็อกในที่สุดสองครั้งคุณสามารถทำสิ่งที่ต้องการstd::exception_ptr e; try { /*try block*/ } catch (...) { e = std::current_exception(); } /*finally block*/ if (e) std::rethrow_exception(e);
sethobrien

1
นั่นคือทั้งหมดที่ฉันอยากรู้! ทำไมไม่มีคำตอบอื่นอธิบายว่าการโยน (... ) + การโยนที่ว่างเปล่า; ทำงานเหมือนบล็อกในที่สุดหรือไม่ บางครั้งคุณก็ต้องการมัน
VinGarcia

โซลูชันที่ฉันให้ไว้ในคำตอบของฉัน ( stackoverflow.com/a/38701485/566849 ) ควรอนุญาตให้มีการโยนข้อยกเว้นจากภายในfinallyบล็อก
Fabio A.

3

ฉันมากับfinallyแมโครที่สามารถนำมาใช้เกือบจะเหมือน ¹ finallyคำหลักในชวา; มันใช้ประโยชน์จากstd::exception_ptrและเพื่อน ๆ แลมบ์ดาฟังก์ชั่นและstd::promiseดังนั้นจึงต้องใช้C++11หรือสูงกว่า; มันยังใช้ประโยชน์จากการขยายคำสั่งผสมการแสดงออก GCC ซึ่งได้รับการสนับสนุนโดยเสียงดังกราว

คำเตือน : คำตอบรุ่นก่อนหน้านี้ใช้การใช้แนวคิดที่แตกต่างพร้อมข้อ จำกัด มากมาย

อันดับแรกให้นิยามคลาสผู้ช่วย

#include <future>

template <typename Fun>
class FinallyHelper {
    template <typename T> struct TypeWrapper {};
    using Return = typename std::result_of<Fun()>::type;

public:    
    FinallyHelper(Fun body) {
        try {
            execute(TypeWrapper<Return>(), body);
        }
        catch(...) {
            m_promise.set_exception(std::current_exception());
        }
    }

    Return get() {
        return m_promise.get_future().get();
    }

private:
    template <typename T>
    void execute(T, Fun body) {
        m_promise.set_value(body());
    }

    void execute(TypeWrapper<void>, Fun body) {
        body();
    }

    std::promise<Return> m_promise;
};

template <typename Fun>
FinallyHelper<Fun> make_finally_helper(Fun body) {
    return FinallyHelper<Fun>(body);
}

แล้วก็มีมาโครตัวจริง

#define try_with_finally for(auto __finally_helper = make_finally_helper([&] { try 
#define finally });                         \
        true;                               \
        ({return __finally_helper.get();})) \
/***/

มันสามารถใช้เช่นนี้:

void test() {
    try_with_finally {
        raise_exception();
    }    

    catch(const my_exception1&) {
        /*...*/
    }

    catch(const my_exception2&) {
        /*...*/
    }

    finally {
        clean_it_all_up();
    }    
}

การใช้std::promiseทำให้มันง่ายมากที่จะใช้ แต่ก็อาจยังแนะนำไม่น้อยของค่าใช้จ่ายที่ไม่จำเป็นซึ่งอาจจะหลีกเลี่ยงได้โดย reimplementing std::promiseเฉพาะฟังก์ชันการทำงานที่จำเป็นจาก


¹ CAVEAT:มีบางสิ่งที่ไม่ได้ทำงานค่อนข้างชอบรุ่น JAVA finallyของ ปิดส่วนหัวของฉัน:

  1. มันเป็นไปไม่ได้ที่จะแยกจากวงรอบนอกด้วยbreakคำสั่งจากภายในtryและcatch()บล็อกเนื่องจากมันอยู่ภายในฟังก์ชันแลมบ์ดา
  2. ต้องมีอย่างน้อยหนึ่งcatch()บล็อกหลังจากtry: มันเป็นข้อกำหนดของซีพลัสพลัส
  3. ถ้าฟังก์ชันมีค่าตอบแทนอื่นที่ไม่ใช่เป็นโมฆะ แต่มีผลตอบแทนภายในไม่มีtryและcatch()'sบล็อกรวบรวมจะล้มเหลวเพราะมาโครจะขยายไปยังรหัสที่จะต้องการที่จะกลับมาเป็นfinally voidนี่อาจเป็นโมฆะเป็นโมฆะ ed โดยมีfinally_noreturnมาโครแปลก ๆ

โดยรวมแล้วฉันไม่รู้ว่าฉันจะใช้สิ่งนี้ด้วยตัวเองหรือเปล่า แต่มันสนุกที่ได้เล่นกับมัน :)


ใช่มันเป็นแค่การแฮ็คที่รวดเร็ว แต่ถ้าโปรแกรมเมอร์รู้ว่าพวกเขากำลังทำอะไรอยู่มันอาจจะมีประโยชน์
Fabio A.

@ MarkLakata ฉันอัปเดตโพสต์ด้วยการใช้งานที่ดีขึ้นซึ่งรองรับการโยนข้อยกเว้นและส่งคืน
Fabio A.

ดูดี. คุณสามารถกำจัด Caveat 2 ได้ด้วยการใส่catch(xxx) {}บล็อกที่เป็นไปไม่ได้ที่จุดเริ่มต้นของfinallyแมโครโดยที่ xxx เป็นประเภทปลอมเพียงเพื่อจุดประสงค์ในการมี catch catch อย่างน้อยหนึ่งบล็อก
Mark Lakata

@ MarkLakata ฉันก็คิดเช่นนั้นเหมือนกัน แต่นั่นจะทำให้มันใช้catch(...)ไม่ได้ใช่ไหม?
Fabio A.

ฉันไม่คิดอย่างนั้น เพียงสร้างประเภทที่คลุมเครือxxxในเนมสเปซส่วนตัวที่จะไม่ใช้
Mark Lakata

2

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

หาก C ++ รองรับคุณต้องการให้โค้ดของคุณมีลักษณะดังนี้:

    extern Queue downstream, upstream;

    int Example()
    {
        try
        {
           while(!ExitRequested())
           {
             X* x = upstream.pop();
             if (!x) break;
             x->doSomething();
             downstream.push(x);
           } 
        }
        finally { 
            downstream.push(nullptr);
        }
    }

ฉันคิดว่ามันเป็นตรรกะมากกว่าที่จะประกาศในที่สุดเมื่อเริ่มต้นของลูปเนื่องจากมันเกิดขึ้นหลังจากลูปออกจาก ... แต่นั่นเป็นการคิดที่ปรารถนาเพราะเราไม่สามารถทำได้ใน C ++ โปรดทราบว่าคิวdownstreamนั้นเชื่อมต่อกับเธรดอื่นดังนั้นคุณจึงไม่สามารถใส่ Sentinel push(nullptr)ใน destructor ของdownstreamเพราะมันไม่สามารถถูกทำลายได้ ณ จุดนี้ ... มันต้องมีชีวิตอยู่จนกว่าเธรดอื่นจะได้รับnullptrที่ได้รับ

ดังนั้นนี่คือวิธีการใช้คลาส RAII กับแลมบ์ดาเพื่อทำสิ่งเดียวกัน:

    class Finally
    {
    public:

        Finally(std::function<void(void)> callback) : callback_(callback)
        {
        }
        ~Finally()
        {
            callback_();
        }
        std::function<void(void)> callback_;
    };

และนี่คือวิธีที่คุณใช้:

    extern Queue downstream, upstream;

    int Example()
    {
        Finally atEnd([](){ 
           downstream.push(nullptr);
        });
        while(!ExitRequested())
        {
           X* x = upstream.pop();
           if (!x) break;
           x->doSomething();
           downstream.push(x);
        }
    }

สวัสดีฉันเชื่อว่าคำตอบของฉันด้านบน ( stackoverflow.com/a/38701485/566849 ) ตอบสนองความต้องการของคุณได้อย่างสมบูรณ์
Fabio A.

1

ดังที่หลาย ๆ คนได้กล่าวไว้วิธีการแก้ปัญหาคือใช้คุณสมบัติ C ++ 11 เพื่อหลีกเลี่ยงการบล็อกในที่สุด unique_ptrหนึ่งในคุณสมบัติที่เป็น

นี่คือคำตอบของ Mephane ที่เขียนโดยใช้รูปแบบ RAII

#include <vector>
#include <memory>
#include <list>
using namespace std;

class Foo
{
 ...
};

void DoStuff(vector<string> input)
{
    list<unique_ptr<Foo> > myList;

    for (int i = 0; i < input.size(); ++i)
    {
      myList.push_back(unique_ptr<Foo>(new Foo(input[i])));
    }

    DoSomeStuff(myList);
}

แนะนำเพิ่มเติมเกี่ยวกับการใช้ unique_ptr กับคอนเทนเนอร์ C ++ Standard Library ได้ที่นี่


0

ฉันต้องการให้ทางเลือกอื่น

หากคุณต้องการให้บล็อกถูกเรียกในที่สุดให้วางบล็อกนี้หลังจากบล็อก catch สุดท้าย (ซึ่งอาจเป็นcatch( ... )ข้อยกเว้นที่ไม่รู้จัก)

try{
   // something that might throw exception
} catch( ... ){
   // what to do with uknown exception
}

//final code to be called always,
//don't forget that it might throw some exception too
doSomeCleanUp(); 

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

bool generalAppState = false;
try{
   // something that might throw exception

   //the very end of try block:
   generalAppState = true;
} catch( ... ){
   // what to do with uknown exception
}

//final code to be called only when exception was thrown,
//don't forget that it might throw some exception too
if( !generalAppState ){
   doSomeCleanUpOfDirtyEnd();
}

//final code to be called only when no exception is thrown
//don't forget that it might throw some exception too
else{
   cleanEnd();
}

สิ่งนี้ใช้ไม่ได้เนื่องจากจุดรวมทั้งหมดของบล็อกในที่สุดคือทำการล้างข้อมูลแม้ว่ารหัสควรอนุญาตให้มีข้อยกเว้นเพื่อออกจากบล็อกรหัส ลองพิจารณา: `ลอง {// อาจโยน" B "} catch (A & a) {} ได้ในที่สุด {// หาก C ++ มี ... // สิ่งที่ต้องเกิดขึ้นแม้ว่า" B "จะถูกโยนก็ตาม } // จะไม่ดำเนินการหากมีการ "โยน" `IMHO จุดของข้อยกเว้นคือการลดรหัสการจัดการข้อผิดพลาดดังนั้นการจับบล็อกทุกครั้งที่มีการขว้างเกิดขึ้น นี่คือเหตุผลที่ RAII ให้ความช่วยเหลือ: หากนำไปใช้อย่างอิสระข้อยกเว้นมีความสำคัญมากที่สุดที่ชั้นบนและชั้นล่าง
ทางการ

1
@ เบา ๆ อย่างเห็นได้ชัดความคิดเห็นของคุณไม่ศักดิ์สิทธิ์ฉันได้รับจุด แต่ใน C ++ ไม่ใช่สิ่งนั้นดังนั้นคุณต้องพิจารณาสิ่งนี้เป็นชั้นบนสุดที่เลียนแบบพฤติกรรมนี้
jave.web

DOWNVOTE = โปรดแสดงความคิดเห็น :)
jave.web

0

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

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

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

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


-2
try
{
  ...
  goto finally;
}
catch(...)
{
  ...
  goto finally;
}
finally:
{
  ...
}

35
สำนวนน่ารัก แต่มันก็ไม่เหมือนกัน กลับมาในบล็อคลองหรือจับจะไม่ผ่านรหัส 'ในที่สุด:' ของคุณ
Edward KMETT

10
มันคุ้มค่าที่จะรักษาคำตอบที่ผิด (ด้วยคะแนน 0) เนื่องจาก Edward Kmett สร้างความแตกต่างที่สำคัญมาก
ทำเครื่องหมาย Lakata

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