2

I'm trying to develop an Exception class, that allows collecting relevant data stream-style.

Following Custom stream to method in C++? I've extended my own class:

class NetworkException : public std::exception, public std::ostream

to pick the error data from a stream and then return whatever it acquired through .what().

Then I tried something like this:

try {
    ssize_t ret = send(sock, headBuffer, headLength, MSG_MORE);

    if (ret <= 0)  throw NetworkException() << "Error sending the header: " << strerror(errno);

    // much more communication code

} catch (NetworkException& e) {
    connectionOK = false;
    logger.Warn("Communication failed: %s",e.what());
}

But the compiler produces an error:

HTTPClient.cpp:246:113: error: use of deleted function 'std::basic_ostream<char>::basic_ostream(const std::basic_ostream<char>&)'

(that's the line with the throw.)

I realize streams don't have copy constructors, but I thought catching a reference instead of the object would suffice. How can I overcome this - can I throw an object that 'swallows' a stream?

Community
  • 1
  • 1
SF.
  • 13,549
  • 14
  • 71
  • 107
  • Possible duplicate of [C++: Throwing an exception invokes the copy constructor?](http://stackoverflow.com/questions/10855506/c-throwing-an-exception-invokes-the-copy-constructor) – Mohamad Elghawi May 12 '16 at 15:22
  • @MohamadElghawi: Well, then this becomes "Can I somehow create a copy constructor for a stream object?" – SF. May 12 '16 at 15:26

6 Answers6

3

What you want to do has been tried before by many people. Of course it's possible but requires some tricks (similar to the ones required to make streaming loggers).

It also turns out to be a bad idea because:

  1. it couples the concept of streaming to the concept of an exception.

  2. It can be done more simply with a single template function

In fact, here are 3 very simple alternatives:

#include <iostream>
#include <sstream>
#include <exception>
#include <stdexcept>
#include <boost/format.hpp>

template<class...Args>
std::string collect(Args&&...args)
{
    std::ostringstream ss;
    using expand = int[];
    void(expand{0, ((ss << args), 0)...});
    return ss.str();
}

struct collector : std::ostringstream
{
    operator std::string() const {
        return str();
    }
};

// derive from std::runtime_error because a network exception will always
// be a runtime problem, not a logic problem
struct NetworkException : std::runtime_error
{
    using std::runtime_error::runtime_error;
};

int main()
{
    try {
        throw NetworkException(collect("the", " cat", " sat on ", 3, " mats"));

    } catch (const std::exception& e) {
        std::cout << e.what() << std::endl;
    }

    try {
        throw NetworkException(collector() << "the cat sat on " << 3 << " mats");

    } catch (const std::exception& e) {
        std::cout << e.what() << std::endl;
    }

    try {
        throw NetworkException((boost::format("the cat sat on %1% mats") % 3).str());

    } catch (const std::exception& e) {
        std::cout << e.what() << std::endl;
    }


    return 0;
}

expected output:

the cat sat on 3 mats
the cat sat on 3 mats
the cat sat on 3 mats

And finally, probably the most stream-like solution:

template<class Exception>
struct raise
{
    [[noreturn]]
    void now() const {
        throw Exception(_ss.str());
    }

    std::ostream& stream() const { return _ss; }

    mutable std::ostringstream _ss;
};

template<class Exception, class T>
const raise<Exception>& operator<<(const raise<Exception>& r, const T& t)
{
    using namespace std;
    r.stream() << t;
    return r;
}

struct now_type {};
static constexpr now_type now {};

template<class Exception>
void operator<<(const raise<Exception>& r, now_type)
{
    r.now();
}

call site example:

raise<NetworkException>() << "the cat " << "sat on " << 3 << " mats" << now;

I've used the sentinel now in order to avoid any nasty destructor jiggery-pokery.

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • I seriously consider cheating a little on the `collector class` approach: Overload the exception's `operator<<(collector&)` without making it a child of any "stream". That way it will look syntactically consistent, collector will be treated as converter to "throwable" type, and the exception will not be tightly coupled with any alien concepts. – SF. May 12 '16 at 16:08
  • @SF. see fourth option at the end. – Richard Hodges May 12 '16 at 16:22
  • @SF. on second thoughts, that's not safe. will remove it. – Richard Hodges May 12 '16 at 16:30
  • @SF. safer replacement added for option 4 – Richard Hodges May 12 '16 at 16:56
2

In addition to ZunTzu's answer: use an ostringstream instead.

::std::ostringstream what;
what << "Error sending the header: " << strerror(errno);
throw ::std::exception(what.str());

Actually, ::std::exception has no constructor taking char const* or ::std::string const&, so you either need to use an appropriate existing subclass or create your own one.

Aconcagua
  • 24,880
  • 4
  • 34
  • 59
2

Simpler would be to not store std::ostringstream, something like this:

struct NetworkException : std::exception
{
    using std::exception::exception;

    template <class T>
    NetworkException& operator<<(const T& arg) {
        std::ostringstream oss; 
        oss << arg;
        err.append(oss.str());
        return *this;
    }

    const char* what() const noexcept override {
        return err.c_str();
    }

private:
    std::string err;
};
  • This is quite clean, but the main issue is that the implementation will copy the contents of `err` multiple times, once for every operand to `<<`. – NicholasM Feb 06 '20 at 18:03
1

Exceptions are always copied at least once. Catching the exception by reference avoids a second copy.

Just an idea: maybe you can embed your stream in a smart pointer such as std::shared_ptr and then throw that smart pointer.

Personally I usually use std::runtime_error everywhere.

jotik
  • 17,044
  • 13
  • 58
  • 123
ZunTzu
  • 7,244
  • 3
  • 31
  • 39
  • 2
    Personally I avoid using `` exceptions like `std::runtime_error` because they [might throw during construction](http://stackoverflow.com/q/36106747/3919155). – jotik May 12 '16 at 15:31
  • @jotik True. Exceptions are sort of broken in C++ anyway. – ZunTzu May 12 '16 at 15:36
1

Can I throw a stream?

No, you cannot. When an exception is thrown, a temporary object is constructed from the exception-expression. Since stream objects cannot be constructed from another stream object, a stream object cannot be thrown.

From the C++11 standard:

15.1 Throwing an exception

3 A throw-expression initializes a temporary object, called the exception object, the type of which is determined by removing any top-level cv-qualifiers from the static type of the operand of throw and adjusting the type from “array of T” or “function returning T” to “pointer to T” or “pointer to function returning T”, respectively. The temporary is an lvalue and is used to initialize the variable named in the matching handler (15.3).

R Sahu
  • 204,454
  • 14
  • 159
  • 270
1

You cannot copy object containing stream object using default copy constructor, but you can write your own copy constructor that will copy content of stream.

#include <iostream>
#include <sstream>

struct StreamableError : public std::exception {
        template <typename T>
        StreamableError& operator << (T rhs) {
            innerStream << rhs;
            return *this;
        }

        StreamableError() = default;

        StreamableError(StreamableError& rhs) {
                innerStream << rhs.innerStream.str();
        }

        virtual const char* what() const noexcept {
            str = innerStream.str();  //this can throw
            return str.c_str();
        }

    private:
        std::stringstream innerStream;
        mutable std::string str;
};


int main() {
        try {
                throw StreamableError() << "Formatted " << 1 << " exception.";
        } catch (std::exception& e) {
                std::cout << e.what() << std::endl;
        }
}

Solution above is not perfect. If str = innerStream.str() throws then std::terminate will be called. If you want to make what method to be trully noexcept then you have two choices:

  1. Copy content of stream to str variable inside copy constructor. Then you will not be able to call what method to obtain exception message before you throw.
  2. Copy content of stream to str variable inside copy constructor and << operator. In this case you will be able to obtain exception message before throw, but you will be copying message each time operator is called, which may be an overkill.
aadam
  • 713
  • 5
  • 18
  • If plain copy of a stupid short string throws, then the general situation is disastrous enough that the exception finishing successfully won't help any. It's not like the program could recover normal operation gracefully in such a situation, or the cause of the situation would be anywhere near that exception. It's putting a lipstick on a corpse - if I can't depend on ability to copy some 50 characters, the best the system can do is let the watchdog kick. Under debugger it won't matter. Without debugger, it's beyond recovery anyway. – SF. May 12 '16 at 20:25