2008-09-01 30 views
23

Nếu tôi sử dụng assert() và xác nhận không thành công thì assert() sẽ gọi số abort(), kết thúc chương trình đang chạy đột ngột. Tôi không đủ khả năng trong mã sản xuất của mình. Có cách nào để khẳng định trong thời gian chạy nhưng có thể nắm bắt các xác nhận không thành công vì vậy tôi có cơ hội để xử lý chúng một cách duyên dáng?Làm cách nào tôi có thể khẳng định() mà không sử dụng hủy bỏ()?

Trả lời

23

Vâng, thực tế là có. Bạn sẽ cần phải viết một hàm xác nhận tùy chỉnh cho mình, như là assert() của C++ chính xác là C assert(), với tính năng "abort()" được gộp vào. May mắn thay, điều này thật đơn giản một cách đáng ngạc nhiên.

Assert.hh

template <typename X, typename A> 
inline void Assert(A assertion) 
{ 
    if(!assertion) throw X(); 
} 

Chức năng trên sẽ ném một ngoại lệ nếu một vị không giữ. Sau đó, bạn sẽ có cơ hội để bắt ngoại lệ. Nếu bạn không nắm bắt được ngoại lệ, terminate() sẽ được gọi, sẽ kết thúc chương trình tương tự như abort().

Bạn có thể thắc mắc về việc tối ưu hóa xác nhận khi chúng tôi đang xây dựng để sản xuất. Trong trường hợp này, bạn có thể xác định các hằng số sẽ biểu thị rằng bạn đang xây dựng để sản xuất và sau đó tham chiếu đến hằng số khi bạn Assert().

debug.hh

#ifdef NDEBUG 
    const bool CHECK_WRONG = false; 
#else 
    const bool CHECK_WRONG = true; 
#endif 

main.cc

#include<iostream> 

struct Wrong { }; 

int main() 
{ 
    try { 
     Assert<Wrong>(!CHECK_WRONG || 2 + 2 == 5); 
     std::cout << "I can go to sleep now.\n"; 
    } 
    catch(Wrong e) { 
     std::cerr << "Someone is wrong on the internet!\n"; 
    } 

    return 0; 
} 

Nếu CHECK_WRONG là một hằng số thì cuộc gọi đến Assert() sẽ được biên dịch đi vào sản xuất, ngay cả khi khẳng định là không phải là một biểu thức liên tục. Có một bất lợi nhỏ trong đó bằng cách tham khảo CHECK_WRONG chúng tôi gõ thêm một chút. Nhưng đổi lại, chúng ta có được một lợi thế ở chỗ chúng ta có thể phân loại các nhóm xác nhận khác nhau và kích hoạt và vô hiệu hóa từng nhóm chúng như chúng ta thấy phù hợp. Vì vậy, ví dụ chúng ta có thể định nghĩa một nhóm các xác nhận mà chúng ta muốn kích hoạt ngay cả trong mã sản xuất, và sau đó xác định một nhóm các xác nhận mà chúng ta chỉ muốn thấy trong các bản xây dựng phát triển.

Chức năng Assert() tương đương với cách gõ

if(!assertion) throw X(); 

nhưng nó cho thấy rõ ràng mục đích của các lập trình viên: làm cho một sự khẳng định. Các xác nhận cũng dễ dàng hơn để grep với phương pháp này, giống như đồng bằng assert() s.

Để biết thêm chi tiết về kỹ thuật này, hãy xem Bjarne Stroustrup's Ngôn ngữ lập trình C++ 3e, phần 24.3.7.2.

+0

Vì bạn có thể chỉ bao giờ được mặc định trường hợp ngoại lệ được xây dựng, không có nhiều lợi ích cho này kể từ khi bạn không thể vận chuyển bất kỳ dữ liệu thời gian chạy để các trang web bắt ... –

+0

Giá trị gia tăng của templatizing đối số khẳng định hơn là làm cho nó int là gì? Nó sẽ phải được chuyển đổi sang int cuối cùng anyway. – JohnMcG

+0

