2010-04-03 31 views
12

Tôi đã triển khai ostream để gỡ lỗi đầu ra gửi kết thúc gửi thông tin gỡ lỗi đến OutputDebugString. Một sử dụng điển hình của nó trông giống như thế này (nơi debug là một đối tượng ostream):Nhược điểm chỉ gỡ lỗi trong C++?

debug << "some error\n"; 

Đối với phát hành xây dựng, những gì là cách đau đớn và performant nhất nhất không đầu ra các báo cáo debug?

Trả lời

7

Làm thế nào về điều này? Bạn sẽ phải kiểm tra xem nó thực sự tối ưu hóa không có gì trong phiên bản:

#ifdef NDEBUG 
    class DebugStream {}; 
    template <typename T> 
    DebugStream &operator<<(DebugStream &s, T) { return s; } 
#else 
    typedef ostream DebugStream; 
#endif 

Bạn sẽ phải vượt qua các đối tượng dòng debug như một DebugStream &, không phải là một ostream &, vì trong phiên bản xây dựng nó không phải là một. Đây là một lợi thế, vì nếu luồng gỡ lỗi của bạn không phải là một ostream, điều đó có nghĩa là bạn không phải chịu hình phạt thời gian chạy bình thường của luồng rỗng hỗ trợ giao diện ostream (các hàm ảo thực sự được gọi nhưng không làm gì cả).

Cảnh báo: Tôi chỉ làm điều này, bình thường tôi sẽ làm điều gì đó tương tự như câu trả lời của Neil - có ý nghĩa vĩ mô "chỉ thực hiện việc này trong bản dựng lỗi", vì vậy rõ ràng mã nguồn là gì? không phải. Một số điều tôi không thực sự muốn trừu tượng.

Macro của Neil cũng có thuộc tính tuyệt đối, chắc chắn, không đánh giá các đối số của nó trong bản phát hành. Ngược lại, ngay cả với mẫu của tôi inlined, bạn sẽ thấy rằng đôi khi:

debug << someFunction() << "\n"; 

không thể được tối ưu hóa để không có gì, bởi vì trình biên dịch không nhất thiết phải biết rằng someFunction() không có tác dụng phụ. Tất nhiên nếu someFunction()không có tác dụng phụ thì bạn có thể muốn nó được gọi trong bản dựng bản phát hành, nhưng đó là sự pha trộn đặc biệt của ghi nhật ký và mã chức năng.

+0

Cảm ơn! Tôi bắt đầu tự nghĩ ra điều gì đó dọc theo những dòng này và tôi rất vui khi thấy tôi không phải là người duy nhất nghĩ về nó. Tôi sẽ thử nó vào thứ hai trở lại tại nơi làm việc và xem như thế nào trình biên dịch có thể tối ưu hóa dòng đi. – Emanuel

+0

Bất kỳ ai sử dụng phương pháp này, xin lưu ý rằng một số mục sẽ không được tối ưu hóa trong chế độ phát hành. Nếu bạn có: '' 'debug << someCPUIntensiveFunctionOrFunctionWhichMayAffectState() <<" \ n "' '' nó sẽ vẫn chạy trong chế độ phát hành. Tôi đã bỏ lỡ cavet này lần đầu tiên trong vòng –

9

Cách phổ biến nhất (và chắc chắn performant nhất) là để loại bỏ chúng bằng cách sử dụng tiền xử lý, sử dụng một cái gì đó như thế này (thực hiện đơn giản nhất có thể):

#ifdef RELEASE 
    #define DBOUT(x) 
#else 
    #define DBOUT(x) x 
#endif 

Sau đó bạn có thể nói

DBOUT(debug << "some error\n"); 

Chỉnh sửa: Bạn có thể làm cho DBOUT phức tạp hơn một chút:

#define DBOUT(x) \ 
    debug << x << "\n" 

cho phép một cú pháp nào đẹp hơn:

DBOUT("Value is " << 42); 

Một lựa chọn thứ hai là xác định DBOUT là suối. Điều này có nghĩa là bạn phải thực hiện một số loại lớp dòng rỗng - xem Implementing a no-op std::ostream. Tuy nhiên, một luồng như vậy có chi phí thời gian chạy trong bản phát hành bản phát hành.

+0

Tôi đã hy vọng có một số cách giữ lại cú pháp iostream tốt đẹp và cũng tối ưu hóa các câu lệnh trong bản phát hành được xây dựng giống như các macro. – Emanuel

3

