Sök…
Fånga undantag
Ett try/catch
används för att fånga undantag. Koden i try
avsnitt är koden som kan kasta ett undantag, och koden i catch
klausul (s) hanterar undantag.
#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();
}
}
Flera catch
klausuler kan användas för att hantera flera undantagstyper. Om flera catch
klausuler finns, försöker undantagshantering mekanism för att matcha dem i den ordning de framträdande i koden:
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();
}
Undantagsklasser som är härledda från en gemensam basklass kan fångas med en enda catch
klausul för den gemensamma basklass. Exemplet ovan kan ersätta de två catch
klausuler för std::length_error
och std::out_of_range
med en enda bestämmelse för 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();
}
Eftersom catch
klausuler försökt i ordning, se till att skriva mer specifika fångst klausuler först, annars din undantagshantering kod kan aldrig få kallas:
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. */
}
En annan möjlighet är catch-all-hanteraren, som kommer att fånga alla kastade objekt:
try {
throw 10;
} catch (...) {
std::cout << "caught an exception";
}
Återupptag (undantag)
Ibland vill du göra något med det undantag du fångar (som att skriva för att logga eller skriva ut en varning) och låta det bubbla upp till det övre omfånget som ska hanteras. För att göra det kan du dra ut alla val av undantag:
try {
... // some code here
} catch (const SomeException& e) {
std::cout << "caught an exception";
throw;
}
Med hjälp av throw;
utan argument kommer att kasta det för närvarande fångade undantaget.
För att återhämta en hanterad std::exception_ptr
har C ++ Standardbiblioteket rethrow_exception
funktionen som kan användas genom att ta med <exception>
-huvudet i ditt program.
#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
Funktion Prova block i konstruktör
Det enda sättet att få undantag i initialiseringslistan:
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;
};
Funktion Försök Blockera för vanlig funktion
void function_with_try_block()
try
{
// try block body
}
catch (...)
{
// catch block body
}
Vilket motsvarar
void function_with_try_block()
{
try
{
// try block body
}
catch (...)
{
// catch block body
}
}
Observera att för konstruktörer och förstörare är beteendet annorlunda eftersom fångstblocket ändå kastar ett undantag (det fångade om det inte finns något annat kast i fångstblockkroppen).
Funktionen main
är tillåtet att ha en funktion try blocket som någon annan funktion, men main
s funktions try blocket kommer inte fånga undantag som uppstår under byggandet av en icke-lokal statisk variabel eller förstörelse av någon statisk variabel. Istället kallas std::terminate
.
Funktion Testa block i destruktorn
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.
}
};
Observera att även om detta är möjligt måste man vara mycket försiktig med att kasta från destruktorn, som om en destruktor som kallas under stacken avlindning kastar ett undantag kallas std::terminate
.
Bästa praxis: kasta efter värde, fångst efter const referens
I allmänhet anses det vara god praxis att kasta efter värde (snarare än med pekaren), men fånga med (const) referens.
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;
}
En anledning till att fångst genom referens är en bra praxis är att det eliminerar behovet av att rekonstruera föremålet när det skickas till fångstblocket (eller när det sprids till andra fångstblock). Fångst genom referens gör det också möjligt att hantera undantagen polymorfiskt och undviker skärning av objekt. Men om du drar om ett undantag (som throw e;
se exempel nedan) kan du fortfarande få objektskivning eftersom throw e;
uttalande gör en kopia av undantaget eftersom vilken typ som helst deklareras:
#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;
}
Om du är säker på att du inte kommer att göra någonting för att ändra undantaget (som att lägga till information eller ändra meddelandet), kan fångst efter const-referens kompilatorn göra optimeringar och kan förbättra prestandan. Men detta kan fortfarande orsaka skarvning av objekt (som ses i exemplet ovan).
Varning: Se upp för att kasta oavsiktliga undantag i catch
, särskilt relaterade till att tilldela extra minne eller resurser. Exempelvis kan konstruktion av logic_error
, runtime_error
eller deras underklasser kasta bad_alloc
grund av att minnet rinner ut vid kopiering av undantagssträngen, I / O-strömmar kan kasta under loggning med respektive undantagsmasker, etc.
Häckt undantag
Under undantagshantering finns det ett vanligt användningsfall när du fångar ett generiskt undantag från en lågnivåfunktion (t.ex. ett filsystemfel eller dataöverföringsfel) och kastar ett mer specifikt undantag på hög nivå som indikerar att viss högnivåoperation kan inte utförs (t.ex. att det inte går att publicera ett foto på webben). Detta tillåter undantagshantering att reagera på specifika problem med högnivåoperationer och tillåter även programmeraren att ha ett felmeddelande att hitta en plats i applikationen där ett undantag inträffade. Nackdelen med den här lösningen är att undantagssamtal är avkortat och det ursprungliga undantaget går förlorat. Detta tvingar utvecklare att manuellt inkludera text med originalundantag i en nyligen skapad.
Kapslade undantag syftar till att lösa problemet genom att fästa undantag på låg nivå, som beskriver orsaken, till ett undantag på hög nivå, som beskriver vad det betyder i detta specifika fall.
std::nested_exception
gör det möjligt att häcka undantag tack vare 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();
}
}
Möjlig utgång:
exception: run() failed
MyException: Couldn't open nonexistent.file
exception: basic_ios::clear
Om du bara arbetar med undantag som ärvts från std::exception
kan kod till och med förenklas.
std :: uncaught_exceptions
C ++ 17 introducerar int std::uncaught_exceptions()
(för att ersätta den begränsade bool std::uncaught_exception()
) för att veta hur många undantag som för närvarande är oupptagna. Det gör det möjligt för en klass att avgöra om den förstörs under en stapel som rullas upp eller inte.
#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;
}
}
Produktion:
In ~Foo: Commit
In main: Rollback
exception/main:Error
Anpassat undantag
Du bör inte kasta råvärden som undantag, istället använda en av standardundantagsklasserna eller göra dina egna.
Att ha din egen undantagsklass som ärvts från std::exception
är ett bra sätt att göra det. Här är en anpassad undantagsklass som direkt ärver från 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;
}
};
Ett exempel kasta fångst:
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();
}
Eftersom du inte bara kastar ett dumt felmeddelande, utan också andra värden som representerar exakt vad felet var, blir din felhantering mycket effektivare och meningsfullare.
Det finns en undantagsklass som låter dig hantera felmeddelanden fint: std::runtime_error
Du kan ärva från den här klassen också:
#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;
}
};
Observera att jag inte har åsidosatt funktionen what()
från basklassen ( std::runtime_error
), dvs vi kommer att använda basklassens version av what()
. Du kan åsidosätta den om du har ytterligare agenda.