Terry Li
Terry Li

Reputation: 17268

How to throw a C++ exception

I have a very poor understanding of exception handling(i.e., how to customize throw, try, catch statements for my own purposes).

For example, I have defined a function as follows: int compare(int a, int b){...}

I'd like the function to throw an exception with some message when either a or b is negative.

How should I approach this in the definition of the function?

Upvotes: 408

Views: 694232

Answers (6)

Danilo
Danilo

Reputation: 1028

Just to put this also out there for anyone curious. The throw functions by calling a constructor of some object and then calls for std::terminate. So its not any particular type or special functionality but simply object is constructed, then terminate is called and deconstructor is rarely called.

The whole syntax can be viewed as foo::foo() -> exit, and eventhough there are difference between similar calls in reality what is printed out in constructor that is what you will see outside of regular informational message - for my c++17 and some pseudo class foo:

terminate called after throwing an instance of 'foo'
Aborted (core dumped)

Where important information is terminate called telling us what happened after constructor is called, and (core dumped) telling us that anything done via code is undone so memory leaks and stuff is "given". So any class/struct or function can be called after throw.

Take for example:

void test_function(){ std::cout << "test function" << std::endl;}
int main(){ throw test_function();} 

Compiler g++ complains due to void since it seems that it isn't valid type to be called exception for. But if I change it to:

void test_function(){ std::cout << "test function" << std::endl; return 1;}
int main(){ throw test_function();}

Everything is fine, only message is a bit different:

terminate called after throwing an instance of 'int'
Aborted (core dumped)

Meaning that if no struct/class is used with throw it will default to type object as caller. This means that , as long as its typed, anything can be put in throw.

This also means that within std::exception (that most exceptions inherit from) most important elements for throw-in are constructor and assignment operator. Where constructor is what sets and displays any message, and assignment operator is used if someone wants to to some class manipulation once exception is throw-n. Other functions as deconstructor and what() function are used only if we need to catch it which is basically whatever. When object instance is caught, we may want to get a messaage or might want to change it, do something because of it and so on. You can use them as events (since it stops your code at the moment its called) or as some other stuff. Catch basically stops your program from terminating so it only calls constructor, and deconstructor after scope ends.

That means that we can make whatever class/struct as errors/warnings or even invent a new type. Which means we can do something like this:

struct temp
{
    temp(uint8_t *incrementer):message("Hi There")
    {
        std::cout << "\t CONSTRUCTED WITH DEFAULT MESSAGE" << std::endl;
        std::cerr << this->message << std::endl;
        (*incrementer) ++;
    }
    temp(uint8_t *incrementer,const char* new_msg)
    :message(new_msg)
    {
        std::cout << "\t CONSTRUCTED WITH NEW MESSAGE" << std::endl;
        std::cerr << this->message << std::endl;
        (*incrementer) ++;
    }
    ~temp()
    {
        std::cout << "\t DECONSTRUCTED" << std::endl;
    }
    
    private:
        const char* message;
};

And called with:

int main(int argc, char** argv)
{   
    std::cout << argc << '\t' << *argv << std::endl; // to avoid unused variable warning
    
    uint8_t incrementer = 0; // counter how many temp exceptions we have called 
    
    {
        
        try
        {
            throw temp(&incrementer);
        }catch (temp t)
        {
            std::cout << "Hey caught it!" << std::endl;
        }
        
        try
        {
            throw temp(&incrementer,"Different Message");
        }catch (temp t)
        {
            std::cout << "Hey caught it AGAIN!" << std::endl;
        }
        
        
        std::cout << "exceptionc called\t" << (int)incrementer  << " times"<< std::endl;
    }
    
    
    return 0;
}

Which gives us this nice little thing:

1   ./main
     CONSTRUCTED WITH DEFAULT MESSAGE
Hi There!
Hey caught it!
     DECONSTRUCTED
     DECONSTRUCTED
     CONSTRUCTED WITH NEW MESSAGE
Different Message
Hey caught it AGAIN!
     DECONSTRUCTED
     DECONSTRUCTED
exceptionc called   2 times

Buuuuut!! Notice this, deconstructor is called 2 times for each catch, which is why I personally wouldn't suggest having some heap allocated classes as exceptions - unless implementing empty constructor as well. For this example, valgrind has no issues so as long as it is on stack it seems it doesn't care how many times its constructed or deconstructed.

Outside of that you can have fun with macros and preprocessor statements to capture function call in constructor with global macros.

Upvotes: 0

Guy Avraham
Guy Avraham

Reputation: 3690

Adding to this answer, as it doesn't seem advantageous to create another answer for this Q&A at this time.

In the case where you create your own custom exception, that derives from std::exception, when you catch "all possible" exceptions types, you should always start the catch clauses with the "most derived" exception type that may be caught. See the example (of what NOT to do):

#include <iostream>
#include <string>

using namespace std;

class MyException : public exception
{
public:
    MyException(const string& msg) : m_msg(msg)
    {
        cout << "MyException::MyException - set m_msg to:" << m_msg << endl;
    }

