Như bạn đã rõ ràng đã đoán, vâng, C ++ cung cấp các khả năng tương tự mà không cần cơ chế đó. Như vậy, nói đúng ra, cơ chế try
/ finally
là không thực sự cần thiết.
Điều đó nói rằng, làm mà không có nó áp đặt một số yêu cầu trên cách phần còn lại của ngôn ngữ được thiết kế. Trong C ++, tập hợp các hành động tương tự được thể hiện trong hàm hủy của lớp. Điều này hoạt động chủ yếu (độc quyền?) Bởi vì lời gọi hàm hủy trong C ++ mang tính xác định. Điều này, đến lượt nó, dẫn đến một số quy tắc khá phức tạp về thời gian sống của đối tượng, một số trong đó được quyết định là không trực quan.
Hầu hết các ngôn ngữ khác cung cấp một số hình thức thu gom rác thay thế. Mặc dù có những điều về thu gom rác gây tranh cãi (ví dụ, hiệu quả của nó so với các phương pháp quản lý bộ nhớ khác), một điều thường không phải là: thời gian chính xác khi một đối tượng sẽ được "dọn dẹp" bởi trình thu gom rác không được gắn trực tiếp đến phạm vi của đối tượng. Điều này ngăn chặn việc sử dụng nó khi dọn dẹp cần phải có tính xác định hoặc khi nó chỉ đơn giản là cần thiết cho hoạt động chính xác hoặc khi xử lý các tài nguyên quý giá đến mức hầu hết việc dọn dẹp của chúng không bị trì hoãn một cách tùy tiện. try
/ finally
cung cấp một cách để các ngôn ngữ đó xử lý các tình huống đòi hỏi phải dọn dẹp xác định.
Tôi nghĩ rằng những người cho rằng cú pháp C ++ cho khả năng này là "kém thân thiện" hơn so với Java là khá thiếu điểm. Tồi tệ hơn, họ đang thiếu một điểm quan trọng hơn nhiều về sự phân chia trách nhiệm vượt xa cú pháp và có nhiều việc phải làm với cách thiết kế mã.
Trong C ++, việc dọn dẹp xác định này xảy ra trong hàm hủy của đối tượng. Điều đó có nghĩa là đối tượng có thể (và thông thường nên được) được thiết kế để tự dọn dẹp. Điều này đi vào bản chất của thiết kế hướng đối tượng - một lớp nên được thiết kế để cung cấp một sự trừu tượng hóa và thực thi các bất biến của chính nó. Trong C ++, người ta thực hiện chính xác điều đó - và một trong những bất biến mà nó cung cấp là khi đối tượng bị phá hủy, các tài nguyên được điều khiển bởi đối tượng đó (tất cả chúng, không chỉ bộ nhớ) sẽ bị hủy một cách chính xác.
Java (và tương tự) có phần khác nhau. Mặc dù họ (hỗ trợ) finalize
về mặt lý thuyết có thể cung cấp các khả năng tương tự, nhưng sự hỗ trợ đó yếu đến mức về cơ bản nó không thể sử dụng được (và trên thực tế, về cơ bản không bao giờ được sử dụng).
Kết quả là, thay vì bản thân lớp có thể thực hiện việc dọn dẹp cần thiết, khách hàng của lớp cần thực hiện các bước để làm như vậy. Nếu chúng ta thực hiện một so sánh đủ thiển cận, thoạt nhìn có thể thấy rằng sự khác biệt này là khá nhỏ và Java khá cạnh tranh với C ++ về mặt này. Chúng tôi kết thúc với một cái gì đó như thế này. Trong C ++, lớp trông giống như thế này:
class Foo {
// ...
public:
void do_whatever() { if (xyz) throw something; }
~Foo() { /* handle cleanup */ }
};
... và mã máy khách trông giống như thế này:
void f() {
Foo f;
f.do_whatever();
// possibly more code that might throw here
}
Trong Java, chúng ta trao đổi thêm một chút mã trong đó đối tượng được sử dụng ít hơn một chút trong lớp. Điều này ban đầu trông giống như một sự đánh đổi thậm chí khá. Trong thực tế, nó cách xa nó, bởi vì trong hầu hết các mã điển hình, chúng tôi chỉ định nghĩa lớp ở một nơi, nhưng chúng tôi sử dụng nó ở nhiều nơi. Cách tiếp cận C ++ có nghĩa là chúng ta chỉ viết mã đó để xử lý việc dọn dẹp ở một nơi. Cách tiếp cận Java có nghĩa là chúng ta phải viết mã đó để xử lý việc dọn dẹp nhiều lần, ở nhiều nơi - mỗi nơi chúng ta sử dụng một đối tượng của lớp đó.
Nói tóm lại, cách tiếp cận Java về cơ bản đảm bảo rằng nhiều trừu tượng mà chúng tôi cố gắng cung cấp là "rò rỉ" - bất kỳ và mọi lớp yêu cầu dọn dẹp xác định đều bắt buộc khách hàng của lớp phải biết về các chi tiết về việc dọn dẹp và cách làm sạch , thay vì những chi tiết bị ẩn trong chính lớp.
Mặc dù tôi đã gọi nó là "cách tiếp cận Java" ở trên, try
/ finally
và các cơ chế tương tự dưới các tên khác không hoàn toàn bị hạn chế đối với Java. Đối với một ví dụ nổi bật, hầu hết (tất cả?) Của các ngôn ngữ .NET (ví dụ: C #) đều cung cấp giống nhau.
Các lần lặp lại gần đây của cả Java và C # cũng cung cấp một cái gì đó ở nửa điểm giữa Java "cổ điển" và C ++ về vấn đề này. Trong C #, một đối tượng muốn tự động dọn dẹp nó có thể thực hiện IDisposable
giao diện, cung cấp một Dispose
phương thức (ít nhất là mơ hồ) tương tự như hàm hủy của C ++. Mặc dù điều này có thể được sử dụng thông qua try
/ finally
like trong Java, C # tự động hóa tác vụ thêm một chút với using
câu lệnh cho phép bạn xác định các tài nguyên sẽ được tạo khi phạm vi được nhập và bị hủy khi phạm vi được thoát. Mặc dù vẫn còn thiếu mức độ tự động hóa và sự chắc chắn do C ++ cung cấp, đây vẫn là một cải tiến đáng kể so với Java. Đặc biệt, người thiết kế lớp có thể tập trung hóa các chi tiết về cách thứcđể loại bỏ các lớp trong việc thực hiện IDisposable
. Tất cả những gì còn lại cho lập trình viên máy khách là gánh nặng ít hơn khi viết một using
câu lệnh để đảm bảo rằng IDisposable
giao diện sẽ được sử dụng khi cần. Trong Java 7 và mới hơn, các tên đã được thay đổi để bảo vệ tội lỗi, nhưng ý tưởng cơ bản là giống hệt nhau.