Khi nào tôi thực sự nên sử dụng noexcept?


508

Các noexcepttừ khóa có thể được một cách thích hợp áp dụng cho nhiều chữ ký chức năng, nhưng tôi không chắc chắn như khi tôi nên xem xét sử dụng nó trong thực tế. Dựa trên những gì tôi đã đọc cho đến nay, việc bổ sung vào phút cuối noexceptdường như để giải quyết một số vấn đề quan trọng phát sinh khi các nhà xây dựng di chuyển ném. Tuy nhiên, tôi vẫn không thể cung cấp câu trả lời thỏa đáng cho một số câu hỏi thực tế khiến tôi phải đọc thêm về noexceptngay từ đầu.

  1. Có nhiều ví dụ về các hàm mà tôi biết sẽ không bao giờ ném, nhưng trình biên dịch không thể tự xác định. Tôi có nên nối noexceptvào khai báo hàm trong tất cả các trường hợp như vậy không?

    Phải suy nghĩ về việc tôi có cần nối thêm hay không noexceptsau mỗi lần khai báo hàm sẽ làm giảm đáng kể năng suất lập trình viên (và thẳng thắn, sẽ là một nỗi đau ở mông). Đối với những tình huống nào tôi nên cẩn thận hơn về việc sử dụng noexceptvà những tình huống nào tôi có thể thoát khỏi ngụ ý noexcept(false)?

  2. Khi nào tôi thực sự có thể mong đợi để quan sát một sự cải thiện hiệu suất sau khi sử dụng noexcept? Cụ thể, đưa ra một ví dụ về mã mà trình biên dịch C ++ có thể tạo mã máy tốt hơn sau khi thêm noexcept.

    Cá nhân, tôi quan tâm noexceptvì sự tự do gia tăng được cung cấp cho trình biên dịch để áp dụng một cách an toàn các loại tối ưu hóa nhất định. Các trình biên dịch hiện đại có tận dụng noexcepttheo cách này? Nếu không, tôi có thể mong đợi một số trong số họ sẽ làm như vậy trong tương lai gần?


40
Mã sử ​​dụng move_if_nothrow(hoặc whatchamacallit) sẽ thấy sự cải thiện hiệu suất nếu có một ctor di chuyển không có ngoại lệ.
R. Martinho Fernandes


5
Đó là move_if_noexcept.
Nikos

Câu trả lời:


180

Tôi nghĩ rằng còn quá sớm để đưa ra câu trả lời "thực tiễn tốt nhất" cho việc này vì chưa có đủ thời gian để sử dụng nó trong thực tế. Nếu điều này được hỏi về các nhà đầu cơ ném ngay sau khi họ xuất hiện thì câu trả lời sẽ rất khác so với bây giờ.

Phải suy nghĩ về việc tôi có cần nối thêm hay không noexceptsau mỗi lần khai báo hàm sẽ làm giảm đáng kể năng suất lập trình viên (và thẳng thắn, sẽ là một nỗi đau).

Chà, sau đó sử dụng nó khi rõ ràng là hàm sẽ không bao giờ ném.

Khi nào tôi thực sự có thể mong đợi để quan sát một sự cải thiện hiệu suất sau khi sử dụng noexcept? [...] Cá nhân, tôi quan tâm noexceptvì sự tự do gia tăng được cung cấp cho trình biên dịch để áp dụng một cách an toàn một số loại tối ưu hóa nhất định.

Có vẻ như lợi ích tối ưu hóa lớn nhất là từ tối ưu hóa người dùng, không phải là trình biên dịch do khả năng kiểm tra noexceptvà quá tải trên nó. Hầu hết các trình biên dịch tuân theo phương pháp xử lý ngoại lệ không phạt nếu bạn không ném, vì vậy tôi nghi ngờ nó sẽ thay đổi nhiều (hoặc bất cứ điều gì) ở cấp mã máy của mã của bạn, mặc dù có thể giảm kích thước nhị phân bằng cách xóa mã xử lý.

