Làm thế nào để ném std :: exceptions với các thông báo biến?


121

Đây là một ví dụ về những gì tôi thường làm khi muốn thêm một số thông tin vào một ngoại lệ:

std::stringstream errMsg;
errMsg << "Could not load config file '" << configfile << "'";
throw std::exception(errMsg.str().c_str());

Có cách nào tốt hơn để làm điều đó không?


10
Tôi đang tự hỏi làm thế nào bạn thậm chí đã quản lý để làm việc theo cách này - std∷exceptionkhông có hàm tạo nào với char*arg.
Hi-Angel

2
Tôi đang tự hỏi điều tương tự. Có thể nó là một phần mở rộng MS không chuẩn cho c ++? Hoặc có thể một cái gì đó mới trong C ++ 14? Tài liệu hiện tại cho biết hàm tạo ngoại lệ std :: không nhận bất kỳ đối số nào.
Chris Warth

1
Vâng, nhưng std::stringcó một constructor ngầm mà phải mất một const char*...
Brice M. Dempsey

6
@Chris Warth Nó dường như là một phần trong quá trình triển khai hậu trường của MS đối với các std::exceptionlớp con của MS và được sử dụng bởi các phiên bản của std::runtime_errorstd::logic_error. Ngoài những cái được xác định bởi tiêu chuẩn, phiên bản của MSVS <exception>còn bao gồm hai hàm tạo nữa, một hàm lấy (const char * const &)và một hàm khác (const char * const &, int). Chúng được sử dụng để đặt một biến riêng tư , const char * _Mywhat; nếu _Mywhat != nullptr, thì what()mặc định trả lại nó. Mã dựa vào nó có thể không di động.
Justin Time - Phục hồi Monica

Câu trả lời:


49

Đây là giải pháp của tôi:

#include <stdexcept>
#include <sstream>

class Formatter
{
public:
    Formatter() {}
    ~Formatter() {}

    template <typename Type>
    Formatter & operator << (const Type & value)
    {
        stream_ << value;
        return *this;
    }

    std::string str() const         { return stream_.str(); }
    operator std::string () const   { return stream_.str(); }

    enum ConvertToString 
    {
        to_str
    };
    std::string operator >> (ConvertToString) { return stream_.str(); }

private:
    std::stringstream stream_;

    Formatter(const Formatter &);
    Formatter & operator = (Formatter &);
};

Thí dụ:

throw std::runtime_error(Formatter() << foo << 13 << ", bar" << myData);   // implicitly cast to std::string
throw std::runtime_error(Formatter() << foo << 13 << ", bar" << myData >> Formatter::to_str);    // explicitly cast to std::string

1
omg Tôi đang tìm cách làm một việc như thế này. Nhưng có lẽ sẽ thay đổi điều hành >> để chức năng rõ ràng để ngăn chặn quá mức (điều hành quá tải)
La Mã Plasil

3
sự khác biệt giữa cái này và std :: stringstream là gì? Nó dường như chứa một chuỗi chuỗi, nhưng có (theo như tôi có thể nói), không có chức năng bổ sung.
matts1

2
Nói chung, nó không phải là cách an toàn 100%. phương pháp std :: stringstream có thể ném một vấn đề exception.The khá tốt mô tả ở đây: boost.org/community/error_handling.html
Arthur P. Golubev

1
@ ArthurP.Golubev Nhưng trong trường hợp này, một cá thể Formatter () cũng khởi tạo một chuỗi chuỗi đằng sau hậu trường, một lần nữa, có thể tạo ra một ngoại lệ. Vậy sự khác biệt là gì?
Zuzu Corneliu,

Chức năng được bổ sung duy nhất là thủ thuật ConvertToString và chuyển thành chuỗi rõ ràng, dù sao thì cũng rất hay. ;)
Zuzu Corneliu

178

Các ngoại lệ tiêu chuẩn có thể được xây dựng từ std::string:

#include <stdexcept>

char const * configfile = "hardcode.cfg";
std::string const anotherfile = get_file();

throw std::runtime_error(std::string("Failed: ") + configfile);
throw std::runtime_error("Error: " + anotherfile);

Lưu ý rằng các lớp cơ sở std::exceptioncó thể không được xây dựng như vậy; bạn phải sử dụng một trong các lớp dẫn xuất cụ thể.


27

