จะโยนข้อยกเว้น std :: ด้วยข้อความตัวแปรได้อย่างไร


122

นี่คือตัวอย่างของสิ่งที่ฉันมักจะทำเมื่อต้องการเพิ่มข้อมูลบางอย่างในข้อยกเว้น:

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

มีวิธีที่ดีกว่าในการทำหรือไม่?


11
ฉันสงสัยว่าคุณจัดการวิธีนี้ได้อย่างไร - std∷exceptionไม่มีตัวสร้างที่มีchar*อาร์กิวเมนต์
Hi-Angel

2
ฉันสงสัยในสิ่งเดียวกัน อาจเป็นส่วนขยาย MS ที่ไม่ได้มาตรฐานไปยัง c ++? หรืออาจจะมีอะไรใหม่ ๆ ใน C ++ 14? เอกสารปัจจุบันระบุว่า std :: exception constructor ไม่ใช้อาร์กิวเมนต์ใด ๆ
Chris Warth

1
ใช่ แต่std::stringมีตัวสร้างโดยนัยซึ่งใช้เวลาconst char*...
Brice M. Dempsey

6
@ Chris Warth มันดูเหมือนจะเป็นส่วนหนึ่งของ MS 'เบื้องหลังฉากการดำเนินการstd::exceptionของเด็กเรียนและจะถูกใช้โดยรุ่นของพวกเขาและstd::runtime_error std::logic_errorนอกเหนือจากที่กำหนดโดยมาตรฐาน, รุ่น MSVS ของ<exception>นอกจากนี้ยังมีอีกสองก่อสร้างซึ่งเป็นหนึ่งในการถ่ายและการซักอื่น(const char * const &)(const char * const &, int)พวกเขากำลังใช้ในการตั้งตัวแปรส่วนตัวconst char * _Mywhat; ถ้า_Mywhat != nullptrแล้วwhat()เริ่มต้นที่มันจะกลับมา รหัสที่ใช้อาจไม่สามารถพกพาได้
Justin Time - คืนสถานะ Monica

คำตอบ:


49

นี่คือทางออกของฉัน:

#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 &);
};

ตัวอย่าง:

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
ฉันกำลังมองหาวิธีการทำสิ่งนี้ แต่อาจจะเปลี่ยนโอเปอเรเตอร์ >> เป็นฟังก์ชันที่ชัดเจนเพื่อป้องกันการโอเวอร์โหลด (ตัวดำเนินการมากเกินไป)
Roman Plášil

3
อะไรคือความแตกต่างระหว่างสิ่งนี้กับ std :: stringstream? ดูเหมือนว่าจะมีสตริงสตรีม แต่มี (เท่าที่ฉันบอกได้) ไม่มีฟังก์ชันเพิ่มเติม
matts1

2
โดยทั่วไปไม่ใช่วิธีที่ปลอดภัย 100% std :: วิธีสตริงสตรีม
Arthur P. Golubev

1
@ ArthurP.Golubev แต่ในกรณีนี้อินสแตนซ์ Formatter () ยังอินสแตนซ์สตรีมสตริงที่อยู่เบื้องหลังซึ่งอาจทำให้เกิดข้อยกเว้นได้อีกครั้ง แล้วความแตกต่างคืออะไร?
Zuzu Corneliu

ฟังก์ชันที่เพิ่มเข้ามาเพียงอย่างเดียวคือเคล็ดลับ ConvertToString และ Explicit cast to string ซึ่งก็ดีอยู่แล้ว ;)
Zuzu Corneliu

180

ข้อยกเว้นมาตรฐานสามารถสร้างได้จาก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);

โปรดทราบว่าชั้นฐานstd::exceptionสามารถไม่ได้ถูกสร้างขึ้นจึง; คุณต้องใช้หนึ่งในคลาสที่ได้รับที่เป็นรูปธรรม


27

มีข้อยกเว้นที่แตกต่างกันเช่นruntime_error, range_error, overflow_error, logic_errorฯลฯ .. คุณต้องผ่านสตริงลงในตัวสร้างของตนและคุณสามารถเชื่อมสิ่งที่คุณต้องการกับข้อความของคุณ นั่นเป็นเพียงการดำเนินการสตริง

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

