Làm thế nào quan trọng để bạn tìm thấy sự an toàn ngoại lệ trong mã C ++ của bạn?


19

Mỗi lần tôi cân nhắc việc làm cho mã của mình trở nên ngoại lệ an toàn, tôi biện minh là không làm điều đó bởi vì nó sẽ rất tốn thời gian. Hãy xem xét đoạn trích tương đối đơn giản này:

Level::Entity* entity = new Level::Entity();
entity->id = GetNextId();
entity->AddComponent(new Component::Position(x, y));
entity->AddComponent(new Component::Movement());
entity->AddComponent(new Component::Render());
allEntities.push_back(entity); // std::vector
entityById[entity->id] = entity; // std::map
return entity;

Để thực hiện bảo đảm ngoại lệ cơ bản, tôi có thể sử dụng một con trỏ có phạm vi trong các newcuộc gọi. Điều này sẽ ngăn rò rỉ bộ nhớ nếu bất kỳ cuộc gọi nào được đưa ra một ngoại lệ.

Tuy nhiên, giả sử tôi muốn thực hiện một đảm bảo ngoại lệ mạnh mẽ. Ít nhất, tôi sẽ cần phải thực hiện một con trỏ dùng chung cho các thùng chứa của mình (tôi không sử dụng Boost), một Entity::Swapcách không cần thiết để thêm các thành phần nguyên tử và một số thành ngữ để thêm nguyên tử vào cả VectorMap. Những điều này không chỉ tốn thời gian để thực hiện mà còn tốn kém vì nó liên quan đến việc sao chép nhiều hơn so với giải pháp không an toàn ngoại lệ.

Cuối cùng, tôi cảm thấy như thời gian dành cho việc đó sẽ không được biện minh chỉ để một CreateEntitychức năng đơn giản là ngoại lệ an toàn. Tôi có lẽ chỉ muốn trò chơi hiển thị một lỗi và đóng tại thời điểm đó.

Làm thế nào đến nay bạn có điều này trong các dự án trò chơi của riêng bạn? Nói chung có thể chấp nhận viết mã không an toàn ngoại lệ cho một chương trình có thể bị sập khi có ngoại lệ không?


21
Khi tôi làm việc trong các dự án C ++ trong quá khứ, về cơ bản, chúng tôi đã giả vờ ngoại lệ không tồn tại.
Tetrad

2
Tôi, cá nhân, yêu ngoại lệ và thường thực sự thích tìm ra cách làm cho mã của tôi cung cấp sự đảm bảo mạnh mẽ. Nếu bạn sẽ gặp sự cố khi bạn gặp một ngoại lệ mặc dù ... có thể đừng bận tâm đến nó.

7
Cá nhân tôi sử dụng ngoại lệ để quản lý .. hm .. 'ngoại lệ' không phải lỗi. Giống như, nếu tôi biết rằng một chức năng có thể bị sập, tôi đã để nó bị sập, nhưng nếu tôi biết rằng một chức năng có thể không đọc thành công một kho lưu trữ, nhưng có một cách khác, tôi bao quanh nó bằng một thử, bắt. và nếu đó là một ngoại lệ "không thể đọc", tôi chỉ quản lý nó theo cách khác mà tôi sẽ làm mà không cần đọc lưu trữ.
Gustavo Maciel

Những gì Gtoknu nói, bạn cần phải nhận thức được ngoại lệ nào mà bất kỳ thư viện bên ngoài nào có thể ném ra.
Peter lsted

Câu trả lời:


26

Nếu bạn sẽ sử dụng ngoại lệ (điều này không có nghĩa là bạn nên có, có những ưu và nhược điểm đối với phương pháp đó nằm ngoài phạm vi cụ thể của câu hỏi này), bạn nên viết mã an toàn ngoại lệ đúng.

Mã không sử dụng ngoại lệ và rõ ràng không ngoại lệ an toàn sẽ tốt hơn mã sử dụng chúng nhưng lại khẳng định một nửa an toàn ngoại lệ của nó. Nếu bạn gặp trường hợp sau, bạn có cả một lớp lỗi và lỗi có thể xảy ra khi có ngoại lệ xảy ra mà bạn hoàn toàn không thể phục hồi, đưa ra một trong những lợi ích của ngoại lệ hoàn toàn vô hiệu. Ngay cả khi đó là một loại thất bại tương đối hiếm, nó vẫn có thể.