Có những ngoại lệ khác nhau như runtime_error, range_error, overflow_error, logic_error, vv .. Bạn cần phải vượt qua chuỗi thành constructor của nó, và bạn có thể nối bất cứ điều gì bạn muốn thông điệp của bạn. Đó chỉ là một hoạt động chuỗi.

std::string errorMessage = std::string("Error: on file ")+fileName;
throw std::runtime_error(errorMessage);

Bạn cũng có thể sử dụng boost::formatnhư thế này:

throw std::runtime_error(boost::format("Error processing file %1") % fileName);

Phiên bản định dạng boost :: ở trên sẽ không biên dịch mà không có chuyển đổi rõ ràng, tức là: runtime_error ((boost :: format ("Text% 1"% 2) .str ())). C ++ 20 giới thiệu một định dạng std :: sẽ cung cấp chức năng tương tự.
Digicrat

17

Lớp sau có thể khá hữu ích:

struct Error : std::exception
{
    char text[1000];

    Error(char const* fmt, ...) __attribute__((format(printf,2,3))) {
        va_list ap;
        va_start(ap, fmt);
        vsnprintf(text, sizeof text, fmt, ap);
        va_end(ap);
    }

    char const* what() const throw() { return text; }
};

Ví dụ sử dụng:

throw Error("Could not load config file '%s'", configfile.c_str());

4
IMO thực tế không tốt, tại sao lại sử dụng thứ như thế này khi đã có một thư viện chuẩn được xây dựng để tối ưu hóa?
Sao chổi Jean-Marie

3
throw std::runtime_error(sprintf("Could not load config file '%s'", configfile.c_str()))
Sao chổi Jean-Marie

4
throw std::runtime_error("Could not load config file " + configfile);(chuyển đổi một hoặc đối số khác thành std::stringnếu cần).
Mike Seymour

9
@MikeSeymour Có, nhưng điều đó sẽ xấu hơn nếu bạn cần đặt chuỗi ở giữa và định dạng số với độ chính xác nhất định, v.v. Thật khó để đánh bại một chuỗi định dạng cũ tốt về độ rõ ràng.
Maxim Egorushkin

2
@MikeSeymour Tôi có thể đồng ý rằng mã tôi đã đăng có thể đi trước thời hạn. An toàn kiểu dễ dàng printfvà bạn bè sắp ra mắt trong C ++ 11. Bộ đệm kích thước cố định vừa là một điều may mắn vừa là một lời nguyền: nó không thất bại trong các tình huống tài nguyên thấp nhưng có thể cắt bớt thông báo. Tôi coi việc cắt bớt thông báo lỗi là một lựa chọn tốt hơn nhưng không thành công. Ngoài ra, tính tiện lợi của chuỗi định dạng đã được chứng minh bởi nhiều ngôn ngữ khác nhau. Nhưng bạn nói đúng, nó chủ yếu là vấn đề của hương vị.
Maxim Egorushkin

11

Sử dụng toán tử chuỗi ký tự nếu C ++ 14 ( operator ""s)

using namespace std::string_literals;

throw std::exception("Could not load config file '"s + configfile + "'"s);

hoặc xác định của riêng bạn nếu trong C ++ 11. Ví dụ

std::string operator ""_s(const char * str, std::size_t len) {
    return std::string(str, str + len);
}

Câu lệnh ném của bạn sau đó sẽ trông như thế này

throw std::exception("Could not load config file '"_s + configfile + "'"_s);

trông đẹp và sạch sẽ.


2
Tôi gặp lỗi này c ++ \ 7.3.0 \ bits \ exception.h | 63 | lưu ý: không có hàm phù hợp nào để gọi đến 'std :: exception :: exception (std :: __ cxx11 :: basic_string <char>)
HaseeB Mir

Hành vi được mô tả bởi @Shreevardhan không được định nghĩa trong thư viện std, mặc dù MSVC ++ sẽ biên dịch nó.
jochen

0

Một cách thực sự tốt hơn sẽ là tạo một lớp (hoặc các lớp) cho các trường hợp ngoại lệ.

Cái gì đó như:

class ConfigurationError : public std::exception {
public:
    ConfigurationError();
};

class ConfigurationLoadError : public ConfigurationError {
public:
    ConfigurationLoadError(std::string & filename);
};

