Strategy of handling errors in C++

Hi

Please talk me about your strategy of handling errors

What do you think about this?

main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#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 

main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#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 
1
2
> Type divide( Type a, Type b )
>    throw ( DivideByZero, OutOfRange<int> )


The use of dynamic-exception-specifications is deprecated - IS.


Rationale:
Although initially appealing, an exception-specification tends to have consequences that require very careful thought to understand. The biggest problem with exception-specifications is that programmers use them as though they have the effect the programmer would like, instead of the effect they actually have.

A non-inline function is the one place a "throws nothing" exception-specification may have some benefit with some compilers.

- boost http://www.boost.org/development/requirements.html#Exception-specification


Besides the overhead for generating the try/catch blocks shown above, which might be minor on efficient compilers, there are at least two other ways that exception specifications can commonly cost you in runtime performance. First, some compilers will automatically refuse to inline a function having an exception specification, just as they can apply other heuristics such as refusing to inline functions that have more than a certain number of nested statements or that contain any kind of loop construct. Second, some compilers don’t optimize exception-related knowledge well at all, and will add the above-shown try/catch blocks even when the function body provably can’t throw.

Moving beyond runtime performance, exception specifications can cost you programmer time because they increase coupling. For example, removing a type from the base class virtual function’s exception specification is a quick and easy way to break lots of derived classes in one swell foop (if you’re looking for a way). Try it on a Friday afternoon checkin, and start a pool to guess the number of angry emails that will be waiting for you in your inbox on Monday morning.

So here’s what seems to be the best advice we as a community have learned as of today:

Moral #1: Never write an exception specification.

Moral #2: Except possibly an empty one, but if I were you I’d avoid even that.

- Sutter http://www.gotw.ca/publications/mill22.htm



For a gentle introduction to error handling, read Chapter 5 of Stroustrup's
'Programming -- Principles and Practice Using C++'

Read this: https://isocpp.org/wiki/faq/exceptions

And then read this (slowly): http://stroustrup.com/except.pdf
JLBorges, I will! Thank you very much!
Topic archived. No new replies allowed.