Sử dụng noexcepttrong bốn công cụ lớn (nhà xây dựng, phân công, không phải là công cụ hủy diệt như chúng đã có noexcept) sẽ có thể gây ra những cải tiến tốt nhất vì noexceptkiểm tra là 'phổ biến' trong mã mẫu như trong stdcác thùng chứa. Chẳng hạn, std::vectorsẽ không sử dụng di chuyển của lớp bạn trừ khi nó được đánh dấu noexcept(hoặc trình biên dịch có thể suy ra cách khác).


6
Tôi nghĩ rằng std::terminatemánh khóe vẫn tuân theo mô hình Zero-Cost. Đó là, thực tế là phạm vi các hướng dẫn trong các noexcepthàm được ánh xạ để gọi std::terminatenếu throwđược sử dụng thay cho ngăn xếp ngăn xếp. Do đó, tôi nghi ngờ rằng nó có nhiều chi phí hơn mà theo dõi ngoại lệ thường xuyên.
Matthieu M.

4
@Klaim Xem điều này: stackoverflow.com/a/10128180/964135 Trên thực tế, nó chỉ là không ném, nhưng noexceptđảm bảo điều này.
Pubby

3
"Nếu một noexcepthàm ném sau đó std::terminateđược gọi là có vẻ như nó sẽ liên quan đến một lượng nhỏ chi phí" thì Không, điều này nên được thực hiện bằng cách không tạo các bảng ngoại lệ cho một hàm như vậy, mà bộ điều phối ngoại lệ sẽ bắt và sau đó bảo lãnh.
Potatoswatter

8
Xử lý ngoại lệ @Pubby C ++ thường được thực hiện mà không cần chi phí ngoại trừ các bảng nhảy có ánh xạ có khả năng ném địa chỉ trang web cuộc gọi đến các điểm nhập xử lý. Loại bỏ các bảng đó cũng gần như loại bỏ hoàn toàn việc xử lý ngoại lệ. Sự khác biệt duy nhất là kích thước tập tin thực thi. Có lẽ không có gì đáng nói.
Potatoswatter

26
"Vậy thì hãy sử dụng nó khi rõ ràng là hàm sẽ không bao giờ ném." Tôi không đồng ý. noexceptlà một phần của giao diện của hàm ; bạn không nên thêm nó chỉ vì việc triển khai hiện tại của bạn không xảy ra. Tôi không chắc về câu trả lời đúng cho câu hỏi này, nhưng tôi khá tự tin rằng chức năng của bạn diễn ra như thế nào hôm nay không liên quan gì đến nó ...
Nemo

133

Khi tôi tiếp tục lặp lại những ngày này: ngữ nghĩa đầu tiên .

Thêm noexcept, noexcept(true)noexcept(false)là lần đầu tiên và quan trọng nhất về ngữ nghĩa. Nó chỉ tình cờ điều kiện một số tối ưu hóa có thể.

Là một lập trình viên đọc mã, sự hiện diện của noexceptnó gần giống với const: nó giúp tôi hiểu rõ hơn những gì có thể hoặc không thể xảy ra. Do đó, đáng để dành thời gian suy nghĩ về việc bạn có biết chức năng đó sẽ ném hay không. Đối với một lời nhắc nhở, bất kỳ loại phân bổ bộ nhớ động có thể ném.


Được rồi, bây giờ để tối ưu hóa có thể.

Các tối ưu hóa rõ ràng nhất thực sự được thực hiện trong các thư viện. C ++ 11 cung cấp một số đặc điểm cho phép biết liệu một hàm có noexcepthay không và chính triển khai Thư viện Chuẩn sẽ sử dụng các đặc điểm đó để ưu tiên các noexceptthao tác trên các đối tượng do người dùng xác định, nếu có thể. Chẳng hạn như di chuyển ngữ nghĩa .

Trình biên dịch chỉ có thể cạo một chút chất béo (có lẽ) từ xử lý dữ liệu ngoại lệ, bởi vì nó để đưa vào tài khoản thực tế là bạn có thể nói dối. Nếu một chức năng được đánh dấu noexceptkhông ném, thì std::terminateđược gọi.

