Có những câu trả lời tuyệt vời ở đó, vì vậy tôi chỉ thêm một số điều đã quên.
0. RAII là về phạm vi
RAII là về cả hai:
- có được một tài nguyên (bất kể là tài nguyên nào) trong hàm tạo và hủy lấy nó trong hàm hủy.
- có hàm tạo được thực thi khi biến được khai báo và hàm hủy tự động thực thi khi biến vượt ra khỏi phạm vi.
Những người khác đã trả lời về điều đó, vì vậy tôi sẽ không nói chi tiết.
1. Khi viết mã bằng Java hoặc C #, bạn đã sử dụng RAII ...
MONSIEUR JOURDAIN: Cái gì! Khi tôi nói, "Nicole, mang dép cho tôi và đưa cho tôi mũ đi ngủ", đó là văn xuôi?
BÁC SĨ TRIẾT HỌC: Vâng, thưa ông.
MONSIEUR JOURDAIN: Trong hơn bốn mươi năm, tôi đã nói văn xuôi mà không biết gì về nó, và tôi rất biết ơn các bạn vì đã dạy tôi điều đó.
- Molière: Quý ông trung lưu, Màn 2, Cảnh 4
Như Monsieur Jourdain đã làm với văn xuôi, C # và thậm chí cả người Java đã sử dụng RAII, nhưng theo những cách ẩn. Ví dụ: mã Java sau (được viết theo cùng một cách trong C # bằng cách thay thế synchronized
bằng lock
):
void foo()
{
// etc.
synchronized(someObject)
{
// if something throws here, the lock on someObject will
// be unlocked
}
// etc.
}
... đã sử dụng RAII: Việc mua lại mutex được thực hiện trong từ khóa ( synchronized
hoặc lock
) và việc hủy chuyển đổi sẽ được thực hiện khi thoát khỏi phạm vi.
Nó rất tự nhiên trong ký hiệu của nó, nó gần như không cần giải thích ngay cả đối với những người chưa bao giờ nghe nói về RAII.
Lợi thế của C ++ so với Java và C # ở đây là mọi thứ đều có thể được thực hiện bằng RAII. Ví dụ, không có trực tiếp xây dựng-in tương đương synchronized
cũng không lock
trong C ++, nhưng chúng ta vẫn có thể có họ.
Trong C ++, nó sẽ được viết:
void foo()
{
// etc.
{
Lock lock(someObject) ; // lock is an object of type Lock whose
// constructor acquires a mutex on
// someObject and whose destructor will
// un-acquire it
// if something throws here, the lock on someObject will
// be unlocked
}
// etc.
}
có thể dễ dàng viết theo cách Java / C # (sử dụng macro C ++):
void foo()
{
// etc.
LOCK(someObject)
{
// if something throws here, the lock on someObject will
// be unlocked
}
// etc.
}
2. RAII có cách sử dụng thay thế
TRẮNG RABBIT: [hát] Tôi đến muộn / Tôi đến muộn / Cho một ngày rất quan trọng. / Không có thời gian để nói "Xin chào." / Tạm biệt. / Tôi đến muộn, tôi đến muộn, tôi đến muộn.
- Alice ở xứ sở thần tiên (phiên bản Disney, 1951)
Bạn biết khi nào hàm tạo sẽ được gọi (khi khai báo đối tượng), và bạn biết khi nào hàm hủy tương ứng của nó sẽ được gọi (khi thoát khỏi phạm vi), vì vậy bạn có thể viết mã gần như kỳ diệu chỉ với một dòng. Chào mừng bạn đến với thế giới thần tiên của C ++ (ít nhất là theo quan điểm của nhà phát triển C ++).
Ví dụ: bạn có thể viết một đối tượng counter (tôi để đó như một bài tập) và sử dụng nó chỉ bằng cách khai báo biến của nó, giống như đối tượng lock ở trên đã được sử dụng:
void foo()
{
double timeElapsed = 0 ;
{
Counter counter(timeElapsed) ;
// do something lengthy
}
// now, the timeElapsed variable contain the time elapsed
// from the Counter's declaration till the scope exit
}
tất nhiên, có thể được viết lại theo cách Java / C # bằng cách sử dụng macro:
void foo()
{
double timeElapsed = 0 ;
COUNTER(timeElapsed)
{
// do something lengthy
}
// now, the timeElapsed variable contain the time elapsed
// from the Counter's declaration till the scope exit
}
3. Tại sao thiếu C ++ finally
?
[SHOUTING] Đây là lần đếm ngược cuối cùng !
- Châu Âu: Đếm ngược cuối cùng (xin lỗi, tôi đã hết trích dẫn, ở đây ... :-)
Các finally
khoản được sử dụng trong C # / Java để xử lý thanh lý tài nguyên trong trường hợp xuất cảnh phạm vi (hoặc thông qua một return
hoặc một ngoại lệ ném).
Người đọc đặc tả tinh tế sẽ nhận thấy C ++ không có mệnh đề cuối cùng. Và đây không phải là một lỗi, vì C ++ không cần nó, vì RAII đã xử lý việc loại bỏ tài nguyên. (Và tin tôi đi, viết một hàm hủy trong C ++ dễ dàng hơn rất nhiều so với viết mệnh đề cuối cùng của Java đúng, hoặc thậm chí là một phương thức Dispose đúng của C #).
Tuy nhiên, đôi khi, một finally
điều khoản sẽ rất hay. Chúng ta có thể làm điều đó trong C ++ không? Có, chúng tôi có thể! Và một lần nữa với việc sử dụng RAII thay thế.
Kết luận: RAII không chỉ là triết lý trong C ++: Đó là C ++
RAII? ĐÂY LÀ C ++ !!!
- Nhận xét phẫn nộ của nhà phát triển C ++, được sao chép một cách đáng xấu hổ bởi một vị vua Sparta khó hiểu và 300 người bạn của ông ta
Khi bạn đạt đến một số kinh nghiệm trong C ++, bạn bắt đầu suy nghĩ về RAII , về trình tạo và trình hủy thực thi tự động .
Bạn bắt đầu suy nghĩ về phạm vi , {
và }
ký tự và trở thành những ký tự quan trọng nhất trong mã của bạn.
Và hầu hết mọi thứ đều phù hợp với RAII: an toàn ngoại lệ, mutexes, kết nối cơ sở dữ liệu, yêu cầu cơ sở dữ liệu, kết nối máy chủ, đồng hồ, tay cầm hệ điều hành, v.v. và cuối cùng, nhưng không kém phần quan trọng, bộ nhớ.
Phần cơ sở dữ liệu không phải là không đáng kể, như nếu bạn chấp nhận trả giá, bạn thậm chí có thể viết theo kiểu " lập trình giao dịch ", thực thi các dòng và các dòng mã cho đến khi quyết định, cuối cùng, nếu bạn muốn cam kết tất cả các thay đổi. hoặc, nếu không thể, hoàn nguyên tất cả các thay đổi (miễn là mỗi dòng đáp ứng ít nhất Bảo đảm Ngoại lệ Mạnh mẽ). (xem phần thứ hai của bài viết Herb's Sutter này về lập trình giao dịch).
Và giống như một câu đố, mọi thứ đều khớp.
RAII là một phần của C ++, C ++ không thể là C ++ nếu không có nó.
Điều này giải thích tại sao các nhà phát triển C ++ có kinh nghiệm lại say mê RAII và tại sao RAII là thứ đầu tiên họ tìm kiếm khi thử một ngôn ngữ khác.
Và nó giải thích tại sao Garbage Collector, trong khi bản thân nó là một phần công nghệ tuyệt vời, lại không quá ấn tượng theo quan điểm của nhà phát triển C ++:
- RAII đã xử lý hầu hết các trường hợp do GC xử lý
- GC xử lý tốt hơn RAII với các tham chiếu vòng tròn trên các đối tượng được quản lý thuần túy (giảm nhẹ bằng cách sử dụng thông minh con trỏ yếu)
- Tuy nhiên, một GC bị giới hạn về bộ nhớ, trong khi RAII có thể xử lý bất kỳ loại tài nguyên nào.
- Như đã mô tả ở trên, RAII có thể làm được nhiều hơn thế ...