Điều này không có nghĩa là nếu bạn sử dụng ngoại lệ, tất cả các mã phải cung cấp bảo đảm ngoại lệ mạnh - chỉ là mỗi đoạn mã sẽ cung cấp bảo đảm một loại nào đó (không, cơ bản, mạnh) để tiêu thụ mã đó bạn biết những gì đảm bảo cho bạn người tiêu dùng có thể cung cấp. Nói chung, thành phần càng thấp trong câu hỏi, bảo đảm càng mạnh.

Nếu bạn sẽ không bao giờ cung cấp một bảo đảm mạnh mẽ hoặc sẽ không bao giờ thực sự nắm bắt các mô hình xử lý ngoại lệ và tất cả các công việc phụ và nhược điểm tiềm ẩn, bạn cũng sẽ không thực sự nhận được tất cả các lợi thế mà nó có thể dễ dàng hơn thời gian chỉ để ngoại lệ hoàn toàn.


12
Chỉ riêng điều này có giá trị +1: "Mã không sử dụng ngoại lệ và rõ ràng không ngoại lệ an toàn sẽ tốt hơn mã sử dụng chúng nhưng là một nửa khẳng định an toàn ngoại lệ của nó."
Maximus Minimus

14

Nguyên tắc chung của tôi: Nếu bạn cần sử dụng ngoại lệ, hãy sử dụng ngoại lệ. Nếu bạn không cần sử dụng ngoại lệ, đừng sử dụng ngoại lệ. Nếu bạn không biết liệu bạn có cần sử dụng ngoại lệ hay không, bạn không cần sử dụng ngoại lệ.

Trường hợp ngoại lệ là có tuyến phòng thủ cuối cùng tuyệt đối của bạn trong các ứng dụng luôn luôn quan trọng. Nếu bạn đang viết phần mềm kiểm soát không lưu hoàn toàn không thể bị lỗi, hãy sử dụng ngoại lệ. Nếu bạn đang viết mã kiểm soát cho nhà máy điện hạt nhân, hãy sử dụng ngoại lệ.

Mặt khác, khi phát triển một trò chơi, bạn tốt hơn hết là tuân theo câu thần chú: Sụp đổ sớm, sụp đổ lớn. Nếu có một vấn đề, bạn thực sự muốn biết về nó càng sớm càng tốt; bạn không bao giờ muốn các lỗi bị vô hình 'xử lý', bởi vì điều này thường sẽ che giấu nguyên nhân thực sự của các hành vi sai trái sau này và khiến việc gỡ lỗi trở nên khó khăn hơn nhiều so với yêu cầu.


5

Vấn đề với các ngoại lệ là dù sao bạn cũng phải đối phó với chúng. Nếu bạn nhận được một ngoại lệ vì hết bộ nhớ thì có thể bạn sẽ không thể xử lý lỗi đó. Cũng có thể chỉ cần đổ toàn bộ trò chơi vào 1 trình xử lý ngoại lệ khổng lồ xuất hiện một hộp lỗi.

Để phát triển trò chơi, tôi thích thử và tìm cách làm cho mã của mình tiếp tục với những thất bại khi có thể.

Ví dụ, tôi sẽ cứng mã lưới ERROR đặc biệt vào trò chơi của mình. Đó là một vòng tròn màu đỏ xoay với chữ X màu trắng trên đó và viền trắng. Tùy thuộc vào trò chơi, một người vô hình có thể tốt hơn. Nó có một ví dụ đơn được khởi tạo khi bắt đầu. Vì nó được đưa vào mã thay vì sử dụng bất kỳ tệp bên ngoài nào nên không thể thất bại.

Trong trường hợp một cuộc gọi loadMesh bình thường không thành công thay vì ném lại một số lỗi và gặp sự cố với máy tính để bàn, nó sẽ âm thầm ghi lại lỗi vào console / logfile và trả về lưới lỗi được mã hóa cứng. Tôi phát hiện ra rằng Skyrim thực sự làm điều tương tự khi một mod làm hỏng mọi thứ. Có một khả năng tốt là người chơi thậm chí sẽ không nhìn thấy lưới lỗi (trừ khi có một số vấn đề lớn và nhiều trong số đó giống như vậy).