Những ngữ nghĩa được chọn vì hai lý do:

  • ngay lập tức được hưởng lợi noexceptngay cả khi phụ thuộc chưa sử dụng nó (khả năng tương thích ngược)
  • cho phép đặc tả noexceptkhi gọi các hàm có thể ném theo lý thuyết, nhưng không được mong đợi cho các đối số đã cho

2
Có thể tôi ngây thơ, nhưng tôi sẽ tưởng tượng một chức năng chỉ gọi các noexceptchức năng sẽ không cần phải làm gì đặc biệt, bởi vì bất kỳ trường hợp ngoại lệ nào có thể phát sinh kích hoạt terminatetrước khi chúng đạt đến cấp độ này. Điều này khác rất nhiều với việc phải đối phó và tuyên truyền một bad_allocngoại lệ.

8
Vâng, có thể định nghĩa noexcept theo cách bạn đề xuất, nhưng đó sẽ là một tính năng thực sự không thể sử dụng được. Nhiều chức năng có thể ném nếu một số điều kiện không giữ được và bạn không thể gọi chúng ngay cả khi bạn biết các điều kiện được đáp ứng. Ví dụ, bất kỳ hàm nào có thể ném std :: không hợp lệ.
tr3w

3
@MatthieuM. Một chút muộn để trả lời, nhưng tuy nhiên. Các hàm được đánh dấu không có ngoại lệ có thể gọi các hàm khác có thể ném, lời hứa là hàm này sẽ không phát ra ngoại lệ tức là chúng chỉ phải tự xử lý ngoại lệ đó!
Honf

4
Tôi đã đưa ra câu trả lời này từ lâu, nhưng sau khi đọc và suy nghĩ về nó, tôi có một nhận xét / câu hỏi. "Di chuyển ngữ nghĩa" là ví dụ duy nhất tôi từng thấy bất cứ ai đưa ra noexceptrõ ràng là hữu ích / một ý tưởng tốt. Tôi bắt đầu nghĩ đến việc xây dựng di chuyển, chuyển nhượng và trao đổi là những trường hợp duy nhất có ... Bạn có biết ai khác không?
Nemo

5
@Nemo: Trong thư viện Standard, nó có thể là duy nhất, tuy nhiên nó thể hiện một nguyên tắc có thể được sử dụng lại ở nơi khác. Một hoạt động di chuyển là một hoạt động tạm thời đặt một số trạng thái trong "limbo" và chỉ khi nào thì noexceptai đó mới có thể sử dụng nó một cách tự tin trên một phần dữ liệu có thể được truy cập sau đó. Tôi có thể thấy ý tưởng này đang được sử dụng ở nơi khác, nhưng thư viện Standard khá mỏng trong C ++ và nó chỉ được sử dụng để tối ưu hóa các bản sao của các yếu tố tôi nghĩ.
Matthieu M.

77

Điều này thực sự tạo ra sự khác biệt lớn (có khả năng) đối với trình tối ưu hóa trong trình biên dịch. Trình biên dịch đã thực sự có tính năng này trong nhiều năm qua câu lệnh throw () sau một định nghĩa hàm, cũng như các phần mở rộng sở hữu. Tôi có thể đảm bảo với bạn rằng các trình biên dịch hiện đại tận dụng kiến ​​thức này để tạo mã tốt hơn.

Hầu như mọi tối ưu hóa trong trình biên dịch đều sử dụng một cái gì đó gọi là "biểu đồ luồng" của hàm để suy luận về những gì hợp pháp. Biểu đồ luồng bao gồm những gì thường được gọi là "khối" của hàm (các vùng mã có một lối vào duy nhất và một lối thoát duy nhất) và các cạnh giữa các khối để chỉ ra nơi dòng chảy có thể nhảy tới. Noexcept làm thay đổi biểu đồ dòng chảy.

Bạn đã yêu cầu một ví dụ cụ thể. Xem xét mã này:

