2009-07-10 57 views
5

Xét đoạn mã sau:C++ chức năng cuộc gọi nhận dạng

void Foo() { 
    ...... 
    LOG_ERROR("I'm error 1") // call 1 
    ..... 
    LOG_ERROR("I'm error 2") // call 2 
    ..... 

} 

LOG_ERROR() là một macro. LOG_ERROR() nên in chuỗi xác định mã trong mã, trong khi giả định là mã có thể thay đổi, nhưng A::Foo() sẽ không thay đổi. Mã nhận dạng sẽ giữ lại trong khi mã số thay đổi.

Điều này có thể được giải quyết bằng cách thêm mã lỗi như là đối số để LOG_ERROR(), nhưng chúng tôi muốn loại bỏ từ các lập trình viên gánh nặng quản lý lỗi mã.

Sử dụng __LINE__ không phải là câu trả lời, vì Foo() có thể di chuyển từ bản dựng đến .

Vì vậy, tôi nghĩ về việc xác định LOG_ERROR() tương đối để bắt đầu của Foo():

  • a. Xác định theo tên tệp (__FILE__) + tên hàm (__FUNCTION__) + số dòng LOG_ERROR() liên quan đến Foo() bắt đầu.
  • b. Xác định theo tên tệp (__FILE__) + tên hàm (__FUNCTION__) + LOG_ERROR() số điện thoại trong Foo().

Giải pháp nên hoạt động với VC++ 2008 và g ++ 4.1.1 ít nhất.

Một giải pháp đề xuất (link text) là:

#define ENABLE_LOG_ERROR static const int LOG_ERROR_start_line = __LINE__ 
#define LOG_ERROR(s) cerr << "error #" << (__LINE__ - LOG_ERROR_start_line) \ 
    << " in " << __func__ << ": " << s << endl 

void Foo() { 
    ENABLE_LOG_ERROR; 
    //... 
    LOG_ERROR("error 1"); 
    int i; 
    LOG_ERROR("error 2"); 
} 

Điều này sẽ buộc người sử dụng để viết ENABLE_LOG_ERROR trong đầu mỗi hàm chứa LOG_ERROR() và cũng có khá nhiều chức năng như vậy.

Có cách nào khác để hoàn thành tác vụ không?

+0

@idimba, nếu không quá nhiều rắc rối, bạn có thể sử dụng tập lệnh để tự động thêm 'ENABLE_LOG_ERROR;' ngay sau mỗi tên hàm. Tôi sử dụng mẹo đó khi tôi * khám phá * mã của bên thứ ba. –

+0

Ý tưởng hay, nhưng nó sẽ ảnh hưởng đến __LINE__ mà chúng tôi sử dụng rộng rãi trong khai thác phụ khai thác gỗ của chúng tôi. – dimba

Trả lời

0

Bằng cách sửa đổi ý tưởng ngăn xếp, hãy sử dụng ánh xạ std::map từ số std::string đến số đếm và tra cứu tên hàm.

std::map<std::string, int> LOG_ERROR_count_map; 
#define LOG_ERROR(s) {\ 
    int count = ++LOG_ERROR_count_map[ __func__ ];\ 
    std::cout << count << " in " __func__ ": " s << std::endl;\ 
} 

Điều này có nghĩa là bạn không cần ENABLE_LOG_ERROR, nhưng với chi phí bản đồ tra cứu cho mỗi nhật ký. (Đó là một sự đánh đổi giữa tính dễ sử dụng và thời gian.)

+0

Cả giải pháp của tôi và GMan đều không cung cấp thể hiện thứ of của LOG_ERROR trong hàm, nhưng gọi hàm thứ ba của LOG_ERROR trong hàm. Vì vậy, chúng không thể được sử dụng để chỉ đơn giản là đếm ngược sử dụng (có lẽ là mục đích sử dụng) khi phân nhánh xảy ra. Bất kỳ hình thức nào của phương pháp tính động sẽ không hoạt động. –

+0

Có thể hiển nhiên là bạn không thể sử dụng bộ tiền xử lý cho số đếm tĩnh dựa trên tên hàm vì không có cách nào để phát hiện khi hàm đã thay đổi - không thể so sánh chuỗi. –

