如果构造函数中有错误,则停止进程

Stop process if there is an error in the constructor

在实用程序 class 文件中,我想打开一个文件来读取或写入它。
如果我打不开,我不想继续这个过程。

FileUtility::FileUtility(const char *fileName) {
  ifstream in_stream;
  in_stream.open(filename);
}

FileUtility fu = FileUtility("bob.txt");
fu.read();
fu.write();

文件bob.txt不存在,所以我不想读写方法。

有干净的方法吗?

当在 C++ 中构建对象失败时,您应该抛出异常,或者从失败的子对象构建中传播异常。

FileUtility(const char* filename) {
    std::ifstream in_stream;
    in_stream.exceptions(std::ios_base::failbit);
    in_stream.open(filename); // will throw if file can't be opened
}

调用代码中可以选择异常处理:

try {
    FileUtility fu = FileUtility("bob.txt");
} catch (std::ios_base::failure) {
    printf("Failed to open bob.txt\n");
    exit(EXIT_FAILURE);
}
// do other stuff

或者,如果您没有捕获异常,运行时将只调用 std::terminate(),它将打印出自己的错误消息,这可能有用也可能没有用:

terminate called after throwing an instance of 'std::ios_base::failure'
  what():  basic_ios::clear
Aborted

通常有四种方法可以将错误状态从被调用方传达给调用方:

1.直接return值(return代码或OUT参数)。

A return 代码对于构造函数调用是不可能的,尽管 OUT 参数是。然而,要求每个函数为此目的提供其 return 代码或 OUT 参数有点侵入性,所以我一般不喜欢这个解决方案,尽管它肯定在各种库中大量使用 API秒。您可以通过向构造函数添加指针或引用参数来使用此方法,调用者可以向其提供某个局部错误变量的地址,构造函数可以在其中存储可能的 return 值。我不推荐这个。

2。异常。

在 C++ 代码和其他语言中,关于异常的优缺点存在着一些两极分化的争论。我可能会因为这样说而投反对票,但我个人认为应该像避免瘟疫一样避免例外情况。请参阅 http://www.joelonsoftware.com/items/2003/10/13.html 以了解与我观点相同的人。但如果您愿意,这是一个可行的解决方案。有关此解决方案的良好演示,请参阅@Brian 的回答。

3。对象属性.

std::ifstream 对象实际上执行此操作,因此您可以利用它。 (实际上,从您的示例代码中,您将 std::ifstream 定义为构造函数中的局部变量,这意味着它不会在调用后持续存在,但是由于您调用了某种 read()write() 构造对象上的方法,这意味着你 do 在调用后保留它,所以我假设后者是正确的推论。)你可以利用它,通过调用 std::ifstream::is_open()。如果你想保持 std::ifstream 的封装,你可以在 FileUtility 上定义你自己的 is_open() 等价物,这将再次简单地 return in_stream.is_open();,假设它是作为属性保留在 FileUtility class.

struct FileUtility {
    ifstream ifs;
    FileUtility(const char* fileName);
    bool is_open(void) const;
};

FileUtility::FileUtility(const char* fileName) { ifs.open(fileName); }
bool FileUtility::is_open(void) const { return ifs.is_open(); }

FileUtility fu = FileUtility("bob.txt");
if (!fu.is_open()) return 1;

或者,您可以仅为 FileUtility class 创建一个全新的错误状态层,并通过它传播 std::ifstream 错误。例如:

struct FileUtility {
    static const int ERROR_NONE = 0;
    static const int ERROR_BADFILE = 1;
    ifstream ifs;
    int error;
    FileUtility(const char* fileName);
};

FileUtility::FileUtility(const char* fileName) : error(ERROR_NONE) {
    ifs.open(fileName);
    if (!ifs.is_open()) { error = ERROR_BADFILE; return; }
}

FileUtility fu = FileUtility("bob.txt");
if (fu.error != FileUtility::ERROR_NONE) return 1;

这些都是合理的解决方案。

4.全局错误状态。

如果一些程序员对这个可能的解决方案做出 "that sounds like a bad idea" 反应,我不会感到惊讶,但事实是许多非常成功和突出的代码库使用这个解决方案来传达错误状态。也许最好的例子是 Windows C API 使用的 errno variable used by the C Standard Library (although it should be mentioned that errno sort of works in conjunction with direct return codes), and the GetLastError() 系统。我想有些人可能会争辩说那真的是 "C approach",例外是 "C++ approach",但同样,我避免像瘟疫这样的例外。

顺便说一句,多线程不是这个解决方案的问题,因为 errnoGetLastError() 都使用线程局部错误状态,而不是真正的全局错误状态。

我最喜欢这个解决方案,因为它简单,非常无创,并且可以很容易地被不同的代码库重用,当然前提是你定义了错误框架(基本上是线程局部变量,可能还有 ERROR_NONE macro/global;见下文)在它自己的库中,在这种情况下,您的代码在错误处理方面会获得一致性。

示例:

#define ERROR_NONE 0
thread_local int error = ERROR_NONE;

struct FileUtility {
    static const int ERROR_BADFILE = 1;
    ifstream ifs;
    FileUtility(const char* fileName);
};

FileUtility::FileUtility(const char* fileName) {
    ifs.open(fileName);
    if (!ifs.is_open()) { error = ERROR_BADFILE; return; }
}

FileUtility fu = FileUtility("bob.txt");
if (error != ERROR_NONE) return 1;

这是我推荐的解决方案;否则我会选择对象属性解决方案。