Có một shader dự phòng quá. Một trong số đó thực hiện các hoạt động ma trận đỉnh cơ bản nhưng nếu không có ma trận thống nhất vì một số lý do thì nó vẫn nên thực hiện chế độ xem trực giao. Nó không có bóng / ánh sáng / vật liệu thích hợp (mặc dù nó có cờ color_overide để tôi có thể sử dụng nó với lưới lỗi của mình).

Vấn đề duy nhất có thể xảy ra là nếu phiên bản lưới lỗi có thể phá vỡ trò chơi vĩnh viễn theo một cách nào đó. Ví dụ, đảm bảo rằng nó không áp dụng vật lý vì nếu người chơi tải một khu vực, thì lưới lỗi sẽ rơi xuống sàn sau đó nếu họ tải lại trò chơi lần này với lưới hoạt động chính xác, nếu nó lớn hơn thì có thể nhúng trong lòng đất. Điều đó không quá khó vì có lẽ bạn sẽ lưu trữ các mắt lưới vật lý của mình với các đồ họa của bạn. Kịch bản cũng có thể là một vấn đề. Với một số thứ bạn sẽ chỉ phải chết cứng. Không thực sự chắc chắn việc sử dụng 'mức' trở lại sẽ là gì (mặc dù bạn có thể tạo một căn phòng nhỏ với một dấu hiệu cho biết có lỗi, chỉ cần đảm bảo rằng lưu bị vô hiệu hóa vì bạn không muốn người chơi bị mắc kẹt trong đó).

Trong trường hợp 'mới', để phát triển trò chơi, có lẽ tốt hơn là xem xét sử dụng một loại nhà máy nào đó. Nó có thể cung cấp cho bạn những lợi thế khác như sắp xếp các đối tượng trước khi bạn thực sự cần chúng và / hoặc làm cho chúng đồng loạt. Nó cũng giúp dễ dàng tạo ra những thứ như một chỉ mục toàn cầu của các đối tượng mà bạn có thể sử dụng để quản lý bộ nhớ, tìm kiếm mọi thứ theo một id, (mặc dù bạn cũng có thể làm điều đó trong các hàm tạo). Và tất nhiên bạn cũng có thể xử lý lỗi phân bổ ở đó.


2

Vấn đề lớn về việc thiếu kiểm tra thời gian chạy và sự cố là khả năng tin tặc sử dụng một sự cố để tiếp quản quá trình và có lẽ sau đó là máy.

Bây giờ, một hacker không thể khai thác một sự cố thực sự, ngay khi sự cố xảy ra, quá trình này là vô ích và vô hại. Nếu bạn chỉ đọc từ một con trỏ rỗng thì đó là một sự cố rõ ràng, bạn đã phạm sai lầm và quá trình chết ngay lập tức.

Nguy hiểm xảy ra khi bạn thực thi một lệnh không phải là bất hợp pháp về mặt kỹ thuật, nhưng không phải là những gì bạn định làm, sau đó tin tặc có thể điều khiển hành động đó bằng cách sử dụng dữ liệu được làm cẩn thận nếu không sẽ an toàn.

Một ngoại lệ chưa được thực hiện không thực sự là một sự cố, nó chỉ là một cách để chấm dứt chương trình. Trường hợp ngoại lệ chỉ xảy ra vì ai đó đã viết một thư viện đặc biệt ném một ngoại lệ trong một số trường hợp nhất định. Thông thường để tránh đi vào một tình huống bất ngờ, có thể là một mở cho một tin tặc.

Trên thực tế, động thái nguy hiểm là bắt ngoại lệ thay vì để chúng trượt, điều đó không có nghĩa là bạn không bao giờ nên làm như vậy, nhưng hãy chắc chắn rằng bạn chỉ bắt những người bạn biết bạn có thể xử lý và để phần còn lại trượt. Nếu không, bạn có nguy cơ hoàn tác các biện pháp an toàn cẩn thận đưa vào thư viện của bạn.

