在向您提供API时,您经常需要声明错误代码(最常见的是int
),之后您经常需要提供一个转换int
错误代码的函数,std::string
以便能够以智能方式向用户报告错误.
我发现了一些关于如何以编程方式维护int
/ std::string
映射的帖子,如下所示:将错误代码映射到C++中的字符串
现在,我在想,为什么不简单地回归std::string
而不是int
?空字符串意味着没有错误,其他任何意味着错误+提供人类可读消息.
我们显然假设您不关心内存使用和性能(您的API通常不会调用函数,执行时间并不重要).
如果您需要客户端能够以编程方式执行某些特定操作,则可以将错误代码声明为常量.但是,你不需要任何int
来std::string
映射了.例如,它将是:
宣言:
static const std::string successMessage; static const std::string fileDoesNotExistMessage; static const std::string internalErrorMessage; std::string openFile( const std::string& fileName );
执行:
static const std::string successMessage = ""; static const std::string fileDoesNotExistMessage = "File does not exist"; static const std::string internalErrorMessage = "Internal error"; std::string openFile( const std::string& fileName ) { if ( ... ) // test file existance { if ( ... ) // internal tests return internalErrorMessage; else return successMessage; } else { return fileDoesNotExistMessage ; } }
然后API用户可以:
int main() { std::string error = openFile( "file.txt" ); if ( error.empty() ) { std::cout << "File was successfully opened" << std::endl; } else if ( error == fileDoesNotExistMessage ) { // specific error handling } else { std::cout << "Unable to open file, error reported is " << error << std::endl; } }
好处:
没有int
/有std::string
破坏映射的风险
易于添加错误代码
易于维护
必须有不满,因为我不知道使用这种方法的开源库...任何想法为什么?
为什么不再进一步做正确的事(tm)?
报告异常错误.这就是他们的目的.
例:
struct file_does_not_exist : std::runtime_error { using std::runtime_error::runtime_error; }; struct internal_error : std::runtime_error { using std::runtime_error::runtime_error; }; void openFile( const std::string& fileName ) { if (!fileExists()) throw file_does_not_exist(fileName + " does not exist"); if (!internal_tests(fileName)) throw internal_error("internal tests failed"); doLogic(); }
用户代码现在变为:
try { openFile("xyz.txt"); do_other_logic(); ... } catch(const std::exception& e) { std::cerr << "failed because: " << e.what() << std::endl; }
#include#include #include #include #include // define our program's exceptions, deriving from standard types struct failed_to_open : std::runtime_error { using std::runtime_error::runtime_error; }; struct database_op_failed : std::runtime_error { using std::runtime_error::runtime_error; }; struct user_update_failed : std::runtime_error { using std::runtime_error::runtime_error; }; // copied from cppreference.com void print_exception(const std::exception& e, int level = 0) { std::cerr << std::string(level, ' ') << "exception: " << e.what() << '\n'; try { std::rethrow_if_nested(e); } catch(const std::exception& e) { print_exception(e, level+1); } catch(...) {} } void open_file(std::fstream& f, const std::string& filename) { throw failed_to_open(std::string("failed to open file " + filename)); } struct database_connection { database_connection() try { open_file(_file, "database.txt"); } catch(...) { std::throw_with_nested(database_op_failed("database failed to create")); } std::fstream _file; }; // note the use of function try blocks coupled with // throw_with_nested to make exception handling *clean and easy* void update_user_age(const std::string& name, int newage) try { database_connection d; // d.update_record(...) // ... RAII } catch(...) { std::throw_with_nested(user_update_failed("failed to update user " + name + " to age " + std::to_string(newage))); } // only one try/catch in the whole program... int main() { try { update_user_age("bob", 30); // ... lots more log here with no need to check errors } catch(const std::exception& e) { // ...which completely explains the error print_exception(e); return(100); } return 0; }
预期产量:
exception: failed to update user bob to age 30 exception: database failed to create exception: failed to open file database.txt