How can I replace my c++ exception macro with an inline function with __LINE__ and __FILE__ support?

旧巷老猫 提交于 2019-12-01 19:02:45

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

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

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

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());
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!