Giống như những người khác đã nói cách hiệu quả nhất là sử dụng bộ tiền xử lý. Thông thường tôi tránh bộ tiền xử lý, nhưng đây là về việc sử dụng hợp lệ duy nhất tôi đã tìm thấy cho nó thanh bảo vệ tiêu đề.

Thông thường, tôi muốn có khả năng bật bất kỳ cấp truy tìm nào trong các tệp thực thi bản phát hành cũng như gỡ lỗi thực thi.Các tệp thực thi gỡ lỗi có mức độ theo dõi mặc định cao hơn, nhưng cấp theo dõi có thể được đặt theo tệp cấu hình hoặc theo thời gian chạy động.

Để kết thúc này macro của tôi trông giống như

#define TRACE_ERROR if (Debug::testLevel(Debug::Error)) DebugStream(Debug::Error) 
#define TRACE_INFO if (Debug::testLevel(Debug::Info)) DebugStream(Debug::Info) 
#define TRACE_LOOP if (Debug::testLevel(Debug::Loop)) DebugStream(Debug::Loop) 
#define TRACE_FUNC if (Debug::testLevel(Debug::Func)) DebugStream(Debug::Func) 
#define TRACE_DEBUG if (Debug::testLevel(Debug::Debug)) DebugStream(Debug::Debug) 

Những điều tốt đẹp về việc sử dụng câu lệnh if là không có chi phí để cho truy tìm mà không được đầu ra, mã truy tìm chỉ được gọi nếu nó sẽ được in.

Nếu bạn không muốn một mức nhất định không xuất hiện trong bản phát hành bản phát hành, hãy sử dụng hằng số có sẵn tại thời gian biên dịch trong câu lệnh if.

#ifdef NDEBUG 
    const bool Debug::DebugBuild = false; 
#else 
    const bool Debug::DebugBuild = true; 
#endif 

    #define TRACE_DEBUG if (Debug::DebugBuild && Debug::testLevel(Debug::Debug)) DebugStream(Debug::Debug) 

Điều này giữ nguyên cú pháp iostream, nhưng bây giờ trình biên dịch sẽ tối ưu hóa câu lệnh if ra khỏi mã, trong bản phát hành bản phát hành.

+0

Việc sử dụng câu lệnh if không phải là một ý tưởng tồi! Tôi biết rằng nếu các câu lệnh trong macro có thể có một số cạm bẫy, vì vậy tôi phải đặc biệt cẩn thận khi xây dựng chúng và sử dụng chúng. Ví dụ: nếu (lỗi) TRACE_DEBUG << "lỗi"; else do_something_for_success(); Sẽ kết thúc thực hiện do_something_for_success() nếu xảy ra lỗi và các câu lệnh theo dõi mức gỡ lỗi bị vô hiệu hóa vì câu lệnh khác liên kết với câu lệnh if bên trong. Tuy nhiên, hầu hết các kiểu mã hóa đều sử dụng dấu ngoặc nhọn để giải quyết vấn đề. nếu (lỗi) { TRACE_DEBUG << "lỗi"; } else do_something_for_success(); – Emanuel

+0

Cách an toàn nhất để thực hiện macro là bọc nó bên trong {macro_here} trong khi (0); –

1

@iain: Rời khỏi phòng trong hộp nhận xét để đăng nó ở đây để rõ ràng.

Việc sử dụng câu lệnh if không phải là ý tưởng tồi! Tôi biết rằng nếu các câu lệnh trong macro có thể có một số cạm bẫy, vì vậy tôi phải đặc biệt cẩn thận khi xây dựng chúng và sử dụng chúng. Ví dụ:

if (error) TRACE_DEBUG << "error"; 
else do_something_for_success(); 

... sẽ kết thúc thực hiện do_something_for_success() nếu một lỗi xảy ra và báo cáo debug cấp dấu vết bị vô hiệu hóa bởi vì các báo cáo khác liên kết với khu vực nội nếu-tuyên bố. Tuy nhiên, hầu hết các kiểu mã hóa đều sử dụng dấu ngoặc nhọn để giải quyết vấn đề.

if (error) 
{ 
    TRACE_DEBUG << "error"; 
} 
else 
{ 
    do_something_for_success(); 
} 

Trong đoạn mã này, do_something_for_success() không được thực thi sai nếu truy tìm cấp độ gỡ lỗi bị tắt.

+0

@Emanuel, có bạn phải cẩn thận về điều này, tôi luôn luôn sử dụng dấu ngoặc cho các câu lệnh if của tôi và cho các vòng lặp. Tôi đã nhìn thấy lỗi mà mọi người thêm một dòng mới vào phần sau đó của một tuyên bố nếu nhưng quên thêm các curlies, mã đã được độc đáo thụt vào để lỗi gần như không thể phát hiện. – iain