@Greg Rogers Bjarne Stroustrup cung cấp giải pháp cho vấn đề bạn đề cập: bạn có thể thêm đối tượng tùy chỉnh mà bạn muốn được ném dưới dạng tham số bổ sung của Assert(). Nhược điểm là Assert() sẽ không giống như khẳng định() nữa vì tham số thêm. – wilhelmtell

6

Các xác nhận trong C/C++ chỉ chạy trong bản dựng gỡ lỗi. Vì vậy, điều này sẽ không xảy ra trong thời gian chạy. Nói chung, các xác nhận sẽ đánh dấu những điều nếu chúng xảy ra cho thấy lỗi và thường hiển thị các giả định trong mã của bạn, v.v.

Nếu bạn muốn có mã kiểm tra lỗi trong thời gian chạy (trong bản phát hành), bạn nên sử dụng ngoại lệ khẳng định đây là những gì chúng được thiết kế để làm. Câu trả lời của bạn về cơ bản kết thúc tốt đẹp một cú ném ngoại lệ trong cú pháp khẳng định. Trong khi điều này sẽ làm việc, không có lợi thế cụ thể để điều này mà tôi có thể xem qua chỉ ném ngoại lệ ở nơi đầu tiên.

+0

Các xác nhận chạy hoặc không chạy trong các bản dựng sản xuất là một tùy chọn trình biên dịch. Họ chắc chắn nhất chạy trong gcc theo mặc định bất kể tùy chọn tối ưu hóa. –

+2

Cá nhân tôi tìm thấy nó là một ý tưởng tốt để giữ các khẳng định trong chế độ phát hành. Bạn có thể thích rằng để ngẫu nhiên treo khi khách hàng của bạn sử dụng các ứng dụng ... –

+2

Đặc biệt là nếu chương trình của bạn sẽ dẫn đến hành vi không xác định nếu khẳng định đó trở thành sai; nó sẽ cho phép khách hàng ít nhất đưa ra một thông báo lỗi có giá trị hơn và các bước tái tạo dễ dàng hơn; xem xét nó KHÔNG BAO GIỜ có nghĩa là xảy ra ngay từ đầu; và bạn nghĩ (và được bảo đảm) nó sẽ không. – dcousens

10

glib's error reporting functions sử dụng phương pháp tiếp tục sau khi xác nhận. glib là thư viện độc lập nền tảng cơ bản mà Gnome (thông qua GTK) sử dụng. Đây là một macro kiểm tra điều kiện tiên quyết và in dấu vết ngăn xếp nếu điều kiện tiên quyết không thành công.

#define RETURN_IF_FAIL(expr)  do {     \ 
if (!(expr))           \ 
{              \ 
     fprintf(stderr,        \ 
       "file %s: line %d (%s): precondition `%s' failed.", \ 
       __FILE__,           \ 
       __LINE__,           \ 
       __PRETTY_FUNCTION__,        \ 
       #expr);            \ 
     print_stack_trace(2);          \ 
     return;             \ 
};    } while(0) 
#define RETURN_VAL_IF_FAIL(expr, val) do {       \ 
if (!(expr))              \ 
{                 \ 
     fprintf(stderr,            \ 
       "file %s: line %d (%s): precondition `%s' failed.",  \ 
       __FILE__,            \ 
       __LINE__,            \ 
       __PRETTY_FUNCTION__,         \ 
       #expr);             \ 
     print_stack_trace(2);           \ 
     return val;             \ 
};    } while(0) 

Đây là chức năng mà in stack trace, viết cho một môi trường có sử dụng các toolchain gnu (gcc):

void print_stack_trace(int fd) 
{ 
    void *array[256]; 
    size_t size; 

    size = backtrace (array, 256); 
    backtrace_symbols_fd(array, size, fd); 
} 

Đây là cách bạn muốn sử dụng các macro:

char *doSomething(char *ptr) 
{ 
    RETURN_VAL_IF_FAIL(ptr != NULL, NULL); // same as assert(ptr != NULL), but returns NULL if it fails. 

    if(ptr != NULL)  // Necessary if you want to define the macro only for debug builds 
    { 
     ... 
    } 

    return ptr; 
} 

