使用lambda和宏进行错误处理有什么缺点吗?

时间:2016-04-27 15:55:54

标签: c++ c++11 error-handling

正确测试函数返回值是基本的,但它可以快速混乱代码并使其难以阅读,如下面的简单示例所示:

#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;
}

你怎么看待这个新版本(虽然它仍然使用宏...)?

4 个答案:

答案 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)

对我来说没问题。我不会把所有的鸡蛋都放进这个篮子里,因为你不必为了解决预处理器语法的限制而变得更加复杂,但实际上,这很好。