5
votes

I currently read the book Effective C++ from Scott Meyers. It says I should prefer inline functions over #define for function-like macros.

Now I try to code an inline function to replace my exception macro. My old macro looks like this:

#define __EXCEPTION(aMessage) \
{ \
    std::ostringstream stream; \
    stream << "EXCEPTION: " << aMessage << ", file " <<__FILE__ << " line " << __LINE__; \
    throw ExceptionImpl(stream.str()); \
}

My new inline function is this:

inline void __EXCEPTION(const std::string aMessage)
{
   std::ostringstream stream;
   stream << "EXCEPTION: " << aMessage << ", file " <<__FILE__ << " line " << __LINE__;
   throw ExceptionImpl(stream.str());
}

As probably some people already expect, now the __FILE__ and __LINE__ macros are useless, because they refer always to the C++-file with the definition of the inline function.

Is there any way to circumvent this behaviour or should I stick with my old macro? I read this threads here, and I already suspect that there is probably no way of my second example to work fine:

4
The second isn't a macro, so its introduction is inaccurate. And sometimes macros are the way to do things (like this).WhozCraig
@WhozCraig Thanks for the hint, I changed it.John

4 Answers

19
votes

Don't use __ (double underscore) as it's reserved. Having an inline function is better.
However, here you need a mix of macro and the function, hence you can do following:

#define MY_EXCEPTION(aMessage) MyException(aMessage, __FILE__, __LINE__) 

inline void MyException(const std::string aMessage,
                        const char* fileName,
                        const std::size_t lineNumber)
{
   std::ostringstream stream;
   stream << "EXCEPTION: " << aMessage << ", file " << fileName << " line " << lineNumber;
   throw ExceptionImpl(stream.str());
}
4
votes

I see this is an old question but I think that the approach of printing the line in the exception macro is fundamentally flawed and I think I have a better alternative. I assume that the macro is used similar to the following code:

try {
    /// code 
    throw;
} 
catch (...) { __EXCEPTION(aMessage); }

With this approach the macro prints the location where the exception was catch'ed. But for troubleshooting and debugging the location where it was throw'n is usually more useful.

To get that information, we can attach the __FILE__ and __LINE__ macros to the exception. However, we still can't get completely rid of macros, but we get at least the exact throw location:

#include <iostream>
#include <exception>
#include <string>

#define MY_THROW(msg) throw my_error(__FILE__, __LINE__, msg)

struct my_error : std::exception
{
    my_error(const std::string & f, int l, const std::string & m)
        :   file(f)
        ,   line(l)
        ,   message(m)
    {}
    std::string file;
    int line;
    std::string message;

    char const * what() const throw() { return message.c_str(); }
};

void my_exceptionhandler()
{
    try {
        throw; // re-throw the exception and capture the correct type
    } 
    catch (my_error & e)
    {
        std::cout << "Exception: " << e.what() << " in line: " << e.line << std::endl;
    }
}

int main()
{
    try {

        MY_THROW("error1");

    } catch(...) { my_exceptionhandler(); }
}

There is one additional improvement possible if we are willing to use boost::exception: We can get rid of macro definitons at least in our own code. The whole program gets shorter and the locations of code execution and error handling can be nicely separated:

#include <iostream>
#include <boost/exception/all.hpp>

typedef boost::error_info<struct tag_error_msg, std::string> error_message;
struct error : virtual std::exception, virtual boost::exception { };
struct my_error:            virtual error { };

void my_exceptionhandler()
{
    using boost::get_error_info;

    try {
        throw;
    }
    catch(boost::exception & e)
    {
        char const * const * file = get_error_info<boost::throw_file>(e);
        int const * line = get_error_info<boost::throw_line>(e);
        char const * const * throw_func = get_error_info<boost::throw_function>(e);
        std::cout << diagnostic_information(e, false) 
                  << " in File: " << *file << "(" << *line << ")"
                     " in Function: " << *throw_func;
    }
}

int main()
{
    try {

        BOOST_THROW_EXCEPTION(my_error() << error_message("Test error"));

    } catch(...) { my_exceptionhandler(); }
}
3
votes

Please consider that there is another difference between using the #define function-like macro in your case in comparison to inline functions. You could have used streaming operators and parameters in your macro's invocation to be composed as your message's text:

__EXCEPTION( "My message with a value " << val )

But most times I've needed something like this, it was to check on a certain condition (like an assertion). So you could extend @iammilind's example with something like:

#define MY_EXCEPTION_COND( cond )                  \
    if (bool(cond) == false)                       \
    {                                              \
        std::string _s( #cond " == false" );       \
        MyException(_s, __FILE__, __LINE__);       \
    }

Or something a little more specialized where the values are also printed:

template <typename T>
inline void MyExceptionValueCompare(const T&          a,
                                    const T&          b,
                                    const char*       fileName,
                                    const std::size_t lineNumber)
{
    if (a != b)
    {
        std::ostringstream stream;
        stream << "EXCEPTION: " << a << " != " << b << ", file " << fileName << " line " << lineNumber;
        throw ExceptionImpl(stream.str());
    }
}

#define MY_EXCEPTION_COMP( a, b )  MyExceptionValueCompare(a, b, __FILE__, __LINE__)

Another approach you may want to take a look at is Microsoft's usage of their __LineInfo class in the Microsoft::VisualStudio::CppUnitTestFramework namespace (VC\UnitTest\Include\CppUnitTestAssert.h). See https://msdn.microsoft.com/en-us/library/hh694604.aspx

1
votes

With std::experimental::source_location, you might do:

#include <experimental/source_location>

void THROW_EX(const std::string_view& message,
              const std::experimental::source_location& location
                  = std::experimental::source_location::current())
{
    std::ostringstream stream;
    stream << "EXCEPTION: " << message
           << ", file " << location.file_name()
           << " line " << location.line();
    throw ExceptionImpl(stream.str());
}