void foo(int x) {
    try {
        bar();
        x = 5;
        // Other stuff which doesn't modify x, but might throw
    } catch(...) {
        // Don't modify x
    }

    baz(x); // Or other statement using x
}

Biểu đồ luồng cho chức năng này là khác nhau nếu barđược dán nhãn noexcept(không có cách nào để thực thi nhảy giữa điểm cuối barvà câu lệnh bắt). Khi được gắn nhãn là noexcept, trình biên dịch chắc chắn giá trị của x là 5 trong hàm baz - khối x = 5 được cho là "thống trị" khối baz (x) mà không có cạnh từ bar()câu lệnh bắt.

Sau đó, nó có thể làm một cái gì đó gọi là "truyền liên tục" để tạo mã hiệu quả hơn. Ở đây nếu baz được nội tuyến, các câu lệnh sử dụng x cũng có thể chứa các hằng số và sau đó những gì được sử dụng để đánh giá thời gian chạy có thể được chuyển thành đánh giá thời gian biên dịch, v.v.

Dù sao, câu trả lời ngắn: noexceptcho phép trình biên dịch tạo ra một biểu đồ luồng chặt chẽ hơn và biểu đồ luồng được sử dụng để lý giải về tất cả các loại tối ưu hóa trình biên dịch phổ biến. Đối với một trình biên dịch, chú thích của người dùng về bản chất này là tuyệt vời. Trình biên dịch sẽ cố gắng tìm ra công cụ này, nhưng nó thường không thể (hàm được đề cập có thể nằm trong tệp đối tượng khác mà trình biên dịch không hiển thị hoặc sử dụng quá mức một số chức năng không hiển thị) hoặc khi có, có một số ngoại lệ tầm thường có thể bị ném mà bạn thậm chí không biết, vì vậy nó không thể gắn nhãn nó thành noexcept(ví dụ phân bổ bộ nhớ có thể ném bad_alloc chẳng hạn).


3
Điều này thực sự tạo ra sự khác biệt trong thực tế? Ví dụ là giả định bởi vì không có gì trước đó x = 5có thể ném. Nếu phần đó của trykhối phục vụ cho bất kỳ mục đích nào, lý luận sẽ không được duy trì.
Potatoswatter

7
Tôi muốn nói rằng nó thực sự khác biệt trong việc tối ưu hóa các chức năng có chứa các khối thử / bắt. Ví dụ tôi đưa ra mặc dù không có ý định, nhưng không đầy đủ. Điểm lớn hơn là noexcept (như câu lệnh throw () trước nó) giúp trình biên dịch tạo ra một biểu đồ luồng nhỏ hơn (ít cạnh hơn, ít khối hơn), một phần cơ bản của nhiều tối ưu hóa sau đó.
Terry Mahaffey

Làm thế nào trình biên dịch có thể nhận ra rằng mã có thể ném ngoại lệ? Là và truy cập vào mảng được coi là ngoại lệ có thể?
Tomas Kubes

3
@ qub1n Nếu trình biên dịch có thể thấy phần thân của hàm, nó có thể tìm các throwcâu lệnh rõ ràng hoặc những thứ khác newcó thể ném. Nếu trình biên dịch không thể nhìn thấy phần thân thì nó phải dựa vào sự hiện diện hay vắng mặt của noexcept. Truy cập mảng đơn giản thường không tạo ra ngoại lệ (C ++ không có giới hạn kiểm tra) vì vậy không, truy cập mảng sẽ không đơn độc khiến trình biên dịch nghĩ rằng một hàm ném ngoại lệ. (Truy cập ngoài giới hạn là UB, không phải là ngoại lệ được bảo đảm.)
cdhowie

@cdhowie " nó phải dựa vào sự hiện diện hay vắng mặt của ngoại lệ " hoặc sự hiện diện của throw()C ++ trước khi không chấp nhận
tò mò

57

