views:

237

answers:

3

Hi, 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...

+3  A: 

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.

GMan
I won't really throw const char*s. It was just a concept.
Helltone
You should not throw exceptions in the destructor (http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.3).
Helltone
@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. :)
GMan
@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.
Charles Bailey
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).
Helltone
@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.
Charles Bailey
@Charles: Thanks for the link, never read that one. @Helltone: You're right, how's that?
GMan
Why error_builder does not inherit from std::ostringstream instead?
Helltone
Helltone
Helltone
@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.
GMan
+1  A: 

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<<().

anon
Sometimes, macros are the best solution.
Helltone
A: 

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;
};
Helltone
Yup, thanks. I forgot about all those. I would recommend removing `uncaught_exception` per Sutter (Charles linked to it). You can simplify the constructor by just streaming in the string, same result. Otherwise this is good.
GMan