【问题标题】:Can I throw a stream?我可以抛出一个流吗?
【发布时间】:2016-05-12 15:21:23
【问题描述】:

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

按照Custom stream to method in C++?,我扩展了自己的课程:

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

从流中挑选错误数据,然后返回它通过.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的行。)

我意识到流没有复制构造函数,但我认为捕获引用而不是对象就足够了。我该如何克服这个问题 - 我可以抛出一个“吞下”流的对象吗?

【问题讨论】:

标签: c++ exception iostream ostream


【解决方案1】:

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

这也是一个坏主意,因为:

  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 以避免任何讨厌的析构函数。

【讨论】:

  • 我认真考虑在 collector class 方法上作弊:重载异常的 operator&lt;&lt;(collector&amp;) 而不使其成为任何“流”的子项。这样它看起来在语法上是一致的,收集器将被视为“可抛出”类型的转换器,并且异常不会与任何外来概念紧密耦合。
  • @SF。见最后的第四个选项。
  • @SF。再想一想,这不安全。将删除它。
  • @SF。为选项 4 添加了更安全的替换
【解决方案2】:

除了 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 或创建自己的。

【讨论】:

    【解决方案3】:

    更简单的方法是不存储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;
    };
    

    【讨论】:

    • 这很干净,但主要问题是实现会多次复制err的内容,每个操作数复制一次到&lt;&lt;
    【解决方案4】:

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

    #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. 将流的内容复制到复制构造函数和&lt;&lt; 运算符内的str 变量。在这种情况下,您将能够在 throw 之前获取异常消息,但是每次调用 operator 时您都将复制消息,这可能有点过头了。

    【讨论】:

    • 如果抛出一个愚蠢的短字符串的普通副本,那么一般情况是灾难性的,成功完成异常将无济于事。在这种情况下,程序无法优雅地恢复正常操作,或者导致这种情况的原因可能是该异常附近的任何地方。它是在尸体上涂口红——如果我不能依靠复制大约 50 个字符的能力,那么系统能做的最好的事情就是让看门狗踢。在调试器下没关系。没有调试器,无论如何都无法恢复。
    【解决方案5】:

    异常总是至少被复制一次。通过引用捕获异常可以避免第二次复制。

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

    我个人通常在任何地方都使用std::runtime_error

    【讨论】:

    • 我个人避免使用&lt;stdexcept&gt; 之类的异常std::runtime_error,因为它们might throw during construction
    • @jotik 是的。无论如何,异常在 C++ 中都被破坏了。
    【解决方案6】:

    我可以抛出一个流吗?

    不,你不能。当抛出异常时,会从 exception-expression 构造一个临时对象。由于无法从另一个流对象构造流对象,因此无法抛出流对象。

    来自 C++11 标准:

    15.1 抛出异常

    3 throw-expression 初始化一个临时对象,称为 exception object,其类型通过删除任何顶级 cv-qualifiers 来确定 来自throw 操作数的静态类型,并将类型从“T 的数组”或“返回 T 的函数”分别调整为“指向 T 的指针”或“指向返回 T 的函数的指针”。临时值是一个左值,用于初始化匹配的 handler (15.3) 中命名的变量。

    【讨论】:

      猜你喜欢
      • 2014-03-10
      • 1970-01-01
      • 2011-03-20
      • 2022-11-27
      • 2013-07-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-19
      相关资源
      最近更新 更多