noexceptcó thể cải thiện đáng kể hiệu suất của một số hoạt động. Điều này không xảy ra ở cấp độ tạo mã máy bằng trình biên dịch, nhưng bằng cách chọn thuật toán hiệu quả nhất: như những người khác đã đề cập, bạn thực hiện lựa chọn này bằng cách sử dụng hàm std::move_if_noexcept. Chẳng hạn, sự tăng trưởng của std::vector(ví dụ, khi chúng ta gọi reserve) phải cung cấp một đảm bảo an toàn ngoại lệ mạnh mẽ. Nếu nó biết rằng Tconstructor di chuyển không ném, nó có thể di chuyển mọi phần tử. Nếu không thì phải sao chép tất cả Ts. Điều này đã được mô tả chi tiết trong bài viết này .


4
Phụ lục: Điều đó có nghĩa là nếu bạn xác định các hàm tạo di chuyển hoặc các toán tử chuyển nhượng thêm noexceptvào chúng (nếu có)! Các hàm thành viên di chuyển được xác định rõ ràng đã noexcepttự động thêm vào chúng (nếu có).
mucaho

33

Khi nào tôi thực sự có thể ngoại trừ để quan sát sự cải thiện hiệu suất sau khi sử dụng noexcept? Cụ thể, đưa ra một ví dụ về mã mà trình biên dịch C ++ có thể tạo mã máy tốt hơn sau khi thêm noexcept.

Ừm, không bao giờ? Không bao giờ là một thời gian? Không bao giờ.

noexceptlà để tối ưu hóa hiệu suất của trình biên dịch theo cùng một cách constdành cho tối ưu hóa hiệu suất của trình biên dịch. Đó là, gần như không bao giờ.

noexceptchủ yếu được sử dụng để cho phép "bạn" phát hiện tại thời điểm biên dịch nếu một hàm có thể đưa ra một ngoại lệ. Hãy nhớ rằng: hầu hết các trình biên dịch không phát ra mã đặc biệt cho các trường hợp ngoại lệ trừ khi nó thực sự ném một cái gì đó. Vì vậy, noexceptkhông phải là vấn đề đưa ra gợi ý cho trình biên dịch về cách tối ưu hóa hàm nhiều như việc cung cấp cho bạn gợi ý về cách sử dụng hàm.

Các mẫu như move_if_noexceptsẽ phát hiện nếu hàm tạo di chuyển được xác định noexceptvà sẽ trả về một const&thay vì &&loại nếu không. Đó là một cách nói để di chuyển nếu nó rất an toàn để làm như vậy.

Nói chung, bạn nên sử dụng noexceptkhi bạn nghĩ rằng nó thực sự hữu ích để làm như vậy. Một số mã sẽ có các đường dẫn khác nhau nếu is_nothrow_constructibleđúng với loại đó. Nếu bạn đang sử dụng mã sẽ làm điều đó, thì hãy thoải mái với noexceptcác nhà xây dựng phù hợp.

Nói tóm lại: sử dụng nó cho các nhà xây dựng di chuyển và các công trình tương tự, nhưng không cảm thấy như bạn phải đi theo nó.


13
Nghiêm túc, move_if_noexceptsẽ không trả lại một bản sao, nó sẽ trả về tham chiếu const lvalue thay vì tham chiếu rvalue. Nói chung, điều đó sẽ khiến người gọi tạo một bản sao thay vì di chuyển, nhưng move_if_noexceptkhông thực hiện bản sao. Nếu không, giải thích tuyệt vời.
Jonathan Wakely

12
+1 Jonathan. Thay đổi kích thước một vectơ, ví dụ, sẽ di chuyển các đối tượng thay vì sao chép chúng nếu hàm tạo di chuyển là noexcept. Vì vậy, "không bao giờ" là không đúng sự thật.
mfontanini

4
Ý tôi là, trình biên dịch sẽ tạo mã tốt hơn trong tình huống đó. OP đang yêu cầu một ví dụ mà trình biên dịch có thể tạo ra một ứng dụng được tối ưu hóa hơn. Đây có vẻ là trường hợp (mặc dù nó không phải là tối ưu hóa trình biên dịch ).
mfontanini

