书中的示例无法编译,将 ifstream 转换为 bool

Example from book failing to compile, converting ifstream into bool

我是C++的学生。我正在阅读“从 C++ 早期对象开始(第 9 版)”一书。第 6 章(关于函数)中的示例 27 从文件中读取数据但不会编译。这是完整代码:

// Program 6-27
#include <iostream>
#include <string>
#include <fstream>
#include <iomanip>
using namespace std;

// Function prototype
bool readData(ifstream &someFile, string &city, double &rain);

int main()
{
    ifstream inputFile;
    string city;
    double inchesOfRain;

    // Display table headings
    cout << "July Rainfall Totals for Selected Cities \n\n";
    cout << " City      Inches \n";
    cout << "_________________ \n";

    // Open the data file
    inputFile.open("rainfall.dat");
    if (inputFile.fail())
    cout << "Error opening data file.\n";
    else
    {
        // Call the readData function
        // Execute the loop as long as it found and read data
        while (readData(inputFile, city, inchesOfRain) == true)
        {
            cout << setw(11) << left << city;
            cout << fixed << showpoint << setprecision(2)
                << inchesOfRain << endl;
        }
        inputFile.close();
    }
    return 0;
}

bool readData(ifstream &someFile, string &city, double &rain)
{
    bool foundData = someFile >> city >> rain;
    return foundData;
}

这里是数据文件的附带数据 Rainfall.dat:

Chicago 3.70
Tampa 6.49
Houston 3.80

问题出在 "bool readData" 函数中的这一行:

bool foundData = someFile >> city >> rain;

我正在使用 Visual Studio Community 2017。"someFile" 得到一条红色波浪线,下拉菜单显示以下错误:

no suitable conversion function from "std::basic_istream<char, std::char_traits<char>>" to "bool" exists

我不太理解错误消息,但已设法使该程序与以下项目一起工作:

一个简单的转换:

bool readData(ifstream &someFile, string &city, double &rain)
{
    return static_cast<bool>(someFile >> city >> rain);
}

或者这个作为替代:

bool readData(ifstream &someFile, string &city, double &rain)
{
    if(someFile >> city >> rain)
        return true;
    else
        return false;
}

所以,我真正的问题是:

流有一个成员

explicit operator bool() const;

这使得它可以转换为 bool 值,但是因为运算符是 explicit 这只在 需要 布尔值的上下文中有效。

您已经发现这包括 if 语句和显式强制转换。它不包括其他类型的表达式,例如赋值。

最初 (C++98) 运算符不是 explicit(因为还没有发明这样的东西)所以代码示例当时可能会起作用。这部分好像没更新。

流的 operator bool 在基 class basic_ios 中声明如下

explicit operator bool() const;
^^^^^^^^

所以没有从 std::basic_ifstream 到类型 bool 的隐式转换。

这个解决方案

bool readData(ifstream &someFile, string &city, double &rain)
{
    return static_cast<bool>(someFile >> city >> rain);
}

看起来不错。

您还可以通过以下方式使用逻辑否定运算符的技巧

bool readData(ifstream &someFile, string &city, double &rain)
{
    return !!(someFile >> city >> rain);
}

因为根据 C++ 标准(5.3.1 一元运算符)

9 The operand of the logical negation operator ! is contextually converted to bool (Clause 4); its value is true if the converted operand is false and false otherwise. The type of the result is bool.

虽然在我看来第一个函数实现更具可读性。

我会考虑

  • 返回 std::ios& 以推迟上下文转换为 bool

    std::ios& readData(std::ifstream &someFile, std::string &city, double &rain) {
        return someFile >> city >> rain;
    }
    

    结果是您可以像这样简单地使用它:

    if (readData(file, city, rain)) {
        // ...
    }
    

    接口将通过仅包含 #include <iosfwd>

  • 进行编译

  • 手动触发上下文转换:

    bool readData(std::ifstream &someFile, std::string &city, double &rain) {
        return bool{someFile >> city >> rain};
    }
    

我想他们直到本书的后面才会介绍它,但我会以不同的方式处理这个问题。我首先定义一个结构来保存城市的名称和降雨量:

struct precipitation { 
    std::string location;
    double amount;
};

然后我将定义 operator>> 的重载以从流中提取该类型的对象:

std::istream &operator>>(std::istream &is, precipitation &p) { 
     return is >> p.location >> p.amount;
}

这几乎是流提取器的标准形式——引用一个流和一个被提取类型的对象的引用,return 流(再次通过引用)。

这可以让您阅读得相当清晰:

precipitation precip;

std::ifstream in("rainfall.dat");

while (in >> precip)
    std::cout << "City: " << precip.location << ", amount: " << precip.amount << "\n";

如果您想知道它是如何工作的:流对象支持转换为布尔值,当从流中读取成功时生成 true,失败时生成 false,所以这从流中读取,直到读取失败,然后停止。

这也是流迭代器所期望的形式,因此您也可以使用它们。例如,要将文件的全部内容读入向量,您可以这样做:

std::vector<precipitation> data { std::istream_iterator<precipitation>(in),{}};