   ~MyException()
   {
        cout << "MyException::~MyException" << endl;
   }

   virtual const char* what() const throw () 
   {
        cout << "MyException::what" << endl;
        return m_msg.c_str();
   }

   const string m_msg;
};

void throwDerivedException()
{
    cout << "throwDerivedException - thrown a derived exception" << endl;
    string execptionMessage("MyException thrown");
    throw (MyException(execptionMessage));
}

void illustrateDerivedExceptionCatch()
{
    cout << "illustrateDerivedExceptionsCatch - start" << endl;
    try 
    {
        throwDerivedException();
    }
    catch (const exception& e)
    {
        cout << "illustrateDerivedExceptionsCatch - caught an std::exception, e.what:" << e.what() << endl;
        // some additional code due to the fact that std::exception was thrown...
    }
    catch(const MyException& e)
    {
        cout << "illustrateDerivedExceptionsCatch - caught an MyException, e.what::" << e.what() << endl;
        // some additional code due to the fact that MyException was thrown...
    }

    cout << "illustrateDerivedExceptionsCatch - end" << endl;
}

int main(int argc, char** argv)
{
    cout << "main - start" << endl;
    illustrateDerivedExceptionCatch();
    cout << "main - end" << endl;
    return 0;
}

NOTE:

  1. The proper order should be vice-versa, i.e.- first you catch (const MyException& e) which is followed by catch (const std::exception& e).

  2. As you can see, when you run the program as is, the first catch clause will be executed (which is probably what you did NOT want in the first place).

  3. Even though the type caught in the first catch clause is of type std::exception, the "proper" version of what() will be called - cause it is caught by reference (change at least the caught argument std::exception type to be by value - and you will experience the "object slicing" phenomena in action).

  4. In case that the "some code due to the fact that XXX exception was thrown..." does important stuff WITH RESPECT to the exception type, there is misbehavior of your code here.

  5. This is also relevant if the caught objects were "normal" object like: class Base{}; and class Derived : public Base {}...

  6. g++ 7.3.0 on Ubuntu 18.04.1 produces a warning that indicates the mentioned issue:

In function ‘void illustrateDerivedExceptionCatch()’: item12Linux.cpp:48:2: warning: exception of type ‘MyException’ will be caught catch(const MyException& e) ^~~~~

item12Linux.cpp:43:2: warning: by earlier handler for ‘std::exception’ catch (const exception& e) ^~~~~

Again, I will say, that this answer is only to ADD to the other answers described here (I thought this point is worth mentioning, yet could not depict it within a comment).

Upvotes: 13

nsanders
nsanders

Reputation: 12636

Simple:

#include <stdexcept>

int compare( int a, int b ) {
    if ( a < 0 || b < 0 ) {
        throw std::invalid_argument( "received negative value" );
    }
}

The Standard Library comes with a nice collection of built-in exception objects you can throw. Keep in mind that you should always throw by value and catch by reference:

try {
    compare( -1, 3 );
}
catch( const std::invalid_argument& e ) {
    // do stuff with exception... 
}

You can have multiple catch() statements after each try, so you can handle different exception types separately if you want.

You can also re-throw exceptions:

catch( const std::invalid_argument& e ) {
    // do something

    // let someone higher up the call stack handle it if they want
    throw;
}

And to catch exceptions regardless of type:

catch( ... ) { };

Upvotes: 542

GPMueller
GPMueller

Reputation: 3149

Though this question is rather old and has already been answered, I just want to add a note on how to do proper exception handling in C++11:

Use std::nested_exception and std::throw_with_nested

It is described on StackOverflow here and here, how you can get a backtrace on your exceptions inside your code without need for a debugger or cumbersome logging, by simply writing a proper exception handler which will rethrow nested exceptions.

Since you can do this with any derived exception class, you can add a lot of information to such a backtrace! You may also take a look at my MWE on GitHub, where a backtrace would look something like this:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

Upvotes: 24

serup
serup

Reputation: 3822

You could define a message to throw when a certain error occurs:

throw std::invalid_argument( "received negative value" );

or you could define it like this:

std::runtime_error greatScott("Great Scott!");          
double getEnergySync(int year) {                        
    if (year == 1955 || year == 1885) throw greatScott; 
    return 1.21e9;                                      
}                                                       

Typically, you would have a try ... catch block like this:

try {
// do something that causes an exception
}catch (std::exception& e){ std::cerr << "exception: " << e.what() << std::endl; }

Upvotes: 13

Cat Plus Plus
Cat Plus Plus

Reputation: 129764

Just add throw where needed, and try block to the caller that handles the error. By convention you should only throw things that derive from std::exception, so include <stdexcept> first.

int compare(int a, int b) {
    if (a < 0 || b < 0) {
        throw std::invalid_argument("a or b negative");
    }
}

void foo() {
    try {
        compare(-1, 0);
    } catch (const std::invalid_argument& e) {
        // ...
    }
}

Also, look into Boost.Exception.

Upvotes: 21

Related Questions