7
@mfontanini: Trình biên dịch chỉ tạo mã tốt hơn vì trình biên dịch buộc phải biên dịch một bộ mã khác nhau . Nó chỉ hoạt động vì std::vectorđược viết để buộc trình biên dịch biên dịch mã khác nhau . Nó không phải là về trình biên dịch phát hiện một cái gì đó; đó là về mã người dùng phát hiện điều gì đó.
Nicol Bolas

3
Vấn đề là, tôi dường như không thể tìm thấy "tối ưu hóa trình biên dịch" trong đoạn trích khi bắt đầu câu trả lời của bạn. Như @ChristianRau đã nói, trình biên dịch tạo ra một mã hiệu quả hơn, không quan trọng nguồn gốc của việc tối ưu hóa đó là gì. Rốt cuộc, trình biên dịch đang tạo ra một mã hiệu quả hơn, phải không? PS: Tôi chưa bao giờ nói đó là tối ưu hóa trình biên dịch, tôi thậm chí còn nói "Đây không phải là tối ưu hóa trình biên dịch".
mfontanini

22

Theo lời của Bjarne ( Ngôn ngữ lập trình C ++, Ấn bản thứ 4 , trang 365):

Trong trường hợp chấm dứt là một phản ứng có thể chấp nhận, một ngoại lệ chưa được xử lý sẽ đạt được điều đó bởi vì nó biến thành một lệnh gọi chấm dứt () (§13.5.2.5). Ngoài ra, một công noexceptcụ xác định (§13.5.1.1) có thể làm cho mong muốn đó rõ ràng.

Hệ thống chịu lỗi thành công là đa cấp. Mỗi cấp độ đối phó với càng nhiều lỗi càng tốt mà không bị méo mó và khiến phần còn lại lên cấp cao hơn. Ngoại lệ hỗ trợ quan điểm đó. Hơn nữa, terminate()hỗ trợ quan điểm này bằng cách cung cấp một lối thoát nếu chính cơ chế xử lý ngoại lệ bị hỏng hoặc nếu nó được sử dụng không đầy đủ, do đó không để lại ngoại lệ. Tương tự, noexceptcung cấp một lối thoát đơn giản cho các lỗi trong đó cố gắng khôi phục dường như không thể thực hiện được.

double compute(double x) noexcept;     {
    string s = "Courtney and Anya";
    vector<double> tmp(10);
    // ...
}

Hàm tạo vector có thể không lấy được bộ nhớ trong mười lần nhân đôi và ném a std::bad_alloc. Trong trường hợp đó, chương trình chấm dứt. Nó chấm dứt vô điều kiện bằng cách gọi std::terminate()(§30.4.1.3). Nó không gọi hàm hủy từ các hàm gọi. Nó được định nghĩa triển khai cho dù các hàm hủy từ phạm vi giữa thrownoexcept(ví dụ, đối với s trong compute ()) được gọi. Chương trình sắp kết thúc, vì vậy chúng ta không nên phụ thuộc vào bất kỳ đối tượng nào. Bằng cách thêm một công noexceptcụ xác định, chúng tôi chỉ ra rằng mã của chúng tôi không được viết để đối phó với một cú ném.


2
Bạn có một nguồn cho trích dẫn này?
Anton Golov

5
@AntonGolov "Ngôn ngữ lập trình C ++, phiên bản thứ 4" pg. 365
Rusty Shackleford

1
Điều này nghe có vẻ như tôi thực sự nên thêm noexceptvào mỗi lần, ngoại trừ tôi rõ ràng muốn quan tâm đến các ngoại lệ. Hãy là sự thật, hầu hết các trường hợp ngoại lệ là không thể thực hiện được và / hoặc gây tử vong đến mức giải cứu hầu như không hợp lý hoặc có thể. Ví dụ, trong ví dụ được trích dẫn, nếu phân bổ thất bại, ứng dụng sẽ khó có thể tiếp tục hoạt động chính xác.
Sơ khai