1

Giải pháp này là phi tiêu chuẩn, nhưng cả hai MSVC và hỗ trợ GCC __COUNTER__, được tăng lên mỗi khi nó được gọi.

#define LOG_ERROR(s) cerr << "error #" << (__COUNTER__) << " in " \ 
<< __func__ << ": " << s << endl 

Lưu ý rằng __COUNTER__ sẽ được đặt lại trong mỗi đơn vị biên soạn và CHỈ trong mỗi đơn vị biên soạn. Vì vậy, nếu Foo() có 7 LOG_ERROR() macro, ở hàm sau Bar() giá trị của __COUNTER__ sẽ là 7 cho lần sử dụng đầu tiên là LOG_ERROR().

+0

Tuy nhiên, tính năng này sẽ không hoạt động giữa các chức năng. – GManNickG

+0

Vấn đề thực tế là nếu bạn chèn một tin nhắn mới, những cái dưới đây tất cả đều được đổi số. –

+0

@GMan: Bạn chính xác rằng nó sẽ không đặt lại cho mỗi chức năng. Tôi đã chỉnh sửa để làm rõ điều đó. @Earwicker: Cũng chính xác. Đây là một giải pháp khá giòn. Nó giả định rằng 'LOG_ERROR()' s chỉ được thêm vào sau tất cả các lời gọi trước đó trong cùng một đơn vị biên dịch. – Geerad

1

Trong khi câu hỏi là về cách tạo mã định danh duy nhất trong một hàm cho mục đích ghi nhật ký, tôi sẽ lùi lại một bước và xem xét vấn đề thực tế cần giải quyết: Cách tạo đầu ra nhật ký có thể dễ dàng xác định dòng mã nguồn mà không đặt gánh nặng lên người viết mã.

Giả sử bạn đang nhúng phiên bản xây dựng duy nhất vào mỗi bản phát hành chương trình của bạn (đó là một ý tưởng hay nói chung). Giả sử bạn đang sử dụng cơ chế kiểm soát mã nguồn giữ lịch sử mã nguồn của bạn (cũng là một ý tưởng rất hay để làm) và có thể trình bày cho bạn nguồn như nó dành cho bất kỳ phiên bản xây dựng được yêu cầu nào của chương trình.

Nếu những giả định đó đúng, thì giải pháp là để chương trình của bạn ghi phiên bản hiện tại của nó vào tệp nhật ký. Sau đó, mỗi mục nhập nhật ký riêng lẻ có thể chỉ ghi lại số dòng qua __LINE__. Do đó, khi ai đó cần sử dụng nhật ký: họ có thể xem số phiên bản trong nhật ký, lấy nguồn tương ứng từ kho kiểm soát mã nguồn và sử dụng số dòng từ nhật ký để chuyển đến nguồn thích hợp dòng. Điều này đặt một gánh nặng nhiều hơn vào người sử dụng đầu ra bản ghi. Tuy nhiên, nếu mã đăng nhập phụ thuộc vào hoặc bị ảnh hưởng bởi mã khác có thể thay đổi từ phiên bản sang phiên bản thì trạng thái lịch sử của mã nguồn có thể được yêu cầu.

Hơn nữa, lợi ích của việc làm theo cách này là nó loại bỏ để cần giả định rằng bất kỳ hàm đã cho sẽ không thay đổi, như ban đầu là một phần của câu hỏi. Vì vậy, phương pháp này có một ứng dụng rộng hơn nhiều.


Theo như triển khai, bạn có thể đăng nhập phiên bản chương trình khi chương trình khởi động hoặc bạn có thể tạo macro ghi nhật ký bao gồm trong mỗi mục nhập.

Nếu phiên bản chương trình thường được lưu trữ ở nơi nào đó không dễ dàng truy cập trong mã nguồn bình thường, bạn có thể tạo bước tạo trước sẽ trích xuất phiên bản và viết nó thành tệp version.h đơn giản dưới dạng #define hoặc const chuỗi. Sau đó, mã đăng nhập hoặc macro có thể tự động sử dụng để luôn xuất phiên bản hiện tại của chương trình.

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