异常传播和上下文



在异常中提供良好的上下文信息的推荐做法是什么?为了说明这一点,这是一个基于经验的简化示例。

#include <iostream>
#include <fstream>
#include <boost/filesystem.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/uuid/string_generator.hpp>
#include <boost/algorithm/string.hpp>
class object
{
public:
void parse(std::istream& is)
{
std::istreambuf_iterator<char> eos;
std::string buf(std::istreambuf_iterator<char>(is), eos);
std::vector<std::string> list;
boost::split(list, buf, boost::is_any_of(","), boost::algorithm::token_compress_on);
for (auto str : list)
{
auto id = boost::uuids::string_generator()(str);
}
}
};
void foo()
{
{
boost::filesystem::ifstream is("foo.txt");
object obj;
obj.parse(is);
}
{
boost::filesystem::ifstream is("bar.txt");
object obj;
obj.parse(is);
}
}
int main()
{
try
{
foo();
}
catch (const std::exception& ex)
{
std::cout << ex.what() << std::endl;
}
return 0;
}

此示例从两个文件中读取多个 uuid。如果一个 uuid 格式不正确,则会引发异常,并且输出消息是正确的"无效的 uuid 字符串"。

在这个简单和静态的示例中,这些信息可能足以找出问题,但在更动态和复杂的情况下,这可能还不够。很高兴知道哪个文件包含无效的 uuid,以及可能解析失败的 uuid。显然,boost::uuid::string_generator无法提供所有这些信息。一种方法是尽早捕获原始异常并重新抛出更多信息,但在这种情况下,我需要这样做两次才能首先获取"uuid"值,然后获取"文件名"。

感谢对此事的任何意见。

是的,如果您需要更多信息进行调试,请捕获然后重新抛出信息量更大的错误消息。

void tryParse(string const &filename)
{
boost::filesystem::ifstream is(filename);
object obj;
try{
obj.parse(is);
}
catch(const std::exception& ex) {
throw "Exception occurred whilst parsing " + filename + ex.what());
}
}
void foo()
{
vector<string> files = { "foo.txt", "bar.txt" };
for(auto const& f : files )
{
tryParse(f);
}
}

你可以改用std::optional,这取决于你使用的是哪种C++标准。

这也取决于您记录错误的方式。如果您有某种日志记录机制,则可以在异常发生时记录异常消息和文件名,而不是向上传播信息。

您可以创建一个单独的类来分析和处理相关的异常。在您的示例中,我没有使用增强和功能的经验,所以我不使用它 - 它仍然演示了这个概念,

#include<iostream>
#include <fstream>
#include <exception>
#include <vector>
#include <string>
// Parses a file, collects data if correct,
// if not throws an exception
class Parser
{
public:
// Creates Parser object for parsing file file_name
// verbose indicates whether more detailed exception message
// should be printed
Parser(const std::string file_name, const bool verbose):
fname(file_name), print_ex_info(verbose) { }
// Parsing wrapper that calls actual parsing function
// and handles/prints exceptions
void parse();
// Retrieve parsed data
std::vector<std::string> get_data() const { return data; }
private:
std::vector<std::string> data;
std::string fname = {};
bool print_ex_info = true;
// Actual parsing
void parse_private(); 
};
void Parser::parse()
{
try{
parse_private();
} catch(const std::exception& ex) {
if (print_ex_info){
std::cout << "File " << fname 
<< " thrown an exception " 
<< ex.what() << std::endl;
}else{
std::cout << ex.what() << std::endl;
}
}
}
// Throws if file contains an entry that 
// is not a positive integer 
// (for simple demonstration)
void Parser::parse_private()
{
std::ifstream in(fname);
std::string entry;
while (in >> entry){
if (entry.find_first_not_of("0123456789") != std::string::npos){
throw std::runtime_error("Invalid entry " + entry + "n");
}else{
data.push_back(entry);  
}
}
}
// Retrieves and uses data parsed from a file 
class Object
{
public:
void parse(const std::string file_name, const bool verbose)
{
Parser file_parser(file_name, verbose);
file_parser.parse();
parsed_data = file_parser.get_data();
}
void print_parsed_data()
{ 
for (const auto& entry : parsed_data)
std::cout << entry << " ";
std::cout << std::endl;
}
private:
std::vector<std::string> parsed_data;
};
int main()
{
Object obj;
bool verbose = true;
// Correct input case
std::cout << "No exception:n";
obj.parse("parser_no_ex.txt", verbose);
obj.print_parsed_data();
std::cout << "n";
// Invalid input, detailed exception info
std::cout << "Exception - verbose version:n";
obj.parse("parser_invalid.txt", verbose);
// Invalid input, reduced exception info
std::cout << "Exception - minimal version:n";
verbose = false;
obj.parse("parser_invalid.txt", verbose);
return 0;
}

在这里,Object充当检索和使用解析数据的中间类。解析的数据在Parser对象中生成,该对象还执行数据检查并引发异常。这样,使用数据的代码就不会与解析相关的异常处理混乱 - 这适用于使用Object的代码和Object本身的功能。

出于类似(混乱、可读性(的原因,Parser有一个额外的代码层用于解析 - 公共函数parse()具有异常处理代码,并调用实际的私有解析函数,该函数没有处理代码,但可以抛出。

此外,分析还有一个verbose选项,用于控制用户在引发异常后希望看到的信息量。能够关闭更多信息异常并不是一个坏主意。

作为参考,这些是本演示中使用的文件,

具有正确输入的文件(所有正整数( -parser_no_ex.txt

123
456
900000
111
00

输入不正确的文件 -parser_invalid.txt

123
456789
***Hello***
345

最新更新