21
  1. Có nhiều ví dụ về các hàm mà tôi biết sẽ không bao giờ ném, nhưng trình biên dịch không thể tự xác định. Tôi có nên nối thêm ngoại lệ vào khai báo hàm trong tất cả các trường hợp như vậy không?

noexceptlà khó khăn, vì nó là một phần của giao diện chức năng. Đặc biệt, nếu bạn đang viết thư viện, mã khách hàng của bạn có thể phụ thuộc vào thuộc noexcepttính. Có thể khó thay đổi nó sau này, vì bạn có thể phá vỡ mã hiện có. Điều đó có thể ít quan tâm hơn khi bạn triển khai mã chỉ được sử dụng bởi ứng dụng của bạn.

Nếu bạn có một chức năng không thể ném, hãy tự hỏi liệu nó sẽ thích ở lại noexcepthay điều đó sẽ hạn chế việc thực hiện trong tương lai? Ví dụ: bạn có thể muốn giới thiệu kiểm tra lỗi đối số bất hợp pháp bằng cách đưa ra các ngoại lệ (ví dụ: đối với kiểm tra đơn vị) hoặc bạn có thể phụ thuộc vào mã thư viện khác có thể thay đổi thông số ngoại lệ của nó. Trong trường hợp đó, an toàn hơn là bảo thủ và bỏ qua noexcept.

Mặt khác, nếu bạn tự tin rằng hàm không bao giờ nên ném và đúng là nó là một phần của đặc tả, bạn nên khai báo nó noexcept. Tuy nhiên, hãy nhớ rằng trình biên dịch sẽ không thể phát hiện các vi phạm noexceptnếu việc triển khai của bạn thay đổi.

  1. Đối với những tình huống nào tôi nên cẩn thận hơn về việc sử dụng noexcept, và trong những tình huống nào tôi có thể thoát khỏi với ẩn ý ngụ ý (sai)?

Có bốn loại chức năng mà bạn nên tập trung vào vì chúng có thể sẽ có tác động lớn nhất:

  1. di chuyển các hoạt động (di chuyển toán tử gán và di chuyển các nhà xây dựng)
  2. hoạt động hoán đổi
  3. bộ giải mã bộ nhớ (xóa toán tử, xóa toán tử [])
  4. hàm hủy (mặc dù những thứ này là ngầm noexcept(true)trừ khi bạn tạo ra chúng noexcept(false))

Các chức năng này thường là noexcept, và rất có thể các triển khai thư viện có thể sử dụng noexcepttài sản. Ví dụ, std::vectorcó thể sử dụng các hoạt động di chuyển không ném mà không phải hy sinh các đảm bảo ngoại lệ mạnh. Nếu không, nó sẽ phải quay lại sao chép các phần tử (như đã làm trong C ++ 98).

Loại tối ưu hóa này ở cấp độ thuật toán và không dựa vào tối ưu hóa trình biên dịch. Nó có thể có một tác động đáng kể, đặc biệt là nếu các yếu tố đắt tiền để sao chép.

  1. Khi nào tôi thực sự có thể mong đợi quan sát sự cải thiện hiệu suất sau khi sử dụng noexcept? Cụ thể, đưa ra một ví dụ về mã mà trình biên dịch C ++ có thể tạo mã máy tốt hơn sau khi thêm noexcept.

Ưu điểm của việc noexceptkhông có đặc điểm kỹ thuật ngoại lệ hoặc throw()là tiêu chuẩn cho phép trình biên dịch tự do hơn khi nói đến ngăn xếp ngăn xếp. Ngay cả trong throw()trường hợp, trình biên dịch phải hoàn toàn giải phóng ngăn xếp (và nó phải thực hiện theo thứ tự ngược lại chính xác của các cấu trúc đối tượng).

Trong noexcepttrường hợp, mặt khác, không bắt buộc phải làm điều đó. Không có yêu cầu rằng ngăn xếp phải được mở ra (nhưng trình biên dịch vẫn được phép làm điều đó). Sự tự do đó cho phép tối ưu hóa mã hơn nữa vì nó làm giảm chi phí luôn có thể giải phóng ngăn xếp.