void doSomethingElse(char *ptr) 
{ 
    RETURN_IF_FAIL(ptr != NULL); 
} 
+3

@wilhelmtell I tin rằng đây là giải pháp tốt nhất cho đến nay bởi vì tôi không đồng ý với việc sử dụng ngoại lệ. Ngoại lệ nên được cho một cái gì đó đặc biệt, tuy nhiên xử lý Assert là không. Ngoài ra, nếu bạn có kế hoạch để lại Asserts On trong bản phát hành, tôi nghĩ bạn nên trừ khi hiệu suất là quan trọng, phương pháp này sẽ hiệu quả hơn. Bạn cũng có thể cá nhân hóa nó để chương trình của bạn không cần thiết thoát ra khi nó xảy ra và bạn sẽ nhận được thông tin trở lại. Tôi đề nghị đọc chương sách của Assmer Programmer về Asserts và Exceptions. Ngoài ra [liên kết này] (http://www.gotw.ca/publications/mill22.htm). – ForceMagic

5

Đây là những gì tôi có trong "assert.h" (Mac OS 10.4):

#define assert(e) ((void) ((e) ? 0 : __assert (#e, __FILE__, __LINE__))) 
#define __assert(e, file, line) ((void)printf ("%s:%u: failed assertion `%s'\n", file, line, e), abort(), 0) 

Dựa vào đó, thay thế cuộc gọi để hủy bỏ() bằng cách ném (ngoại lệ). Và thay vì printf bạn có thể định dạng chuỗi thành thông báo lỗi của ngoại lệ. Cuối cùng, bạn nhận được một cái gì đó như thế này:

#define assert(e) ((void) ((e) ? 0 : my_assert (#e, __FILE__, __LINE__))) 
#define my_assert(e, file, line) (throw std::runtime_error(\ 
    std::string(file:)+boost::lexical_cast<std::string>(line)+": failed assertion "+e)) 

Tôi chưa cố gắng biên dịch nó, nhưng bạn hiểu ý nghĩa.

Lưu ý: bạn cần phải đảm bảo rằng tiêu đề "ngoại lệ" luôn được bao gồm, cũng như tiêu đề (nếu bạn quyết định sử dụng nó để định dạng thông báo lỗi). Nhưng bạn cũng có thể làm cho "my_assert" một hàm và chỉ khai báo nguyên mẫu của nó. Một cái gì đó như:

void my_assert(const char* e, const char* file, int line); 

Và thực hiện nó ở đâu đó mà bạn có thể tự do bao gồm tất cả các tiêu đề bạn yêu cầu.

Bọc nó vào một số #ifdef DEBUG nếu bạn cần, hoặc không nếu bạn luôn muốn chạy các kiểm tra đó.

+0

Vì vậy, nó vẫn còn để tranh luận xem một vĩ mô là tốt hơn so với một chức năng mẫu. Ngoài ra, toán tử trình tự có nghĩa là toán hạng có thể được thực hiện theo bất kỳ thứ tự tùy ý nào. Tôi nghi ngờ đây là những gì bạn có nghĩa là trong __asert(). – wilhelmtell

+0

Chức năng "__assert" trong khối đầu tiên xuất phát từ tiêu đề hệ thống của tôi (0S 10.4) Tôi hy vọng họ biết họ đang làm gì. Macro giúp dễ dàng nhận thông tin FILE và LINE. Nhưng tại một số điểm nó được khá mỹ phẩm ... – rlerallut

-1
_set_error_mode(_OUT_TO_MSGBOX); 

hãy tin tôi, chức năng này có thể giúp bạn.

+0

Điều này dường như là một chức năng chỉ có Windows: [link] (https://msdn.microsoft.com/en-us/library/aa235388 (v = vs.60). aspx) –

+0

Và ít nhất bạn nên giải thích tiêu đề/trình biên dịch/cái gì là cần thiết để sử dụng nó. –

Các vấn đề liên quan