3

I want to derive a stringstream so that I can use the operator<< to construct a message which will then be thrown. The API would look like:

error("some text") << " more text " << 42 << std::endl;

This should do a

throw "some text more text 42"

So what I did is make an errorbuf (inheriting from streambuf) which overloads the 'overflow' method and then create an ostream(&errorbuf). I wonder if I shouldn't instead inherit from basic_ostringstream or something...

Giovanni Funchal
  • 8,934
  • 13
  • 61
  • 110

4 Answers4

4

You could probably make it easier by doing something like:

class error_builder
{
public:
    error_builder(const std::string& pMsg = "")
    {
        mMsg << pMsg;
    }

    ~error_builder(void)
    {
        throw std::runtime_error(mMsg.str());
    }

    template <typename T>
    error_builder& operator<<(const T& pX)
    {
        mMsg << pX;

        return *this;
    }

private:
    std::stringstream mMsg;    
};


error_builder("some text") << " more text " << 42 << std::endl;

Note that you shouldn't throw strings like you are, hence I used std::runtime_error. All exceptions should derive from std::exception, which runtime_error does, that way all meaningful exceptions can be caught with const std::exception&.

This works because the temporary lives until the end of the full expression.

GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • I won't really throw const char*s. It was just a concept. – Giovanni Funchal Mar 17 '10 at 07:51
  • You should not throw exceptions in the destructor (http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.3). – Giovanni Funchal Mar 17 '10 at 07:55
  • @Helltone: This is an exception. Understand the reason you shouldn't *generally* (key word here) throw: during stack unwinding if two exceptions become active the application is terminated. This is obviously not a case here, as it's intended to throw immediately. (To be fair, the stream *could* fail and throw, but meh.) EDIT: Well, fixed it anyway, so there you go. :) – GManNickG Mar 17 '10 at 07:59
  • 2
    @Helltone: Exceptions for streams are off by default and stream exceptions are one of the few things that are likely to throw during the lifetime of the `error_builder`. Otherwise, the only thing that is likely to be thrown is a `std::bad_alloc` and if that's going to happen then `std::terminate` is probably the best that can happen anyway. – CB Bailey Mar 17 '10 at 08:04
  • There is a bug when you do << the first time it overwrites the initial value set in the constructor, because the stringstream does not have the "at-the-end" attribute. Instead of : mMsg(pMsg), you should do something like : mMsg(pMsg, std::ios_base::out | std::ios_base::ate). – Giovanni Funchal Mar 17 '10 at 08:15
  • @GMan: Of course your 'fix' means that there are now corner cases which won't work if they rely on error_build exceptions being thrown - e.g. in a properly try...catch protected block of code in a destructor that happens to be being called due to stack unwinding of another exception. See http://www.gotw.ca/gotw/047.htm for what I mean. – CB Bailey Mar 17 '10 at 08:19
  • @Charles: Thanks for the link, never read that one. @Helltone: You're right, how's that? – GManNickG Mar 17 '10 at 08:37
  • Why error_builder does not inherit from std::ostringstream instead? – Giovanni Funchal Mar 17 '10 at 08:52
  • '<< std::endl' doesn't seem to work... I get error: no match for 'operator<<' in '((error_builder*)error_builder(((const std::string&)(& std::basic_string, std::allocator >(((const char*)"some text"), ((const std::allocator&)((const std::allocator*)(& std::allocator()))))))).error_builder::operator<< [with T = char [12]](((const char (&)[12])" more text ")))->error_builder::operator<< [with T = int](((const int&)((const int*)(&42)))) << std::endl' – Giovanni Funchal Mar 17 '10 at 09:20
  • @Helltone: Thanks for adding the missing operators (on your answer). The reason I don't inherit from `stringstream` is because this isn't meant to be a `stringstream`. Composition does the job well, and prevents mis-use. – GManNickG Mar 17 '10 at 15:09
3

Some operators are missing from GMan's solution.

class error {
   public:
   explicit error(const std::string& m = "") :
          msg(m, std::ios_base::out | std::ios_base::ate)
   {}

   ~error() {
      if(!std::uncaught_exception()) {
         throw std::runtime_error(msg.str());
      }
   }

   template<typename T>
   error& operator<<(const T& t) {
      msg << t;
      return *this;
   }

   error& operator<<(std::ostream& (*t)(std::ostream&)) {
      msg << t;
      return *this;
   }
   error& operator<<(std::ios& (*t)(std::ios&)) {
      msg << t;
      return *this;
   }
   error& operator<<(std::ios_base& (*t)(std::ios_base&)) {
      msg << t;
      return *this;
   }
   private:
   std::ostringstream msg;
};
Giovanni Funchal
  • 8,934
  • 13
  • 61
  • 110
2

I'll trot out my favourite macro again here:

#define ATHROW( msg )                                               \
{                                                                   \
    std::ostringstream os;                                          \
    os << msg;                                                      \
    throw ALib::Exception( os.str(), __LINE__, __FILE__ );          \
}                                                                   \

In use:

ATHROW( "Invalid value: " << x << " should be " << 42 );

the exception type is from my own library, but I think you get the idea. This is much simpler than deriving your own stream class, and avoids lots of nasty complications with op<<().

-1

I usually just create my own exception classes. You only have to override what() and can provide as many constructors as you like. To build up the error message, just use vasprintf (if available) or std::ostringstream like above.

Here's an example:

class CustomException : public std::exception {
private:
    const std::string message;
public:
    CustomException(const std::string &format, ...) {
        va_list args;
        va_start(args, format);
        char *formatted = 0;
        int len = vasprintf(&formatted, format.c_str(), args);
        if (len != -1) {
            message = std::string(formatted);
            free(formatted);
        } else {
            message = format;
        }
        va_end(args);
    }
    const char *what() const {
        return message.c_str();
    }
};

If you don't have vasprintf, you can also use vsnprintf with a buffer on the stack...

onitake
  • 1,369
  • 7
  • 14
  • I strongly advice against `vasprintf()`. It is an old C-style function and comes with many caveats, starting with portability issues and that it cannot handle C++ objects, even, if those have a well defined `<<` operator for output to a `std::ostream`. – Kai Petzke Mar 24 '22 at 09:34