Introduction to exception handling

Dirk Eddelbuettel — written Jan 13, 2013 — source

One of the many features that make C++ different from C is exception handling. This is a somewhat big topic, and large codebases sometimes eschew exceptions for lack of traceability in truly large programs (and eg the Google in-house C++ style guide is a well-known example of the Just say no school). Opinions are divided; exceptions are generally seen as a useful tool for smaller-scale projects.

We tend to agree. For our purposes, exceptions are just fine. They allow for a fine-grained way to report errors to R.

The basic idea is the that we must surround code which could throw an exception by a block of try and catch.

A simple example will help.

#include <Rcpp.h>

using namespace Rcpp;
 
// [[Rcpp::export]]
double takeLog(double val) {
    try {
        if (val <= 0.0) {         	// log() not defined here
            throw std::range_error("Inadmissible value");
        }
        return log(val);
    } catch(std::exception &ex) {	
	forward_exception_to_r(ex);
    } catch(...) { 
	::Rf_error("c++ exception (unknown reason)"); 
    }
    return NA_REAL;             // not reached
}

We can look at this example with a valid, and an invalid argument:

# works
takeLog(exp(1))
[1] 1
# throws exception
tryCatch(takeLog(-1.0),
         error = print)
<std::range_error: Inadmissible value>

As we can see, execptions works as expected. By throwing an exception derived from the standard exception call, we arrive in the case first catch branch where the exception text can be captured and turned into a standard R error message.

The scaffolding of the try and catch is even automatically added by our common tools cxxfunction() (from the inline package) and sourceCpp(). So this shorter function is equivalent when these tools are used. Otherwise the macros BEGIN_CPP and END_CPP can be used.

#include <Rcpp.h>

using namespace Rcpp;

// [[Rcpp::export]]
double takeLog2(double val) {
    if (val <= 0.0) {         	// log() not defined here
        throw std::range_error("Inadmissible value");
    }
    return log(val);
}

Again, we can look at this example with a valid, and an invalid argument:

# works
takeLog2(exp(1))
[1] 1
# throws exception
tryCatch(takeLog2(-1.0),    
         error = print)
<std::range_error: Inadmissible value>

This shows that due to the automatic addition of the needed infrastructure, exception handling can add a useful mechanism to signal error conditions back to R.

There is even a shortcut defined as Rcpp function stop:

#include <Rcpp.h>

using namespace Rcpp;

// [[Rcpp::export]]
double takeLog3(double val) {
    if (val <= 0.0) {         	// log() not defined here
        stop("Inadmissible value");
    }
    return log(val);
}
# works
takeLog3(exp(1))
[1] 1
# throws exception
tryCatch(takeLog3(-1.0), 
         error = print)
<Rcpp::exception: Inadmissible value>

tags: basics 

Related Articles