0

在 Cygwin 1.7.24 上使用 GCC 4.7.3。编译器选项包括: -std=gnu++11 -Wall -Wextra

我正在开发一个命令行应用程序,我需要能够加载和保存一组字符串,所以我围绕 std::set 编写了一个快速包装类来添加加载和保存方法。

// KeySet.h

#ifndef KEYSET_H
#define KEYSET_H

#include <cstdlib>
#include <sys/stat.h>
#include <cerrno>
#include <cstring>

#include <string>
#include <set>
#include <iostream>
#include <fstream>

inline bool file_exists (const std::string& filename)
{
/*
    Utility routine to check existance of a file.  Returns true or false,
    prints an error and exits with status 2 on an error.
*/
    struct  stat buffer;
    int     error = stat(filename.c_str(), &buffer);
    if (error == 0) return true;
    if (errno == ENOENT) return false;
    std::cerr << "Error while checking for '" << filename << "': " << strerror(errno) << std::endl;
    exit (2);
}

class KeySet
{
private:
    std::string             filename;
    std::set<std::string>   keys;

public:
    KeySet() {}
    KeySet(const std::string Pfilename) : filename(Pfilename) {}

    void set_filename (const std::string Pfilename) {filename = Pfilename;}
    std::string get_filename () {return filename;}
    auto size () -> decltype(keys.size()) {return keys.size();}
    auto cbegin() -> decltype(keys.cbegin()) {return keys.cbegin();}
    auto cend() -> decltype(keys.cend()) {return keys.cend();}
    auto insert(const std::string key) -> decltype(keys.insert(key)) {return keys.insert(key);}
    void load ();
    void save ();
};

void KeySet::load ()
{
    if (file_exists(filename)) {
        errno = 0;
        std::ifstream   in (filename, std::ios_base::in);

        if (in.fail()) {
            std::cerr << "Error opening '" << filename << "' for reading: " << strerror(errno) << std::endl;
            exit (2);
        }

        std::string     token;
        if (token.capacity() < 32) token.reserve(32);

        while (in >> token) keys.insert(token);

        if (!in.eof()) {
            std::cerr << "Error reading '" << filename << "': " << strerror(errno) << std::endl;
            exit (2);
        }

        in.clear(); // need to clear flags before calling close
        in.close();
        if (in.fail()) {
            std::cerr << "Error closing '" << filename << "': " << strerror(errno) << std::endl;

            exit (2);
        }
    }
}

void KeySet::save ()
{
    errno = 0;
    std::ofstream   out (filename, std::ios_base::out);

    if (out.fail()) {
        std::cerr << "Error opening '" << filename << "' for writing: " << strerror(errno) << std::endl;
        exit (2);
    }

    for (auto key = keys.cbegin(), end = keys.cend(); key != end; ++key) {
        out << *key << std::endl;
    }

    out.close();
    if (out.fail()) {
        std::cerr << "Error writing '" << filename << "': " << strerror(errno) << std::endl;
        exit (2);
    }
}

#endif

//

这是一个测试加载方法的快速程序。

// ks_test.cpp

#include "KeySet.h"

int main()
{
    KeySet          test;
    std::string     filename = "foo.keys.txt";

    test.set_filename(filename);

    test.load();

    for (auto key = test.cbegin(), end = test.cend(); key != end; ++key) {
        std::cout << *key << std::endl;
    }
}

数据文件中只有“一二三”。

当我去运行测试程序时,我的测试程序出现以下错误:

$ ./ks_test
Error closing 'foo.keys.txt': No error

cppreference.com和cplusplus.com都说close 方法应该在错误时设置失败位。保存方法工作正常,如果我在关闭后注释掉错误检查,加载方法工作正常。这真的应该有效还是我误解了应该如何接近?提前致谢。

根据 Joachim Pileborg 和 Konrad Rudolph 的评论进行了编辑,以澄清、修复错字并调整代码。

编辑为代码添加解决方案。

4

2 回答 2

6

您在这里有两个错误:第一个是关于您如何阅读,更具体地说是阅读循环。直到您尝试读取并且读取失败之后eof才会设置该标志。相反,您应该这样做:

while (in >> token) { ... }

否则,您将循环一次到多次并尝试读取文件末尾之外的内容。

第二个问题是您注意到的问题,它取决于第一个问题。由于您尝试读取文件末尾之外的内容,因此即使没有真正的错误,流也会设置failbit导致in.fail()返回。true

于 2013-09-04T07:02:00.230 回答
0

事实证明, ifstream 的 close 方法(我假设所有其他 IO 对象)在关闭文件之前不会 清除错误标志。这意味着如果您在关闭期间检查错误,则需要在文件结束后关闭流之前添加显式 clear() 调用。就我而言,我in.clear();在通话之前添加了in.close();它,它按我的预期工作。

于 2013-09-05T17:47:19.830 回答