Strategy of handling errors in C++
-
Hi
Please talk me about your strategy of handling errors
What do you think about this?
main.cpp
[CODE]
#include <iostream>
#include "Calculator.h"int main()
{
Calculator<float> calculator;try { float result = calculator.divide( 24.7f, 3.0f ); std::cout << "Result = " << result << std::endl; } catch ( const LogicError &e ) { std::cerr << e.what() << std::endl; return 1; } catch ( ... ) { std::cerr << "Error: unknown expection" << std::endl; return 1; } return 0;
}[/CODE]
Calculator.h
[CODE]
#ifndef CALCULATOR_H
#define CALCULATOR_H#include <string>
#include "DivideByZero.h"
#include "OutOfRange.h"template <typename Type>
class Calculator
{
public:
// Divide nums from the range [-1000, 1000]
Type divide( Type a, Type b )
throw ( DivideByZero, OutOfRange<int> )
{
std::string functionName = "Calculator::divide()";
if ( b == 0 ) {
throw DivideByZero( functionName );
}const int beginOfRange = -1000; const int endOfRange = 1000; if ( ( a < beginOfRange ) || ( a > endOfRange ) || ( b < beginOfRange ) || ( b > endOfRange ) ) { throw OutOfRange<int>( beginOfRange, endOfRange, functionName ); } return a / b; }
};
#endif // CALCULATOR_H[/CODE]
DivideByZero.h
[CODE]
#ifndef DIVIDEBYZERO_H
#define DIVIDEBYZERO_H#include <string>
#include "LogicError.h"class DivideByZero : public LogicError
{
public:
DivideByZero( const std::string &functionName ) :
LogicError( functionName )
{
m_message = "Error: divide by zero in the "
"function " + m_functionName;
}
};#endif // DIVIDEBYZERO_H[/CODE]
OutOfRange.h
[CODE]
#ifndef OUTOFRANGE_H
#define OUTOFRANGE_H#include <string>
#include "LogicError.h"template <typename Type>
class OutOfRange : public LogicError
{
public:
OutOfRange( Type beginOfRange,
Type endOfRange,
const std::string &functionName ) :
LogicError( functionName )
{
m_message = "Error: values must be from the range "
"[" + std::to_string( beginOfRange ) +
", " + std::to_string( endOfRange ) + "]"
" in the function " + m_functionName;
}
};#endif // OUTOFRANGE_H[/CODE]
LogicError.h
[CODE]
#ifndef LOGICERROR_H
#define LOGICERROR_H#include <string>
#include <stdexcept>class LogicError : public std::logic_error
{
public:LogicError( const std::string &functionName ) : std::logic_error( "" ), m_functionName( functionName ), m_message( "" ) { } virtual ~LogicError( ) throw( ) { } virtual const char *what( ) const throw( ) { return m_message.c_str( ); } std::string message( ) const { return m_message; }
protected:
std::string m_functionName;
std::string m_message;
};#endif // LOGICERROR_H[/CODE]
-
This is Qt example. It is about reading/writing file
main.cpp
[code]
#include <iostream>
#include <vector>
#include <QString>
#include "freeFunctions.h"
#include "Person.h"int main( )
{
// Person array for saving
Person david( "David", "White");
Person ivan( "Ivan", "Green" );
std::vector<Person> persons;
persons.push_back( david );
persons.push_back( ivan );try {
// Parse the person array to the string content
QString content;
parsePersonsToStrContent( persons, content );// Save the string content to the file
QString fileName = "file.txt";
writeData( fileName, content );// Read the string content from the file
QString readContent;
readData( fileName, readContent );// Parse the string content to the person array
std::vector<Person> readPersons;
parseContentToPersons( readContent, readPersons );// Print the person array on the screen
printData( readPersons );
} catch ( const LogicError &e ) {
std::cerr << e.what( ) << std::endl;
return 1;
} catch ( const FileError &e ) {
std::cerr << e.what( ) << std::endl;
return 1;
} catch ( ... ) {
std::cerr << "Error: unknown exception" << std::endl;
return 1;
}return 0;
}
[/code]Person.h
[code]
#ifndef PERSON_H
#define PERSON_H#include <QString>
class Person {
public:Person( const QString &firstName = "",
const QString &lastName = "" ) :
m_firstName( firstName ),
m_lastName( lastName )
{}
QString firstName( ) const
{
return m_firstName;
}QString lastName( ) const
{
return m_lastName;
}void setFirstName( const QString &firstName )
{
m_firstName = firstName;
}void setLastName( const QString &lastName )
{
m_lastName = lastName;
}private:
QString m_firstName;
QString m_lastName;
};#endif // PERSON_H
[/code]freeFunctions.h
[code]
#ifndef FREEFUNCTIONS_H
#define FREEFUNCTIONS_H#include <vector>
#include <QString>
#include "FileOpenError.h"
#include "FileReadError.h"
#include "FileWriteError.h"
#include "EmptyArgument.h"
#include "Person.h"void readData( const QString &fileName, QString &content )
throw ( EmptyArgument, FileOpenError, FileReadError );void parseContentToPersons( const QString &content,
std::vector<Person> &persons )
throw ( EmptyArgument );void parsePersonsToStrContent( const std::vector<Person> &persons,
QString &content)
throw ( EmptyArgument );void writeData( const QString &fileName,
const QString &content )
throw ( EmptyArgument, FileOpenError, FileWriteError );void printData( const std::vector<Person> &persons )
throw ( EmptyArgument );#endif // FREEFUNCTIONS_H
[/code] -
freeFunctions.cpp
[code]
#include <iostream>
#include <string>
#include <QFile>
#include <QRegExp>
#include <QTextStream>
#include <QDebug>
#include "freeFunctions.h"void readData(const QString &fileName, QString &content )
throw ( EmptyArgument, FileOpenError, FileReadError )
{
std::string functionName = "readData()";// Check argument
if ( fileName.isEmpty( ) ) {
throw EmptyArgument( functionName );
}// Open the input file for reading
QFile file( fileName );
if( !file.open( QIODevice::ReadOnly ) ) {
throw FileOpenError( fileName.toStdString( ), functionName );
}// Read the content from the file
QByteArray data = file.readAll( );
if ( data.isEmpty( ) ) {
throw FileReadError( fileName.toStdString( ), functionName );
}content = QString( data );
}void parseContentToPersons( const QString &content, std::vector<Person> &persons )
throw ( EmptyArgument )
{
std::string functionName = "parseContentToPersons()";// Check the input argument
if ( content.isEmpty( ) ) {
throw EmptyArgument( functionName );
}QRegExp regExp("(\w+) (\w+)");
int pos = 0;
while ( ( pos = regExp.indexIn( content, pos ) ) != -1 ) {
QString firstName = regExp.cap( 1 );
QString lastName = regExp.cap( 2 );
Person person( firstName, lastName );
persons.push_back( person );
pos += regExp.matchedLength( );
}
}void parsePersonsToStrContent( const std::vector<Person> &persons,
QString &content)
throw ( EmptyArgument )
{
std::string functionName = "parsePersonsToStrContent()";// Check the input argument
if ( persons.empty( ) ) {
throw EmptyArgument( functionName );
}for ( std::size_t i = 0; i < persons.size( ); ++i ) {
QString firstName = persons[i].firstName( );
QString lastName = persons[i].lastName( );
QString line = QString( "%1 %2\n" ).arg( firstName ).arg( lastName );
content.append( line );
}
}void writeData( const QString &fileName, const QString &content )
throw ( EmptyArgument, FileOpenError, FileWriteError )
{
std::string functionName = "writeData()";// Check arguments
if ( fileName.isEmpty( ) || content.isEmpty( ) ) {
throw EmptyArgument( functionName );
}// Open the output file for writing
QFile file( fileName );
if ( !( file.open( QIODevice::WriteOnly ) ) ) {
throw FileOpenError( fileName.toStdString( ), functionName );
}// Write data to the output file
QTextStream stream( &file );
stream << content;
if ( stream.status() != QTextStream::Ok ) {
throw FileWriteError( fileName.toStdString( ), functionName );
}
}void printData( const std::vector<Person> &persons )
throw ( EmptyArgument )
{
std::string functionName = "printData()";// Check the input argument
if ( persons.empty( ) ) {
throw EmptyArgument( functionName );
}// Print data
for ( std::size_t i = 0; i < persons.size( ); ++i ) {
std::cout << "First Name: " << persons[i].firstName( ).toStdString( ) << std::endl;
std::cout << "Last Name: " << persons[i].lastName( ).toStdString( ) << std::endl;
std::cout << std::endl;
}
}
[/code]FileError.h
[code]
#ifndef FILEERROR_H
#define FILEERROR_H#include <string>
#include <stdexcept>class FileError : public std::runtime_error
{
public:FileError( const std::string &fileName,
const std::string &functionName) :
std::runtime_error( "" ),
m_message( "" ),
m_fileName( fileName ),
m_functionName( functionName )
{}
virtual ~FileError( ) throw( )
{}
virtual const char *what() const throw( )
{
return m_message.c_str( );
}std::string message( ) const
{
return m_message;
}protected:
std::string m_message;
std::string m_fileName;
std::string m_functionName;
};#endif // FILEERROR_H
[/code] -
FileOpenError.h
[code]
#ifndef FILEOPENERROR_H
#define FILEOPENERROR_H#include <string>
#include "FileError.h"class FileOpenError : public FileError {
public:FileOpenError( const std::string &fileName,
const std::string &functionName) :
FileError( fileName, functionName )
{
m_message = "Error: unable to open the file "" +
m_fileName + "" in the function "" +
m_functionName + """;
}
};#endif // FILEOPENERROR_H
[/code]FileReadError.h
[code]
#ifndef FILEREADERROR_H
#define FILEREADERROR_H#include <string>
#include "FileError.h"class FileReadError : public FileError {
public:FileReadError( const std::string &fileName,
const std::string &functionName ) :
FileError( fileName, functionName )
{
m_message = "Error: unable to read the file "" + m_fileName +
"" in the function "" + m_functionName + """;
}
};#endif // FILEREADERROR_H
[/code]FileWriteError.h
[code]
#ifndef FILEWRITEERROR_H
#define FILEWRITEERROR_H#include <string>
#include "FileError.h"class FileWriteError : public FileError {
public:FileWriteError( const std::string &fileName,
const std::string &functionName ) :
FileError( fileName, functionName )
{
m_message = "Error: unable to write to the file " +
m_fileName + " in the function " + m_functionName;
}
};#endif // FILEWRITEERROR_H
[/code]EmptyArgument.h
[code]
#ifndef EMPTYARGUMENT_H
#define EMPTYARGUMENT_H#include <string>
#include "LogicError.h"class EmptyArgument : public LogicError {
public:EmptyArgument( const std::string &functionName ) :
LogicError( functionName )
{
m_message = "Error: empty argument in the "
"function " + m_functionName;
}
};#endif // EMPTYARGUMENT_H
[/code]LogicError.h
[code]
#ifndef LOGICERROR_H
#define LOGICERROR_H#include <string>
#include <stdexcept>class LogicError : public std::logic_error
{
public:LogicError( const std::string &functionName ) :
std::logic_error( "" ),
m_functionName( functionName ),
m_message( "" )
{}
virtual ~LogicError( ) throw( )
{}
virtual const char *what( ) const throw( )
{
return m_message.c_str( );
}std::string message( ) const
{
return m_message;
}protected:
std::string m_functionName;
std::string m_message;
};#endif // LOGICERROR_H
[/code] -
Hi,
there is no this is how to handle errors in C++. From my POV, there are two ways to do it:
Exception handling
return values
I prefer return values as exceptions for me are for exceptional cases (no more memory...) and not for 'normal' errors.
Qt also does not use exception handling, so if you are inside Qt calls, make sure never to throw exceptions back to the event loop.
-
Gerolf, thank you! What do you think about my class Receiver? Can I use it in large projects?
[CODE]
void MainWindow::runReceiver()
{
try {
m_receiver->run();
connect( m_receiver, SIGNAL( signalReceivedData( QByteArray ) ),
this, SLOT( slotReceivedData( QByteArray ) ) );
} catch ( const PortError &e ) {
QString message( e.what() );
QMessageBox::information( this, tr( "Error" ), message );
return;
} catch( ... ) {
QString message( "Error: unknown exception" );
QMessageBox::information( this, tr( "Error" ), message );
return;
}
}
[/CODE]Receiver.h
[CODE]
#ifndef RECEIVER_H
#define RECEIVER_H#include <QObject>
#include <QString>
#include <QSerialPort>
#include <stdexcept>
#include <string>
#include "PortError.h"class Receiver : public QObject {
Q_OBJECT
public:Receiver( const QString &portName = QString( "COM2" ), QSerialPort::BaudRate baudRate = QSerialPort::Baud9600, QSerialPort::DataBits dataBits = QSerialPort::Data8, QSerialPort::Parity parity = QSerialPort::NoParity, QSerialPort::StopBits stopBits = QSerialPort::OneStop, QSerialPort::FlowControl flowControl = QSerialPort::NoFlowControl ); Receiver( const Receiver &receiver ); ~Receiver(); void run( ) throw( PortError ); QString getPortName() const; QSerialPort::BaudRate getBaudRate() const; QSerialPort::DataBits getDataBist() const; QSerialPort::Parity getParity() const; QSerialPort::StopBits getStopBits() const; QSerialPort::FlowControl getFlowControl() const;
signals:
void signalReceivedData( QByteArray data );private slots:
void slotReadyRead( );private:
QSerialPort m_serialPort;
QString m_portName;
QSerialPort::BaudRate m_baudRate;
QSerialPort::DataBits m_dataBits;
QSerialPort::Parity m_parity;
QSerialPort::StopBits m_stopBits;
QSerialPort::FlowControl m_flowControl;
};#endif // RECEIVER_H
[/CODE]Receiver.cpp
[CODE]
#include "Receiver.h"Receiver::Receiver( const QString &portName,
QSerialPort::BaudRate baudRate,
QSerialPort::DataBits dataBits,
QSerialPort::Parity parity,
QSerialPort::StopBits stopBits,
QSerialPort::FlowControl flowControl ) :
m_portName( portName ),
m_baudRate( baudRate ),
m_dataBits( dataBits ),
m_parity( parity ),
m_stopBits( stopBits ),
m_flowControl( flowControl )
{
}Receiver::Receiver( const Receiver &receiver )
{
this->m_portName = receiver.getPortName();
this->m_baudRate = receiver.getBaudRate();
this->m_dataBits = receiver.getDataBist();
this->m_parity = receiver.getParity();
this->m_stopBits = receiver.getStopBits();
this->m_flowControl = receiver.getFlowControl();
}Receiver::~Receiver()
{
m_serialPort.close();
}void Receiver::run( ) throw( PortError )
{
m_serialPort.setPortName( m_portName );if ( !m_serialPort.open( QIODevice::ReadOnly ) ) { throw PortError( m_portName.toStdString() ); } m_serialPort.setBaudRate( m_baudRate ); m_serialPort.setDataBits( m_dataBits ); m_serialPort.setParity( m_parity ); m_serialPort.setStopBits( m_stopBits ); m_serialPort.setFlowControl( m_flowControl ); connect( &m_serialPort, SIGNAL( readyRead( ) ), this, SLOT( slotReadyRead( ) ) );
}
QString Receiver::getPortName() const
{
return m_portName;
}QSerialPort::BaudRate Receiver::getBaudRate() const
{
return m_baudRate;
}QSerialPort::DataBits Receiver::getDataBist() const
{
return m_dataBits;
}QSerialPort::Parity Receiver::getParity() const
{
return m_parity;
}QSerialPort::StopBits Receiver::getStopBits() const
{
return m_stopBits;
}QSerialPort::FlowControl Receiver::getFlowControl() const
{
return m_flowControl;
}void Receiver::slotReadyRead( )
{
QByteArray data;
data = m_serialPort.readAll( );
emit signalReceivedData( data );
}
[/CODE]PortError.h
[CODE]
#ifndef PORTERROR_H
#define PORTERROR_H#include <stdexcept>
#include <string>class PortError : public std::runtime_error
{
public:
PortError( const std::string &portName ) : std::runtime_error( "" )
{
m_message = "Error: unable to open the port "" +
portName + """;
}virtual ~PortError() throw() { } virtual const char *what() const throw() { return m_message.c_str(); } std::string getMessage() { return m_message; }
private:
std::string m_message;
};#endif // PORTERROR_H
[/CODE] -
[quote author="Gerolf" date="1413793241"]Hi,
there is no this is how to handle errors in C++. From my POV, there are two ways to do it:
Exception handling
return values
[/quote]
I would throw a third way into the mix - although it is closely related to return values, and applicable only in certain cases: Error status as a class member.
Suppose we have a class that opens a file, reads its content, does something and closes the file again. We could structure it like this@class FileProcessor
{
public:
void handleFile(const QString& fileName);
ErrorStatus getErrorStatus() const;
SomeDataClass getProcessedData() const;private:
void openFile();
void readFile();
void processData();
void closeFile();ErrorStatus m_ErrorStatus;
QString m_CurrentFileName;
// Some temporary member variables...not important for this discusssion
}@The methods would be implemented in such a way:
@void FileProcessor::handleFile(const QString& fileName)
{
m_CurrentFileName = fileName;openFile();
readFile();
processData();
closeFile();
}void FileProcessor::openFile()
{
if (m_ErrorStatus.isErrorSet())
{
return;
}if ( ! QFile::exists(m_CurrentFileName) )
{
// Ooops...file does not exist
m_ErrorStatus.setError("File does not exist");
return;
}
// Open the file - set errors if they happen, as above
}void FileProcessor::readFile()
{
if (m_ErrorStatus.isErrorSet())
{
return;
}
// Read the file - set errors if they happen
}void FileProcessor::processData()
{
if (m_ErrorStatus.isErrorSet())
{
return;
}
// Process the data - set errors if they happen
}void FileProcessor::closeFile()
{
if (m_ErrorStatus.isErrorSet())
{
return;
}
// Close the file - set errors if they happen
}ErrorStatus FileProcessor::getErrorStatus() const
{
return m_ErrorStatus;
}@The caller would use this class in this way:
@
FileProcessor processor;
processor.handleFile(myFilename);
if (processor.getErrorStatus().isErrorSet())
{
// Log, report, whatever
}
else
{
// Use data and continue
}
@Edit: Insert nod to "Clean Code" by Robert C. Martin
-
[quote author="Asperamanca" date="1414597531"]I would throw a third way into the mix - although it is closely related to return values, and applicable only in certain cases: Error status as a class member.[/quote]That's a good one.
An example of a Qt class that does this is "http://qt-project.org/doc/qt-5/qxmlstreamreader.html":QXmlStreamReader. See the following functions:
- QXmlStreamReader::raiseError()
- QXmlStreamReader::hasError()
- QXmlStreamReader::error()
- QXmlStreamReader::errorString()
-
...but ErrorStatus means: Having a by design not multithreading / reentrant class.
To not break parallel calls and to be more safe it's better to treat each method delivering an error status separately. So return values (classes, enums etc.) are better and for critical program flow or memory errors exceptions are part of the ANSI C++ standard (see: new/delete).
But: As mentioned before Qt doesn't handle exceptions everywhere - so throwing inside a slot or event handling method can crash your app. No: Not can - it will ;)
ciao,
Chris -
...but ErrorStatus means: Having a by design not multithreading / reentrant class.
To not break parallel calls and to be more safe it's better to treat each method delivering an error status separately. So return values (classes, enums etc.) are better and for critical program flow or memory errors exceptions are part of the ANSI C++ standard (see: new/delete).
But: As mentioned before Qt doesn't handle exceptions everywhere - so throwing inside a slot or event handling method can crash your app. No: Not can - it will ;)
ciao,
Chris -
@8Observer8 On his page, Jon K. talks about C++ exceptions.
There's a useful class (esc.hpp) there at the bottom (under the video's). Jon K.'s main points are:
Exception-Safety Guidelines- Throw by value. Catch by reference.
- No dynamic exception specifications. Use noexcept.
- Destructors that throw are evil.
- Use RAII. [esc.hpp] (Every responsibility is an object. One responsibility per object.)
- All cleanup code called from a destructor.
- Support swapperator (With No-Throw Guarantee)
- Draw "Critical Lines" for the Strong Guarantee
- Know where to catch (Switch/Strategy/Some Success)
- Prefer exceptions to error codes