2
#ifdef RELEASE 
    #define DBOUT(x) 
#else 
    #define DBOUT(x) x 
#endif 

Chỉ cần sử dụng điều này trong chính các nhà khai thác luồng thực tế. Bạn thậm chí có thể viết một toán tử duy nhất cho nó.

template<typename T> Debugstream::operator<<(T&& t) { 
    DBOUT(ostream << std::forward<T>(t);) // where ostream is the internal stream object or type 
} 

Nếu trình biên dịch của bạn không thể tối ưu hóa các chức năng trống trong chế độ phát hành, thì đã đến lúc có trình biên dịch mới.

Tôi đã làm tất nhiên sử dụng tham chiếu rvalue và chuyển tiếp hoàn hảo, và không có đảm bảo rằng bạn có trình biên dịch như vậy. Nhưng, bạn có thể chắc chắn chỉ cần sử dụng một const ref nếu trình biên dịch của bạn chỉ là C++ 03 tuân thủ.

5

Một phương pháp đẹp:

#ifdef _DEBUG 
#define DBOUT cout // or any other ostream 
#else 
#define DBOUT 0 && cout 
#endif 

DBOUT << "This is a debug build." << endl; 
DBOUT << "Some result: " << doSomething() << endl; 

Chừng nào bạn không làm bất cứ điều gì lạ, chức năng gọi và truyền cho DBOUT sẽ không được gọi trong phiên bản xây dựng. Macro này hoạt động vì ưu tiên toán tử và logic AND; bởi vì && có mức ưu tiên thấp hơn <<, hãy xây dựng bản dựng biên dịch DBOUT << "a"0 && (cout << "a"). Logic AND không đánh giá biểu thức ở bên phải nếu biểu thức bên trái đánh giá về 0 hoặc false; bởi vì biểu thức bên tay trái luôn luôn đánh giá bằng không, biểu thức bên phải luôn bị xóa bởi bất kỳ trình biên dịch nào đáng sử dụng trừ khi tất cả tối ưu hóa bị tắt (và thậm chí sau đó, mã rõ ràng không thể truy cập vẫn có thể bị bỏ qua.)


Dưới đây là một ví dụ về những điều kỳ lạ rằng sẽ phá vỡ macro này:

DBOUT << "This is a debug build." << endl, doSomething(); 

Watch dấu phẩy. doSomething() sẽ luôn được gọi, bất kể có hay không _DEBUG được xác định. Điều này là do báo cáo kết quả được đánh giá trong phiên bản xây dựng như:

(0 && (cout << "This is a debug build." << endl)), doSomething(); 
// evaluates further to: 
false, doSomething(); 

Để sử dụng dấu phẩy với macro này, dấu phẩy phải được bọc trong dấu ngoặc đơn, như vậy:

DBOUT << "Value of b: " << (a, b) << endl; 

Một ví dụ khác:

(DBOUT << "Hello, ") << "World" << endl; // Compiler error on release build 

Trong bản xây dựng bản phát hành, điều này được đánh giá là:

(0 && (cout << "Hello, ")) << "World" << endl; 
// evaluates further to: 
false << "World" << endl; 

gây ra lỗi trình biên dịch vì không thể chuyển đổi bool sang trái bởi con trỏ char trừ khi toán tử tùy chỉnh được xác định. Cú pháp này cũng gây ra vấn đề bổ sung:

(DBOUT << "Result: ") << doSomething() << endl; 
// evaluates to: 
false << doSomething() << endl; 

Giống như khi các dấu phẩy được sử dụng kém, doSomething() vẫn được gọi, bởi vì kết quả của nó đã được chuyển tới các nhà điều hành trái ca. (Điều này chỉ có thể xảy ra khi một nhà điều hành tùy chỉnh được định nghĩa rằng trái chuyển một bool bởi một con trỏ char;. Nếu không, một lỗi biên dịch xảy ra)

Đừng nghĩ giải lao DBOUT << .... Nếu bạn muốn ngoặc đơn một số nguyên thay đổi, sau đó ngoặc đơn nó, nhưng tôi không biết một lý do chính đáng để ngoặc đơn một toán tử luồng.

+1

Đây là một ý tưởng hay và nó hoạt động hoàn hảo. – martin

+1

Chính xác những gì tôi tưởng tượng nó có thể trông giống như, và những gì tôi đang tìm kiếm. Cảm ơn rất nhiều ! – user1913596

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