खोज…
अपवादों को पकड़ना
अपवादों को पकड़ने के लिए एक try/catch
ब्लॉक का उपयोग किया जाता है। try
अनुभाग में कोड वह कोड है जो अपवाद को फेंक सकता है, और catch
क्लॉज़ (एस) में कोड अपवाद को संभालता है।
#include <iostream>
#include <string>
#include <stdexcept>
int main() {
std::string str("foo");
try {
str.at(10); // access element, may throw std::out_of_range
} catch (const std::out_of_range& e) {
// what() is inherited from std::exception and contains an explanatory message
std::cout << e.what();
}
}
एकाधिक अपवाद प्रकारों को संभालने के लिए एकाधिक catch
क्लॉज़ का उपयोग किया जा सकता है। यदि कई catch
क्लॉज़ मौजूद हैं, तो कोड में उनकी उपस्थिति के क्रम में अपवाद हैंडलिंग तंत्र उन्हें मैच करने की कोशिश करता है:
std::string str("foo");
try {
str.reserve(2); // reserve extra capacity, may throw std::length_error
str.at(10); // access element, may throw std::out_of_range
} catch (const std::length_error& e) {
std::cout << e.what();
} catch (const std::out_of_range& e) {
std::cout << e.what();
}
अपवाद वर्ग जो एक सामान्य आधार वर्ग से प्राप्त होते हैं, उन्हें सामान्य आधार वर्ग के लिए एकल catch
क्लॉज के साथ पकड़ा जा सकता है। उपरोक्त उदाहरण std::length_error
और std::out_of_range
के लिए एक क्लॉज के साथ दो catch
क्लाज को प्रतिस्थापित कर सकता है std:exception
:
std::string str("foo");
try {
str.reserve(2); // reserve extra capacity, may throw std::length_error
str.at(10); // access element, may throw std::out_of_range
} catch (const std::exception& e) {
std::cout << e.what();
}
चूँकि catch
क्लॉज़ को क्रम में रखने की कोशिश की जाती है, इसलिए पहले से अधिक विशिष्ट कैच क्लॉज़ लिखना सुनिश्चित करें, अन्यथा आपके अपवाद हैंडलिंग कोड को शायद मिल सकता है:
try {
/* Code throwing exceptions omitted. */
} catch (const std::exception& e) {
/* Handle all exceptions of type std::exception. */
} catch (const std::runtime_error& e) {
/* This block of code will never execute, because std::runtime_error inherits
from std::exception, and all exceptions of type std::exception were already
caught by the previous catch clause. */
}
एक और संभावना कैच-ऑल हैंडलर है, जो किसी भी फेंकी हुई वस्तु को पकड़ लेगा:
try {
throw 10;
} catch (...) {
std::cout << "caught an exception";
}
रेथ्रो (प्रचार) अपवाद
कभी-कभी आप अपने द्वारा पकड़े गए अपवाद के साथ कुछ करना चाहते हैं (जैसे कि लॉग करने के लिए लिखना या चेतावनी को प्रिंट करना) और इसे ऊपरी दायरे तक संभाले रखने की अनुमति दें। ऐसा करने के लिए, आप अपने द्वारा पकड़े गए किसी भी अपवाद को वापस ला सकते हैं:
try {
... // some code here
} catch (const SomeException& e) {
std::cout << "caught an exception";
throw;
}
throw;
का उपयोग करना throw;
तर्कों के बिना वर्तमान में पकड़े गए अपवाद को फिर से फेंक देंगे।
किसी प्रबंधित std::exception_ptr
rethrow_exception
को rethrow_exception
लिए, C ++ मानक लाइब्रेरी में rethrow_exception
फ़ंक्शन है जिसका उपयोग आपके प्रोग्राम में <exception>
शीर्षलेख को शामिल करके किया जा सकता है।
#include <iostream>
#include <string>
#include <exception>
#include <stdexcept>
void handle_eptr(std::exception_ptr eptr) // passing by value is ok
{
try {
if (eptr) {
std::rethrow_exception(eptr);
}
} catch(const std::exception& e) {
std::cout << "Caught exception \"" << e.what() << "\"\n";
}
}
int main()
{
std::exception_ptr eptr;
try {
std::string().at(1); // this generates an std::out_of_range
} catch(...) {
eptr = std::current_exception(); // capture
}
handle_eptr(eptr);
} // destructor for std::out_of_range called here, when the eptr is destructed
रचनाकार में ब्लॉक की कोशिश करें
शुरुआती सूची में अपवाद को पकड़ने का एकमात्र तरीका:
struct A : public B
{
A() try : B(), foo(1), bar(2)
{
// constructor body
}
catch (...)
{
// exceptions from the initializer list and constructor are caught here
// if no exception is thrown here
// then the caught exception is re-thrown.
}
private:
Foo foo;
Bar bar;
};
नियमित कार्य के लिए ब्लॉक का प्रयास करें
void function_with_try_block()
try
{
// try block body
}
catch (...)
{
// catch block body
}
के बराबर है
void function_with_try_block()
{
try
{
// try block body
}
catch (...)
{
// catch block body
}
}
ध्यान दें कि कंस्ट्रक्टर्स और डिस्ट्रक्टर्स के लिए, व्यवहार अलग-अलग होता है क्योंकि कैच ब्लॉक री-थ्रो फिर भी एक अपवाद है (कैच ब्लॉक बॉडी में कोई अन्य थ्रो नहीं होने पर पकड़ा गया)।
फ़ंक्शन main
को किसी अन्य फ़ंक्शन की तरह एक फ़ंक्शन ट्रायल ब्लॉक करने की अनुमति है, लेकिन main
फ़ंक्शन फ़ंक्शन ब्लॉक एक गैर-स्थानीय स्थैतिक चर के निर्माण या किसी भी स्थिर चर के विनाश के दौरान होने वाले अपवादों को नहीं पकड़ेगा। इसके बजाय, std::terminate
को कहा जाता है।
विनाश में फंक्शन की कोशिश करें
struct A
{
~A() noexcept(false) try
{
// destructor body
}
catch (...)
{
// exceptions of destructor body are caught here
// if no exception is thrown here
// then the caught exception is re-thrown.
}
};
ध्यान दें, हालांकि यह संभव है, किसी को विध्वंसक से फेंकने के साथ बहुत सावधानी बरतने की आवश्यकता होती है, जैसे कि स्टैक अनइंडिंग के दौरान कहा जाने वाला एक विध्वंसक एक अपवाद फेंकता है, std::terminate
को कहा जाता है।
सबसे अच्छा अभ्यास: मूल्य से फेंक, कब्ज संदर्भ द्वारा पकड़
सामान्य तौर पर, इसे मूल्य (सूचक द्वारा) के बजाय फेंकने के लिए अच्छा अभ्यास माना जाता है, लेकिन (कॉन्स्ट) संदर्भ द्वारा पकड़ते हैं।
try {
// throw new std::runtime_error("Error!"); // Don't do this!
// This creates an exception object
// on the heap and would require you to catch the
// pointer and manage the memory yourself. This can
// cause memory leaks!
throw std::runtime_error("Error!");
} catch (const std::runtime_error& e) {
std::cout << e.what() << std::endl;
}
संदर्भ द्वारा पकड़ने का एक कारण यह है कि यह एक अच्छा अभ्यास है कि यह कैच ब्लॉक (या जब अन्य कैच ब्लॉक्स के माध्यम से प्रचारित हो रहा हो) को पास होने पर ऑब्जेक्ट को फिर से संगठित करने की आवश्यकता को समाप्त कर देता है। संदर्भ द्वारा पकड़ने से भी अपवाद पॉलीमॉर्फिक रूप से नियंत्रित किया जा सकता है और ऑब्जेक्ट स्लाइसिंग से बचा जाता है। हालाँकि, यदि आप एक अपवाद को हटा रहे हैं (जैसे throw e;
, नीचे उदाहरण देखें), तो आप अभी भी ऑब्जेक्ट स्लिंग प्राप्त कर सकते हैं क्योंकि throw e;
स्टेटमेंट अपवाद की एक प्रति बनाता है जो कुछ भी घोषित किया जाता है:
#include <iostream>
struct BaseException {
virtual const char* what() const { return "BaseException"; }
};
struct DerivedException : BaseException {
// "virtual" keyword is optional here
virtual const char* what() const { return "DerivedException"; }
};
int main(int argc, char** argv) {
try {
try {
throw DerivedException();
} catch (const BaseException& e) {
std::cout << "First catch block: " << e.what() << std::endl;
// Output ==> First catch block: DerivedException
throw e; // This changes the exception to BaseException
// instead of the original DerivedException!
}
} catch (const BaseException& e) {
std::cout << "Second catch block: " << e.what() << std::endl;
// Output ==> Second catch block: BaseException
}
return 0;
}
यदि आप सुनिश्चित हैं कि आप अपवाद को बदलने के लिए कुछ भी नहीं करने जा रहे हैं (जैसे जानकारी जोड़ें या संदेश को संशोधित करें), तो कॉन्स्ट रेफरेंस द्वारा पकड़ने से कंपाइलर को अनुकूलन करने की अनुमति मिलती है और प्रदर्शन में सुधार हो सकता है। लेकिन यह अभी भी ऑब्जेक्ट स्प्लिसिंग का कारण बन सकता है (जैसा कि ऊपर उदाहरण में देखा गया है)।
चेतावनी: catch
ब्लॉकों में अनपेक्षित अपवादों को फेंकने से सावधान रहें, विशेष रूप से अतिरिक्त मेमोरी या संसाधनों को आवंटित करने से संबंधित। उदाहरण के लिए, निर्माण logic_error
, runtime_error
या उनके उपवर्गों फेंक सकती है bad_alloc
निकलता जा रहा है जब अपवाद स्ट्रिंग को कॉपी स्मृति के कारण, आई / ओ धाराओं संबंधित अपवाद मास्क सेट, आदि के साथ प्रवेश के दौरान फेंक सकती है
नेस्टेड अपवाद
अपवाद से निपटने के दौरान एक सामान्य उपयोग का मामला होता है जब आप निम्न-स्तरीय फ़ंक्शन (जैसे कि फ़ाइल सिस्टम त्रुटि या डेटा स्थानांतरण त्रुटि) से एक सामान्य अपवाद को पकड़ते हैं और अधिक विशिष्ट उच्च-स्तरीय अपवाद को फेंकते हैं जो इंगित करता है कि कुछ उच्च-स्तरीय ऑपरेशन हो सकते हैं प्रदर्शन नहीं किया जाना चाहिए (जैसे वेब पर फोटो प्रकाशित करने में असमर्थ होना)। यह उच्च स्तर के संचालन के साथ विशिष्ट समस्याओं पर प्रतिक्रिया करने के लिए अपवाद से निपटने की अनुमति देता है और यह भी अनुमति देता है, केवल एक संदेश में त्रुटि, प्रोग्रामर को आवेदन में एक जगह खोजने के लिए जहां एक अपवाद हुआ। इस समाधान का नकारात्मक पहलू यह है कि अपवाद कॉलस्टैक छोटा है और मूल अपवाद खो गया है। यह डेवलपर्स को नवनिर्मित एक में मूल अपवाद के पाठ को मैन्युअल रूप से शामिल करने के लिए मजबूर करता है।
नेस्टेड अपवाद का उद्देश्य निम्न स्तर के अपवाद को संलग्न करके समस्या को हल करना है, जो कारण का वर्णन करता है, एक उच्च स्तर के अपवाद पर, जो यह बताता है कि इस विशेष मामले में इसका क्या मतलब है।
std::nested_exception
घोंसले के अपवादों को धन्यवाद देने की अनुमति देता है std::throw_with_nested
:
#include <stdexcept>
#include <exception>
#include <string>
#include <fstream>
#include <iostream>
struct MyException
{
MyException(const std::string& message) : message(message) {}
std::string message;
};
void print_current_exception(int level)
{
try {
throw;
} catch (const std::exception& e) {
std::cerr << std::string(level, ' ') << "exception: " << e.what() << '\n';
} catch (const MyException& e) {
std::cerr << std::string(level, ' ') << "MyException: " << e.message << '\n';
} catch (...) {
std::cerr << "Unkown exception\n";
}
}
void print_current_exception_with_nested(int level = 0)
{
try {
throw;
} catch (...) {
print_current_exception(level);
}
try {
throw;
} catch (const std::nested_exception& nested) {
try {
nested.rethrow_nested();
} catch (...) {
print_current_exception_with_nested(level + 1); // recursion
}
} catch (...) {
//Empty // End recursion
}
}
// sample function that catches an exception and wraps it in a nested exception
void open_file(const std::string& s)
{
try {
std::ifstream file(s);
file.exceptions(std::ios_base::failbit);
} catch(...) {
std::throw_with_nested(MyException{"Couldn't open " + s});
}
}
// sample function that catches an exception and wraps it in a nested exception
void run()
{
try {
open_file("nonexistent.file");
} catch(...) {
std::throw_with_nested( std::runtime_error("run() failed") );
}
}
// runs the sample function above and prints the caught exception
int main()
{
try {
run();
} catch(...) {
print_current_exception_with_nested();
}
}
संभावित उत्पादन:
exception: run() failed
MyException: Couldn't open nonexistent.file
exception: basic_ios::clear
यदि आप केवल std::exception
से विरासत में मिले अपवादों के साथ काम करते std::exception
, तो कोड को सरल भी बनाया जा सकता है।
std :: uncaught_exceptions
C ++ 17 int std::uncaught_exceptions()
परिचय देता है int std::uncaught_exceptions()
(सीमित bool std::uncaught_exception()
को बदलने के लिए bool std::uncaught_exception()
) यह जानने के लिए कि वर्तमान में कितने अपवाद हैं। एक वर्ग के लिए यह निर्धारित करने की अनुमति देता है कि क्या यह स्टैक अनइंडिंग के दौरान नष्ट हो गया है या नहीं।
#include <exception>
#include <string>
#include <iostream>
// Apply change on destruction:
// Rollback in case of exception (failure)
// Else Commit (success)
class Transaction
{
public:
Transaction(const std::string& s) : message(s) {}
Transaction(const Transaction&) = delete;
Transaction& operator =(const Transaction&) = delete;
void Commit() { std::cout << message << ": Commit\n"; }
void RollBack() noexcept(true) { std::cout << message << ": Rollback\n"; }
// ...
~Transaction() {
if (uncaughtExceptionCount == std::uncaught_exceptions()) {
Commit(); // May throw.
} else { // current stack unwinding
RollBack();
}
}
private:
std::string message;
int uncaughtExceptionCount = std::uncaught_exceptions();
};
class Foo
{
public:
~Foo() {
try {
Transaction transaction("In ~Foo"); // Commit,
// even if there is an uncaught exception
//...
} catch (const std::exception& e) {
std::cerr << "exception/~Foo:" << e.what() << std::endl;
}
}
};
int main()
{
try {
Transaction transaction("In main"); // RollBack
Foo foo; // ~Foo commit its transaction.
//...
throw std::runtime_error("Error");
} catch (const std::exception& e) {
std::cerr << "exception/main:" << e.what() << std::endl;
}
}
आउटपुट:
In ~Foo: Commit
In main: Rollback
exception/main:Error
कस्टम अपवाद
आपको अपवाद के रूप में कच्चे मूल्यों को नहीं फेंकना चाहिए, इसके बजाय मानक अपवाद वर्गों में से एक का उपयोग करें या अपना खुद का बनाएं।
अपने स्वयं के अपवाद वर्ग को std::exception
से प्राप्त होना std::exception
बारे में जाने का एक अच्छा तरीका है। यहाँ एक कस्टम अपवाद वर्ग है जो सीधे std::exception
:
#include <exception>
class Except: virtual public std::exception {
protected:
int error_number; ///< Error number
int error_offset; ///< Error offset
std::string error_message; ///< Error message
public:
/** Constructor (C++ STL string, int, int).
* @param msg The error message
* @param err_num Error number
* @param err_off Error offset
*/
explicit
Except(const std::string& msg, int err_num, int err_off):
error_number(err_num),
error_offset(err_off),
error_message(msg)
{}
/** Destructor.
* Virtual to allow for subclassing.
*/
virtual ~Except() throw () {}
/** Returns a pointer to the (constant) error description.
* @return A pointer to a const char*. The underlying memory
* is in possession of the Except object. Callers must
* not attempt to free the memory.
*/
virtual const char* what() const throw () {
return error_message.c_str();
}
/** Returns error number.
* @return #error_number
*/
virtual int getErrorNumber() const throw() {
return error_number;
}
/**Returns error offset.
* @return #error_offset
*/
virtual int getErrorOffset() const throw() {
return error_offset;
}
};
एक उदाहरण फेंक पकड़:
try {
throw(Except("Couldn't do what you were expecting", -12, -34));
} catch (const Except& e) {
std::cout<<e.what()
<<"\nError number: "<<e.getErrorNumber()
<<"\nError offset: "<<e.getErrorOffset();
}
जैसा कि आप न केवल एक गूंगा त्रुटि संदेश फेंक रहे हैं, बल्कि कुछ अन्य मान भी दर्शाते हैं कि त्रुटि क्या थी, आपकी त्रुटि हैंडलिंग बहुत अधिक कुशल और सार्थक हो जाती है।
एक अपवाद वर्ग है कि आप त्रुटि संदेशों को अच्छी तरह से संभालते हैं: std::runtime_error
आप इस वर्ग से भी विरासत में मिल सकते हैं:
#include <stdexcept>
class Except: virtual public std::runtime_error {
protected:
int error_number; ///< Error number
int error_offset; ///< Error offset
public:
/** Constructor (C++ STL string, int, int).
* @param msg The error message
* @param err_num Error number
* @param err_off Error offset
*/
explicit
Except(const std::string& msg, int err_num, int err_off):
std::runtime_error(msg)
{
error_number = err_num;
error_offset = err_off;
}
/** Destructor.
* Virtual to allow for subclassing.
*/
virtual ~Except() throw () {}
/** Returns error number.
* @return #error_number
*/
virtual int getErrorNumber() const throw() {
return error_number;
}
/**Returns error offset.
* @return #error_offset
*/
virtual int getErrorOffset() const throw() {
return error_offset;
}
};
ध्यान दें कि मैंने आधार वर्ग ( std::runtime_error
) से what()
फ़ंक्शन को ओवरराइड नहीं किया है, अर्थात हम बेस क्लास के संस्करण का उपयोग what()
। यदि आपके पास आगे का एजेंडा है तो आप इसे ओवरराइड कर सकते हैं।