正确测试函数返回值是基本的,但它可以快速混乱代码并使其难以阅读,如下面的简单示例所示:
#include <iostream>
#include <fstream>
int main(int argc, char **argv)
{
std::string filename("/usr/include/malloc.h");
std::ifstream ifs(filename.c_str());
if (!ifs.is_open())
{
std::cerr << "Failed to open file " << filename << std::endl;
return 1;
}
ifs.close();
std::cout << "Passed the first error handling" << std::endl;
filename = "/this/file/does/not/exist";
ifs.open(filename.c_str());
if (!ifs.is_open())
{
std::cerr << "Failed to open file " << filename << std::endl;
return 1;
}
return 0;
}
我想到了一个通过使用像这样的宏和c ++ 11 lambda函数来减少混乱的解决方案:
#include <iostream>
#include <fstream>
#define RETURN_IF(X,Y,Z) if ( X ) { Y ; return Z; }
auto open_file_error = [](const std::string& filename)
{
std::cerr << "Failed to open file " << filename << std::endl;
};
int main(int argc, char **argv)
{
std::string filename("/usr/include/malloc.h");
std::ifstream ifs(filename.c_str());
RETURN_IF (!ifs.is_open(), open_file_error(filename), 1 );
ifs.close();
std::cout << "Passed the first error handling" << std::endl;
filename = "/this/file/does/not/exist";
ifs.open(filename.c_str());
RETURN_IF (!ifs.is_open(), open_file_error(filename), 1 );
return 0;
}
正如您所看到的,主要功能不那么混乱。您是否认为这样做有缺点或者是否可以成为主要使用的方法?
请注意,我使用多个宏来处理带或不带返回值的情况,以测试与值等的等式。
我建议下面的新版本考虑两件事: - 关于使用异常而不是返回值的偏好的答案和评论; - 放弃强调std :: ifstream特定错误,这些错误不是问题的主题。
#include <iostream>
#include <fstream>
#include <exception>
class OurExceptionForTheExternalLibraryFailure : public std::exception {};
#define CLEANUP_AND_THROW_IF(X,Y,Z) if ( X ) { Y ; throw Z; }
/* Return true in case of succes and false otherwise */
bool anyExternalFunction(const std::string& aString)
{
std::ifstream ifs(aString.c_str());
if (ifs.is_open())
{
ifs.close();
return true;
}
else
{
return false;
}
}
auto this_external_function_error_cleanup = [](const std::string& aString)
{
std::cerr << "The external function failed " << aString << std::endl;
// other stuff
};
int main(int argc, char **argv)
{
try
{
std::string aString = "/usr/include/malloc.h";
bool functionResult = anyExternalFunction(aString);
CLEANUP_AND_THROW_IF (!functionResult, this_external_function_error_cleanup(aString), OurExceptionForTheExternalLibraryFailure() );
std::cout << "Passed the first error handling" << std::endl;
aString = "/this/file/does/not/exist";
functionResult = anyExternalFunction(aString);
CLEANUP_AND_THROW_IF (!functionResult, this_external_function_error_cleanup(aString), OurExceptionForTheExternalLibraryFailure() );
}
catch (const OurExceptionForTheExternalLibraryFailure& e)
{
std::cerr << "Catched OurExceptionForTheExternalLibraryFailure. There was an error" << std::endl;
}
return 0;
}
你怎么看待这个新版本(虽然它仍然使用宏...)?
答案 0 :(得分:4)
好吧,如果您已经在使用lambdas,并且您不希望所有测试代码到处都是,那么您总是可以执行类似的操作(注意:未编译/未经测试的代码)
template <typename FileReader>
void with_file(std::string file, FileReader&& reader) {
std::ifstream in(file);
if (in) {
reader(in);
} else {
throw std::runtime_error("Failed to open file: " + file); // NOTE: I'm being lazy here
}
}
int main(...) {
with_file("foo.txt", [](auto& in) {
// do something with the stream
});
}
..但这是一个偏好问题,我喜欢异常,lambda和小实用功能,但有些可能不会...
答案 1 :(得分:3)
这几乎就是何时使用例外的教科书示例。
但是,您不必编写自己的代码来测试文件是否正确打开,并在失败时抛出异常(等等)。 Iostreams已经相当直接地支持了,所以你可以编写类似这样的代码:#include <fstream>
#include <iostream>
int main() {
try {
std::ifstream in("/usr/include/malloc.h");
in.exceptions(std::ios::failbit);
in.close();
std::cout << "passed first test.\n";
std::ifstream in2("/this/file/does/not/exist");
in2.exceptions(std::ios::failbit);
in2.close();
std::cout << "Passed second test\n";
}
catch (std::system_error &f) {
std::cerr << "Failed to open file: " << f.what() << "\n";
}
}
当然,如果你想从main
获得try / catch,你也可以这样做。我不确定你这样做会有多大收获。
然而,更普遍的是,例外显然是这项工作的正确工具。对于没有提供将异常报告为异常的其他函数,您可能必须编写自己的包装器。但是,无论哪种方式,如果你有一个具有一定范围的正常返回值的函数,以及一个(或几个)&#34;特殊的&#34;值表示失败(和类似),这是一个相当不错的指示,表明它通过返回值表示异常情况 - 处理异常情况的正确方法是通过异常而不是返回值。 / p>
我不会尝试重申(长)列表中的原因/何时/如何使用异常处理,而是将您(作为起点)引用到Herb Sutter关于{{3的旧文章}}
答案 2 :(得分:2)
建议这是一个更清洁的例子。现在使用例外... 我没有测试它与你的例子100%相同的行为(我很欣赏这只是一个例子)。
顺便说一句,MFC有一个“SUCCESS”宏,它会对你的“RETURN_IF”进行类似的检查。我也不喜欢那个宏...
#include <iostream>
#include <fstream>
#include <string>
void TestForFileOpen(const std::string& filename)
{
std::ifstream ifs(filename.c_str());
if (!ifs.is_open())
{
throw std::exception("Failed");
}
}
void ReportFileOpenFailure(const std::string& filename)
{
std::cerr << "Failed to open file " << filename << std::endl;
}
void NoisyTestForFileOpen(const std::string& filename)
{
try
{
TestForFileOpen(filename);
}
catch(...)
{
ReportFileOpenFailure(filename);
throw;
}
}
int main(int argc, char **argv)
{
std::string filename("/usr/include/malloc.h");
try
{
NoisyTestForFileOpen(filename);
std::cout << "Passed the first error handling" << std::endl;
filename = "/this/file/does/not/exist";
NoisyTestForFileOpen(filename);
}
catch (...)
{
return 1;
}
return 0;
}
自定义API的更一般示例:
#include <iostream>
#include <fstream>
#include <string>
class IFileTester
{
public:
virtual ~IFileTester() {}
// throws if file cannot be opened
virtual void TestForFileOpen(const std::string& filename) const = 0;
};
class IfStreamFileTester : public IFileTester // implement as many versions as you need
{
public:
virtual void TestForFileOpen(const std::string& filename) const
{
// implement this in terms of ifstream
std::ifstream ifs(filename.c_str());
// thanks @Jerry-Coffin
ifs.exceptions(std::ios::failbit);
}
};
void ReportFileOpenFailure(const std::string& filename)
{
std::cerr << "Failed to open file " << filename << std::endl;
}
void NoisyTestForFileOpen(const IFileTester& fileTester, const std::string& filename)
{
try
{
fileTester.TestForFileOpen(filename);
}
catch(...)
{
ReportFileOpenFailure(filename);
throw;
}
}
int main(int argc, char **argv)
{
IFileTester& fileTester = IfStreamFileTester();
std::string filename("/usr/include/malloc.h");
try
{
NoisyTestForFileOpen(fileTester, filename);
std::cout << "Passed the first error handling" << std::endl;
filename = "/this/file/does/not/exist";
NoisyTestForFileOpen(fileTester, filename);
}
catch (...)
{
return 1;
}
return 0;
}
答案 3 :(得分:0)
对我来说没问题。我不会把所有的鸡蛋都放进这个篮子里,因为你不必为了解决预处理器语法的限制而变得更加复杂,但实际上,这很好。