Lý do là các ngoại lệ thích hợp hơn nhiều so với việc chỉ chuyển một chuỗi. Cung cấp các lớp khác nhau cho các lỗi, bạn cho nhà phát triển cơ hội để xử lý một lỗi cụ thể theo cách tương ứng (không chỉ hiển thị thông báo lỗi). Những người bắt được ngoại lệ của bạn có thể cụ thể như họ cần nếu bạn sử dụng hệ thống phân cấp.

a) Người ta có thể cần biết lý do cụ thể

} catch (const ConfigurationLoadError & ex) {
// ...
} catch (const ConfigurationError & ex) {

a) người khác không muốn biết chi tiết

} catch (const std::exception & ex) {

Bạn có thể tìm thấy một số cảm hứng về chủ đề này trong https://books.google.ru/books?id=6tjfmnKhT24C Chương 9

Ngoài ra, bạn có thể cung cấp một thông báo tùy chỉnh quá, nhưng phải cẩn thận - nó không phải là an toàn để soạn một tin nhắn với một trong hai std::stringhoặc std::stringstreamhoặc bất kỳ cách nào khác có thể gây ra một ngoại lệ .

Nói chung, không có sự khác biệt cho dù bạn cấp phát bộ nhớ (làm việc với chuỗi theo cách C ++) trong phương thức khởi tạo của ngoại lệ hay ngay trước khi ném - std::bad_allocngoại lệ có thể được ném trước ngoại lệ mà bạn thực sự muốn.

Vì vậy, một bộ đệm được phân bổ trên ngăn xếp (như trong câu trả lời của Maxim) là một cách an toàn hơn.

Nó được giải thích rất tốt tại http://www.boost.org/community/error_handling.html

Vì vậy, cách tốt hơn sẽ là một loại ngoại lệ cụ thể và tránh soạn chuỗi được định dạng (ít nhất là khi ném).


0

Gặp phải một vấn đề tương tự, trong đó việc tạo thông báo lỗi tùy chỉnh cho các ngoại lệ tùy chỉnh của tôi làm cho mã xấu xí. Đây là giải pháp của tôi:

class MyRunTimeException: public std::runtime_error
{
public:
      MyRunTimeException(const std::string &filename):std::runtime_error(GetMessage(filename)) {}
private:
      static std::string GetMessage(const std::string &filename)
     {
           // Do your message formatting here. 
           // The benefit of returning std::string, is that the compiler will make sure the buffer is good for the length of the constructor call
           // You can use a local std::ostringstream here, and return os.str()
           // Without worrying that the memory is out of scope. It'll get copied
           // You also can create multiple GetMessage functions that take all sorts of objects and add multiple constructors for your exception
     }
}

Điều này tách biệt logic để tạo ra các thông báo. Ban đầu tôi đã nghĩ về việc ghi đè cái gì (), nhưng sau đó bạn phải ghi lại thông điệp của mình ở đâu đó. std :: runtime_error đã có bộ đệm bên trong.


0

Có lẽ điều này?

throw std::runtime_error(
    (std::ostringstream()
        << "Could not load config file '"
        << configfile
        << "'"
    ).str()
);

Nó tạo ra một dòng ostringstream tạm thời, gọi các toán tử << khi cần thiết và sau đó bạn đặt nó trong dấu ngoặc tròn và gọi hàm .str () trên kết quả được đánh giá (là một dòng ostringstream) để chuyển một chuỗi std :: tạm thời đến hàm tạo của runtime_error.

Lưu ý: dòng ostringstream và chuỗi là thời gian tạm thời của giá trị r và do đó sẽ vượt ra khỏi phạm vi sau khi dòng này kết thúc. Phương thức khởi tạo của đối tượng ngoại lệ PHẢI lấy chuỗi đầu vào bằng cách sử dụng ngữ nghĩa sao chép hoặc di chuyển (tốt hơn).

Bổ sung: Tôi không nhất thiết phải coi cách tiếp cận này là "phương pháp hay nhất", nhưng nó có hiệu quả và có thể được sử dụng một cách hiệu quả. Một trong những vấn đề lớn nhất là phương thức này yêu cầu phân bổ heap và do đó toán tử << có thể ném. Bạn có thể không muốn điều đó xảy ra; tuy nhiên, nếu bạn rơi vào trạng thái đó, bạn có thể có nhiều vấn đề hơn để lo lắng!

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.