我可以扔一条小溪吗?

时间:2016-05-12 15:21:24

标签: c++ exception iostream ostream

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

关注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的行。)

我意识到流没有复制构造函数,但我认为捕获引用而不是对象就足够了。我怎样才能克服这个问题 - 我可以抛弃一个吞下的物体吗?一条小溪?

6 个答案:

答案 0 :(得分:2)

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

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

  1. 它将流媒体的概念与异常的概念结合起来。

  2. 使用单一模板功能

  3. 可以更简单地完成

    事实上,这里有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。

答案 1 :(得分:1)

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

只是一个想法:也许你可以将你的流嵌入一个智能指针,如@Before,然后抛出那个智能指针。

我个人通常在任何地方使用https://en.wikipedia.org/w/api.php?action=query&prop=pageimages&titles=Jaguar&pithumbsize=500

答案 2 :(得分:1)

除了ZunTzu的回答:改为使用ostringstream。

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

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

答案 3 :(得分:1)

  

我可以投一个流吗?

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

来自C ++ 11标准:

  

15.1抛出异常

     

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

答案 4 :(得分:1)

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

#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运算符中的<<变量。在这种情况下,您将能够在throw之前获取异常消息,但是每次调用operator时您都将复制消息,这可能是一种过度杀伤。

答案 5 :(得分:1)

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