Câu hỏi liên quan về không có ngoại lệ, giải nén ngăn xếp và hiệu suất đi sâu vào chi tiết hơn về chi phí khi yêu cầu ngăn xếp ngăn xếp.

Tôi cũng đề nghị cuốn sách "Hiệu quả hiện đại C ++" của Scott Meyers, "Mục 14: Khai báo các hàm không có ngoại lệ nếu chúng không phát ra ngoại lệ" để đọc thêm.


Tuy nhiên, sẽ có ý nghĩa hơn nhiều nếu các ngoại lệ được triển khai trong C ++ như trong Java, nơi bạn đánh dấu phương thức có thể ném bằng throwstừ khóa thay vì noexceptphủ định. Tôi chỉ không thể có được một số lựa chọn thiết kế C ++ ...
doc

Họ đặt tên cho nó noexceptthrowđã được thực hiện. Đơn giản chỉ cần đặt throwcó thể được sử dụng gần như cách bạn đề cập, ngoại trừ họ đã làm hỏng thiết kế của nó để nó trở nên gần như vô dụng - thậm chí gây bất lợi. Nhưng chúng tôi đang bị mắc kẹt với nó ngay bây giờ vì loại bỏ nó sẽ là một thay đổi đột phá với rất ít lợi ích. noexceptVề cơ bản là như vậy throw_v2.
AnorZaken

Làm thế nào là throwkhông hữu ích?
tò mò

@cquilguy "ném" chính nó (để ném ngoại lệ) là hữu ích, nhưng "ném" như một công cụ xác định ngoại lệ đã không được chấp nhận và trong C ++ 17 thậm chí đã bị xóa. Để biết lý do tại sao các công cụ xác định ngoại lệ không hữu ích, hãy xem câu hỏi này: stackoverflow.com/questions/88573/
mẹo

1
@ PhilippClaßen Công throw()cụ xác định ngoại lệ không cung cấp bảo đảm giống như nothrow?
tò mò

17

Có nhiều ví dụ về các hàm mà tôi biết sẽ không bao giờ ném, nhưng trình biên dịch không thể tự xác định. Tôi có nên nối thêm ngoại lệ vào khai báo hàm trong tất cả các trường hợp như vậy không?

Khi bạn nói "Tôi biết [họ] sẽ không bao giờ ném", bạn có nghĩa là bằng cách kiểm tra việc thực hiện chức năng bạn biết rằng chức năng đó sẽ không ném. Tôi nghĩ rằng cách tiếp cận là từ trong ra ngoài.

Tốt hơn là xem xét liệu một hàm có thể đưa ngoại lệ trở thành một phần của thiết kế hàm hay không: quan trọng như danh sách đối số và liệu một phương thức có phải là trình biến đổi (... const) hay không. Tuyên bố rằng "chức năng này không bao giờ ném ngoại lệ" là một hạn chế trong việc thực hiện. Bỏ qua nó không có nghĩa là chức năng có thể ném ngoại lệ; nó có nghĩa là phiên bản hiện tại của chức năng tất cả các phiên bản trong tương lai có thể đưa ra ngoại lệ. Đó là một hạn chế làm cho việc thực hiện khó khăn hơn. Nhưng một số phương pháp phải có ràng buộc để thực sự hữu ích; quan trọng nhất, vì vậy chúng có thể được gọi từ các hàm hủy, nhưng cũng để thực hiện mã "roll-back" trong các phương thức cung cấp bảo đảm ngoại lệ mạnh.


Đây là câu trả lời tốt nhất cho đến nay. Bạn đang đảm bảo cho người dùng phương pháp của mình, đó là một cách khác để nói rằng bạn đang hạn chế việc thực hiện của mình mãi mãi (thay đổi đột phá). Cảm ơn cho quan điểm khai sáng.
AnorZaken

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.