C ++ รองรับการบล็อก ' ที่สุด ' หรือไม่?
อะไรคือ สำนวน RAII ?
อะไรคือความแตกต่างระหว่างคำสั่ง RAII ของ C ++ กับการใช้คำสั่ง 'C' ?
C ++ รองรับการบล็อก ' ที่สุด ' หรือไม่?
อะไรคือ สำนวน RAII ?
อะไรคือความแตกต่างระหว่างคำสั่ง RAII ของ C ++ กับการใช้คำสั่ง 'C' ?
คำตอบ:
ไม่ 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 สำนวน
ใน C ++ ในที่สุดไม่จำเป็นต้องใช้เพราะ RAII
RAII ย้ายความรับผิดชอบของข้อยกเว้นด้านความปลอดภัยจากผู้ใช้ของวัตถุไปยังนักออกแบบ (และผู้ดำเนินการ) ของวัตถุ ฉันจะโต้แย้งว่านี่เป็นสถานที่ที่ถูกต้องเพราะคุณจะต้องได้รับการยกเว้นด้านความปลอดภัยที่ถูกต้องเพียงครั้งเดียว (ในการออกแบบ / การนำไปใช้) ในการใช้งานครั้งสุดท้ายคุณจะต้องได้รับการยกเว้นด้านความปลอดภัยที่ถูกต้องทุกครั้งที่คุณใช้วัตถุ
นอกจากนี้ IMO โค้ดยังมีลักษณะที่ดูดีขึ้น
ตัวอย่าง:
วัตถุฐานข้อมูล เพื่อให้แน่ใจว่ามีการใช้การเชื่อมต่อฐานข้อมูลจะต้องเปิดและปิด โดยใช้ RAII สิ่งนี้สามารถทำได้ในตัวสร้าง / destructor
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 ไม่ว่าเราจะลองและใช้ในทางที่ผิด
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
// Make sure not to throw exception if one is already propagating.
มันเป็นสิ่งสำคัญสำหรับ C + + destructors ที่จะไม่โยนข้อยกเว้นด้วยเหตุผลนี้
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"; }
FinalAction
โดยทั่วไปแล้วจะเหมือนกับScopeGuard
สำนวนยอดนิยมเท่านั้นที่มีชื่อแตกต่างกัน
นอกเหนือจากการทำความสะอาดได้ง่ายด้วยวัตถุที่อิงกับกองซ้อน RAII ยังมีประโยชน์เพราะการล้างข้อมูลแบบ 'อัตโนมัติ' แบบเดียวกันนั้นเกิดขึ้นเมื่อวัตถุนั้นเป็นสมาชิกของคลาสอื่น เมื่อคลาสที่เป็นเจ้าของถูกทำลายทรัพยากรที่จัดการโดยคลาส RAII จะถูกล้างข้อมูลเนื่องจาก dtor สำหรับคลาสนั้นถูกเรียกใช้เป็นผลลัพธ์
ซึ่งหมายความว่าเมื่อคุณไปถึง RAII เนอร์วาน่าและสมาชิกทุกคนในชั้นเรียนใช้ RAII (เช่นตัวชี้อัจฉริยะ) คุณสามารถออกไปด้วย dtor ที่ง่ายมาก (อาจเป็นค่าเริ่มต้น) สำหรับเจ้าของคลาสเนื่องจากไม่จำเป็นต้องจัดการด้วยตนเอง อายุการใช้งานทรัพยากรของสมาชิก
ทำไมถึงแม้ว่าภาษาที่ได้รับการจัดการให้บล็อกในที่สุดแม้จะมีทรัพยากรที่ถูกจัดสรรโดยอัตโนมัติโดยตัวเก็บขยะหรือไม่
ที่จริงแล้วภาษาที่ใช้นักสะสมขยะต้องการ "ที่สุด" มากกว่านี้ ตัวรวบรวมขยะไม่ทำลายวัตถุของคุณในเวลาที่เหมาะสมดังนั้นจึงไม่สามารถเชื่อถือได้ในการล้างปัญหาที่ไม่เกี่ยวข้องกับหน่วยความจำอย่างถูกต้อง
ในแง่ของข้อมูลที่จัดสรรแบบไดนามิกหลายคนแย้งว่าคุณควรใช้สมาร์ทพอยน์เตอร์
อย่างไรก็ตาม ...
RAII ย้ายความรับผิดชอบของข้อยกเว้นด้านความปลอดภัยจากผู้ใช้ของวัตถุไปยังนักออกแบบ
น่าเศร้านี่คือความพินาศของตนเอง นิสัยการเขียนโปรแกรม C เก่าตายยาก เมื่อคุณใช้ไลบรารีที่เขียนด้วยภาษา C หรือสไตล์ C มาก RAII จะไม่ถูกใช้งาน สรุปการเขียนทั้ง API ส่วนหน้านั่นคือสิ่งที่คุณต้องทำงานกับ จากนั้นการขาด "ในที่สุด" ก็กัดจริงๆ
CleanupFailedException
จะจัดให้มีสิ่งเพื่อให้ข้อยกเว้นที่ค้างอยู่และข้อยกเว้นที่เกิดขึ้นในระหว่างการทำความสะอาดทั้งสองได้รับการเก็บไว้ในที่ใหม่ มีวิธีการที่เป็นไปได้ที่จะบรรลุผลดังกล่าวโดยใช้ RAII หรือไม่?
SomeObject.DoSomething()
เมธอดและต้องการทราบว่า (1) สำเร็จหรือไม่ (2) ล้มเหลวโดยไม่มีผลข้างเคียง (3) ล้มเหลวด้วยผลข้างเคียงที่ผู้โทรเตรียมไว้เพื่อรับมือกับ หรือ (4) ล้มเหลวโดยมีผลข้างเคียงที่ผู้โทรไม่สามารถรับมือได้ มีเพียงผู้โทรเท่านั้นที่จะรู้ว่าสถานการณ์นั้นสามารถและไม่สามารถรับมือได้ สิ่งที่ผู้โทรต้องการคือวิธีการรู้ว่าสถานการณ์คืออะไร มันแย่มากที่ไม่มีกลไกมาตรฐานสำหรับการให้ข้อมูลที่สำคัญที่สุดเกี่ยวกับข้อยกเว้น
การจำลองแบบบล็อก "ในที่สุด" อีกอันโดยใช้ฟังก์ชันแลมบ์ดา 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(); });
...
ขออภัยที่ขุดเธรดเก่า แต่มีข้อผิดพลาดที่สำคัญในการใช้เหตุผลต่อไปนี้:
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 ที่ได้รับการจัดการให้บล็อกสุดท้ายแม้จะมีการยกเลิกการจัดสรรทรัพยากรโดยอัตโนมัติโดยตัวรวบรวมขยะหรือไม่
คำแนะนำ: คุณสามารถทำอะไรกับ "ในที่สุด" มากกว่าแค่การจัดสรรคืนหน่วยความจำ
new
ไม่ส่งคืน NULL แต่จะมีข้อผิดพลาดแทน
std::shared_ptr
และstd::unique_ptr
โดยตรงใน stdlib
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 บางอย่างจะทำให้กลไกนี้พัง
ตามที่ระบุไว้ในคำตอบอื่น ๆ 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 นั้นใกล้เคียงกับการใช้คุณสมบัติมาตรฐานที่คุณจะได้รับ
ไม่ใช่จริง ๆ แต่คุณสามารถเลียนแบบพวกมันเพื่อขยายตัวอย่างเช่น:
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
ภายในบล็อคลองและจับได้
std::exception_ptr e; try { /*try block*/ } catch (...) { e = std::current_exception(); } /*finally block*/ if (e) std::rethrow_exception(e);
finally
บล็อก
ฉันมากับ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
ของ ปิดส่วนหัวของฉัน:
break
คำสั่งจากภายในtry
และcatch()
บล็อกเนื่องจากมันอยู่ภายในฟังก์ชันแลมบ์ดาcatch()
บล็อกหลังจากtry
: มันเป็นข้อกำหนดของซีพลัสพลัสtry
และcatch()'s
บล็อกรวบรวมจะล้มเหลวเพราะมาโครจะขยายไปยังรหัสที่จะต้องการที่จะกลับมาเป็นfinally
void
นี่อาจเป็นโมฆะเป็นโมฆะ ed โดยมีfinally_noreturn
มาโครแปลก ๆโดยรวมแล้วฉันไม่รู้ว่าฉันจะใช้สิ่งนี้ด้วยตัวเองหรือเปล่า แต่มันสนุกที่ได้เล่นกับมัน :)
catch(xxx) {}
บล็อกที่เป็นไปไม่ได้ที่จุดเริ่มต้นของfinally
แมโครโดยที่ xxx เป็นประเภทปลอมเพียงเพื่อจุดประสงค์ในการมี catch catch อย่างน้อยหนึ่งบล็อก
catch(...)
ไม่ได้ใช่ไหม?
xxx
ในเนมสเปซส่วนตัวที่จะไม่ใช้
ฉันมีกรณีการใช้งานที่ฉันคิดว่า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);
}
}
ดังที่หลาย ๆ คนได้กล่าวไว้วิธีการแก้ปัญหาคือใช้คุณสมบัติ 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 ได้ที่นี่
ฉันต้องการให้ทางเลือกอื่น
หากคุณต้องการให้บล็อกถูกเรียกในที่สุดให้วางบล็อกนี้หลังจากบล็อก 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();
}
ฉันยังคิดว่า RIIA ไม่ใช่สิ่งทดแทนที่มีประโยชน์อย่างเต็มที่สำหรับการจัดการข้อยกเว้นและการมีในที่สุด BTW ฉันยังคิดว่า RIIA เป็นชื่อที่ไม่ดีรอบตัว ฉันเรียกพวกภารโรงประเภทนี้และใช้พวกมันมากมาย 95% ของเวลาที่พวกเขาไม่ได้เริ่มต้นหรือรับทรัพยากรพวกเขาใช้การเปลี่ยนแปลงบางอย่างในขอบเขตที่กำหนดหรือการตั้งค่าบางสิ่งแล้วและทำให้แน่ใจว่ามันถูกทำลาย นี่เป็นรูปแบบชื่อทางการที่หมกมุ่นอยู่กับอินเทอร์เน็ตที่ฉันถูกทำร้ายแม้กระทั่งบอกชื่อของฉันก็อาจจะดีกว่า
ฉันไม่คิดว่ามันสมเหตุสมผลที่จะกำหนดให้ทุกรายการที่ซับซ้อนของรายการเฉพาะกิจต้องมีคลาสที่เขียนขึ้นเพื่อเก็บไว้เพื่อหลีกเลี่ยงภาวะแทรกซ้อนเมื่อทำความสะอาดทุกอย่างกลับมาในหน้าที่ต้องจับหลายรายการ ประเภทข้อยกเว้นหากมีสิ่งผิดปกติในกระบวนการ สิ่งนี้จะนำไปสู่คลาสเฉพาะกิจที่ไม่จำเป็นอีกต่อไป
ใช่มันดีสำหรับคลาสที่ออกแบบมาเพื่อจัดการทรัพยากรเฉพาะหรือคลาสทั่วไปที่ออกแบบมาเพื่อจัดการชุดของทรัพยากรที่คล้ายกัน แต่ถึงแม้ว่าทุกสิ่งที่เกี่ยวข้องจะมีสิ่งห่อหุ้มดังกล่าว แต่การประสานงานของการล้างข้อมูลอาจไม่ใช่เพียงแค่การเรียกร้องแบบย้อนกลับของผู้ทำลายล้าง
ฉันคิดว่ามันสมเหตุสมผลดีสำหรับ C ++ ที่จะมีในที่สุด ฉันหมายถึง jeez บิตและบ็อบจำนวนมากติดอยู่กับมันในช่วงหลายสิบปีที่ผ่านมาซึ่งดูเหมือนว่าคนแปลกหน้าจู่ ๆ ก็จะกลายเป็นคนหัวโบราณมากกว่าบางสิ่งบางอย่างในที่สุดซึ่งอาจเป็นประโยชน์และอาจไม่มีอะไรใกล้เคียง เพิ่ม (แม้ว่าจะเป็นเพียงการคาดเดาในส่วนของฉัน)
try
{
...
goto finally;
}
catch(...)
{
...
goto finally;
}
finally:
{
...
}
finally
ไม่ได้ทำ