我可以扔流吗?

Can I throw a stream?

我正在尝试开发一个异常 class,它允许收集相关数据流样式。

Custom stream to method in C++? 之后我扩展了自己的 class:

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

从流中选取错误数据,然后 return 通过 .what().

获取的任何数据

然后我尝试了这样的事情:

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

但编译器产生错误:

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

(这是带有 throw 的那一行)

我知道流没有复制构造函数,但我认为捕获引用而不是对象就足够了。我怎样才能克服这个问题 - 我可以抛出一个 'swallows' 流的对象吗?

异常总是至少被复制一次。通过引用捕获异常避免了第二个副本。

只是一个想法:也许您可以将您的流嵌入到智能指针中,例如 std::shared_ptr,然后抛出该智能指针。

就个人而言,我通常到处都使用 std::runtime_error

除了ZunTzu的回答:改用ostringstream。

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

实际上,::std::exception 没有采用 char const* 或 ::std::string const& 的构造函数,因此您需要使用适当的现有 subclass 或创建您自己的构造函数。

你想做的事情已经被很多人尝试过了。这当然是可能的,但需要一些技巧(类似于制作流式记录器所需的技巧)。

事实证明这也是一个坏主意,因为:

  1. 它将流的概念与异常的概念相结合。

  2. 用一个模板函数就可以更简单

事实上,这里有 3 个非常简单的选择:

#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;
}

预期输出:

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

最后,可能是最像流的解决方案:

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

调用站点示例:

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

我使用了哨兵 now 以避免任何讨厌的析构函数 jiggery-pokery。

Can I throw a stream?

不,你不能。抛出异常时,将根据 exception-expression 构造一个临时对象。由于不能从另一个流对象构造流对象,因此不能抛出流对象。

来自 C++11 标准:

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

您不能使用默认复制构造函数复制包含流对象的对象,但您可以编写自己的复制构造函数来复制流的内容。

#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;
        }
}

上面的解决方案并不完美。如果 str = innerStream.str() 抛出然后 std::terminate 将被调用。如果你想让 what 方法成为真正的 noexcept 那么你有两个选择:

  1. 将流的内容复制到复制构造函数中的 str 变量。那么你将无法在抛出之前调用what方法来获取异常消息。
  2. 将流的内容复制到复制构造函数和 << 运算符中的 str 变量。在这种情况下,您将能够在抛出之前获取异常消息,但是每次调用运算符时您都将复制消息,这可能是一种矫枉过正。

更简单的方法是不存储 std::ostringstream,像这样:

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