Tập trung vào các lỗi không nhất thiết phải đưa ra ngoại lệ, như viết vượt quá cuối mảng. Chương trình của bạn sẽ không thể làm điều đó?


2

Bạn cần xem xét các trường hợp ngoại lệ của bạn và cần xem xét các điều kiện có thể kích hoạt chúng. Rất nhiều thời gian, một tỷ lệ lớn những gì mọi người sử dụng ngoại lệ có thể tránh được bằng các kiểm tra thích hợp trong chu trình xây dựng / kiểm tra gỡ lỗi / phát triển. Sử dụng các xác nhận, vượt qua các tham chiếu, luôn khởi tạo các biến cục bộ khi khai báo, memset-zero tất cả các phân bổ, NULL sau khi miễn phí, v.v., và rất nhiều trường hợp ngoại lệ lý thuyết đã biến mất.

Xem xét: nếu một cái gì đó thất bại và có thể là một ứng cử viên để ném một ngoại lệ - tác động là gì? Nó quan trọng đến mức nào? Nếu bạn không cấp phát bộ nhớ trong trò chơi (lấy ví dụ ban đầu của bạn), bạn thường gặp vấn đề lớn hơn trong tay so với cách bạn xử lý trường hợp thất bại và xử lý trường hợp thất bại bằng cách ngoại lệ sẽ không gây ra vấn đề lớn hơn biến đi. Tệ hơn - nó thực sự có thể che giấu sự thật rằng vấn đề lớn hơn thậm chí còn tồn tại. Bạn có muốn điều đó? Tôi biết tôi không. Tôi biết rằng nếu tôi thất bại trong việc cấp phát bộ nhớ, tôi muốn gặp sự cố và bị đốt cháy khủng khiếp và làm điều đó càng gần điểm hỏng càng tốt, để tôi có thể đột nhập vào trình gỡ lỗi, tìm hiểu những gì đang xảy ra và khắc phục nó đúng cách.


1

Không chắc chắn nếu trích dẫn người khác là phù hợp trên SE nhưng tôi cảm thấy không có câu trả lời nào khác thực sự cảm động về điều này.

Trích dẫn Jason Gregory từ cuốn sách Game Engine Architecture . (SÁCH TUYỆT VỜI!)

"Xử lý ngoại lệ có cấu trúc (SEH) bổ sung rất nhiều chi phí cho chương trình. Mỗi khung ngăn xếp phải được tăng cường để chứa thông tin bổ sung theo yêu cầu của quá trình tháo gỡ ngăn xếp. Ngoài ra, quá trình thư giãn ngăn xếp thường rất chậm - theo thứ tự từ hai đến ba đắt hơn gấp nhiều lần so với việc trả về từ hàm. Ngoài ra, nếu ngay cả một hàm trong chương trình (hoặc thư viện) của bạn sử dụng SEH, toàn bộ chương trình của bạn phải sử dụng SEH. Trình biên dịch không thể biết các hàm nào có thể ở trên bạn trên ngăn xếp cuộc gọi khi bạn ném một ngoại lệ. "

Như đã nói, động cơ của tôi đang xây dựng không sử dụng ngoại lệ. Điều này là do chi phí hiệu suất tiềm năng được đề cập trước đó và thực tế là cho tất cả các mục đích của tôi, mã trả về lỗi hoạt động tốt. Lần duy nhất tôi thực sự cần tìm kiếm thất bại là trong việc khởi tạo và tải tài nguyên và trong những trường hợp đó, việc trả về một boolean cho thấy thành công hoạt động tốt.


2
Xử lý ngoại lệ có cấu trúc là một loại xử lý ngoại lệ cụ thể có sẵn trên Windows, không giống với ngoại lệ C ++ nói chung.
Kylotan

Doh, tôi không thể tin rằng tôi đã không biết điều này. Cảm ơn vì sự đúng đắn của bạn!
KlashnikovKid

0

Tôi luôn làm cho ngoại lệ mã của mình an toàn, đơn giản vì nó giúp dễ dàng biết được vấn đề phát sinh ở đâu, vì tôi có thể đăng nhập một cái gì đó bất cứ khi nào một ngoại lệ được tạo hoặc bắt.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.