คุณยังสามารถใช้boost::formatสิ่งนี้:

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

เวอร์ชัน boost :: format ด้านบนจะไม่คอมไพล์โดยไม่มีการแปลงอย่างชัดเจนเช่น: runtime_error ((boost :: format ("Text% 1"% 2) .str ())) C ++ 20 แนะนำรูปแบบ std :: ที่จะให้ฟังก์ชันการทำงานที่คล้ายกัน
Digicrat

17

ชั้นเรียนต่อไปนี้อาจมีประโยชน์มาก:

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; }
};

ตัวอย่างการใช้งาน:

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

4
IMO ปฏิบัติไม่ดีทำไมต้องใช้อะไรแบบนี้ในเมื่อมีไลบรารีมาตรฐานที่สร้างขึ้นเพื่อการเพิ่มประสิทธิภาพ
Jean-Marie Comets

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

4
throw std::runtime_error("Could not load config file " + configfile); (การแปลงอาร์กิวเมนต์หนึ่งหรืออาร์กิวเมนต์อื่นเป็น std::stringถ้าจำเป็น)
Mike Seymour

9
@MikeSeymour ใช่ แต่จะน่าเกลียดกว่านี้หากคุณต้องวางสตริงไว้ตรงกลางและจัดรูปแบบตัวเลขด้วยความแม่นยำที่แน่นอน ฯลฯ มันยากที่จะเอาชนะสตริงรูปแบบเก่าที่ดีในแง่ของความชัดเจน
Maxim Egorushkin

2
@MikeSeymour ฉันยอมรับว่ารหัสที่ฉันโพสต์อาจจะก่อนเวลา แบบพกพาที่ปลอดภัยprintfและเพื่อน ๆ ใกล้เข้ามาใน C ++ 11 บัฟเฟอร์ขนาดคงที่เป็นทั้งพรและคำสาป: ไม่ล้มเหลวในสถานการณ์ที่ทรัพยากรเหลือน้อย แต่อาจตัดทอนข้อความ ฉันคิดว่าการตัดข้อความแสดงข้อผิดพลาดเป็นตัวเลือกที่ดีกว่าแล้วก็ล้มเหลว นอกจากนี้ความสะดวกในการจัดรูปแบบสตริงได้รับการพิสูจน์แล้วด้วยภาษาต่างๆมากมาย แต่คุณพูดถูกมันเป็นเรื่องของรสนิยมเป็นส่วนใหญ่
Maxim Egorushkin

11

ใช้ตัวดำเนินการสตริงลิเทอรัลถ้า C ++ 14 ( operator ""s)

using namespace std::string_literals;

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

หรือกำหนดของคุณเองถ้าอยู่ใน C ++ 11 ตัวอย่างเช่น

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

จากนั้นคำสั่งโยนของคุณจะมีลักษณะเช่นนี้

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

ซึ่งดูดีและสะอาด


2
ฉันได้รับข้อผิดพลาดนี้c ++ \ 7.3.0 \ bits \ except.h | 63 | หมายเหตุ: ไม่มีฟังก์ชันที่ตรงกันสำหรับการเรียกไปที่ 'std :: exception :: exception (std :: __ cxx11 :: basic_string <char>)
HaseeB Mir

พฤติกรรมที่อธิบายโดย @Shreevardhan ไม่ได้กำหนดไว้ในไลบรารี std แม้ว่า MSVC ++ จะคอมไพล์ก็ตาม
jochen

0

วิธีที่ดีกว่าจริงๆคือการสร้างคลาส (หรือคลาส) สำหรับข้อยกเว้น

สิ่งที่ต้องการ:

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

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

เหตุผลก็คือข้อยกเว้นนั้นดีกว่าการถ่ายโอนสตริง การให้คลาสที่แตกต่างกันสำหรับข้อผิดพลาดคุณให้โอกาสนักพัฒนาในการจัดการกับข้อผิดพลาดเฉพาะในลักษณะที่สอดคล้องกัน (ไม่ใช่แค่แสดงข้อความแสดงข้อผิดพลาด) คนที่จับข้อยกเว้นของคุณสามารถเจาะจงได้ตามที่ต้องการหากคุณใช้ลำดับชั้น

