PDA

View Full Version : Strategy of handling errors in C++



8Observer8
19th October 2014, 17:38
Hi

Please talk me about your strategy of handling errors

What do you think about this?

main.cpp


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

Calculator.h


#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

DivideByZero.h


#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

OutOfRange.h


#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

LogicError.h


#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

d_stranz
19th October 2014, 19:20
This isn't a Qt question. You would get a wider range of opinions if you posted this question to a forum strictly dedicated to C++, not to a programming toolkit. (Like Stack Overflow (http://stackoverflow.com/)).

There is nothing particularly wrong with your approach, except for the fact that with exceptions you have to be very careful how you use them if you want to be able to recover from one after it is thrown. In your example, you simply exit the program when an exception is thrown. Your users would get very tired of that after the first couple of times.

What happens if you want to give your user a chance to correct the error and continue with using the program? When an exception is thrown, it will be caught by whatever catch clause is in scope that handles the type of exception thrown. If that catch clause is in a convenient place for your program to put up an error message and then return to normal operation, that's good. But the problem with catch clauses is that you have no idea where the exception was thrown. Especially if you have a "catch-all" catch clause (catch( ... )), you could end up there and have no idea what to do to recover.

The only time I use try / catch logic is when I can retain complete control over where exceptions are thrown and caught. In any kind of a complex GUI-based program, throwing exceptions at a low level in the logic, and catching them in some high-level "error handler" generally leads to the situation where you have no choice except to exit the program, because you have no way to recover and get back to where the exception was thrown.

I tend to use other methods, like checking that the arguments are valid before passing them into a method. I you have a method (like "divide"), where you know that one of the arguments can't be zero, then check that before calling divide() and tell the user that it is an invalid argument. Don't let divide() blow up and then try to recover after the fact.

Others might have a different opinion.

8Observer8
20th October 2014, 08:10
Thank you!

This is Qt example. It is about reading/writing file

main.cpp


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


Person.h


#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


freeFunctions.h


#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


freeFunctions.cpp


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


FileError.h


#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


FileOpenError.h


#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


FileReadError.h


#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


FileWriteError.h


#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


EmptyArgument.h


#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


LogicError.h


#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

8Observer8
24th October 2014, 07:40
What do you think about my class Receiver? Can I use it in large projects?



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


Receiver.h


#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


Receiver.cpp


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


PortError.h


#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