ก) อาจจำเป็นต้องทราบเหตุผลที่เฉพาะเจาะจง

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

ก) อีกคนไม่ต้องการทราบรายละเอียด

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

คุณสามารถค้นหาแรงบันดาลใจเกี่ยวกับหัวข้อนี้ได้ในhttps://books.google.ru/books?id=6tjfmnKhT24Cบทที่ 9

นอกจากนี้คุณสามารถระบุข้อความที่กำหนดเองได้เช่นกัน แต่โปรดระวัง - การเขียนข้อความด้วยวิธีใดวิธีหนึ่งstd::stringหรือstd::stringstreamวิธีอื่นใดที่อาจทำให้เกิดข้อยกเว้นไม่ปลอดภัยไม่ปลอดภัย

โดยทั่วไปไม่มีความแตกต่างไม่ว่าคุณจะจัดสรรหน่วยความจำ (ทำงานกับสตริงในลักษณะ C ++) ในตัวสร้างข้อยกเว้นหรือก่อนโยน - std::bad_allocข้อยกเว้นสามารถโยนก่อนที่คุณต้องการจริงๆ

ดังนั้นบัฟเฟอร์ที่จัดสรรบนสแต็ก (เช่นในคำตอบของ Maxim) จึงเป็นวิธีที่ปลอดภัยกว่า

มีการอธิบายไว้เป็นอย่างดีที่http://www.boost.org/community/error_handling.html

ดังนั้นวิธีที่ดีกว่าจะเป็นประเภทเฉพาะของข้อยกเว้นและหลีกเลี่ยงการเขียนสตริงที่จัดรูปแบบ (อย่างน้อยเมื่อขว้างปา)


0

พบปัญหาที่คล้ายกันในการสร้างข้อความแสดงข้อผิดพลาดที่กำหนดเองสำหรับข้อยกเว้นที่กำหนดเองของฉันทำให้โค้ดน่าเกลียด นี่คือทางออกของฉัน:

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
     }
}

สิ่งนี้จะแยกตรรกะในการสร้างข้อความ เดิมทีฉันเคยคิดที่จะลบล้างสิ่งที่ () แต่คุณต้องจับข้อความของคุณที่ไหนสักแห่ง std :: runtime_error มีบัฟเฟอร์ภายในอยู่แล้ว


0

อาจจะเป็นแบบนี้?

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

สร้าง ostringstream ชั่วคราวเรียกตัวดำเนินการ << ตามความจำเป็นจากนั้นคุณห่อสิ่งนั้นในวงเล็บกลมและเรียกใช้ฟังก์ชัน. str () บนผลลัพธ์ที่ประเมิน (ซึ่งเป็น ostringstream) เพื่อส่งสตริง std :: ชั่วคราวไปยังตัวสร้าง ของ runtime_error

หมายเหตุ: ostringstream และสตริงเป็นค่า r-value tempo ไลบรารีดังนั้นจึงควรออกนอกขอบเขตหลังจากสิ้นสุดบรรทัดนี้ ตัวสร้างออบเจ็กต์ข้อยกเว้นของคุณต้องใช้สตริงอินพุตโดยใช้สำเนาหรือ (ดีกว่า) ย้ายความหมาย

เพิ่มเติม: ฉันไม่จำเป็นต้องคิดว่าแนวทางนี้เป็น "แนวทางปฏิบัติที่ดีที่สุด" แต่ก็ใช้ได้ผลและสามารถใช้ได้ในระยะสั้น ๆ ปัญหาที่ใหญ่ที่สุดอย่างหนึ่งคือวิธีนี้ต้องมีการจัดสรรฮีปดังนั้นตัวดำเนินการ << จึงสามารถโยนได้ คุณอาจไม่ต้องการให้เกิดขึ้น อย่างไรก็ตามหากคุณเข้าสู่สถานะนั้นคุณอาจมีปัญหาที่ต้องกังวลมากขึ้น!

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.