在函数中返回 ifstream

15

这里有一个可能非常新手的问题:如何(如果可能的话)从函数中返回一个 ifstream?

基本上,我需要从用户那里获取数据库的文件名,如果该文件名对应的数据库不存在,则需要为用户创建该文件。我知道如何做到这一点,但是只能通过要求用户在创建文件后重新启动程序来实现。如果可能的话,我想避免给用户带来这种不便,但是下面的函数无法在gcc中编译:

ifstream getFile() {
    string fileName;
    cout << "Please enter in the name of the file you'd like to open: ";
    cin >> fileName;
    ifstream first(fileName.c_str());
    if(first.fail()) {
        cout << "File " << fileName << " not found.\n";
        first.close();
        ofstream second(fileName.c_str());
        cout << "File created.\n";
        second.close();
        ifstream third(fileName.c_str());
        return third; //compiler error here
    }
    else
        return first;
}

编辑:抱歉,忘记告诉您编译错误出现在哪里以及是什么:

main.cpp:45: note: synthesized method ‘std::basic_ifstream<char, std::char_traits<char> >::basic_ifstream(const std::basic_ifstream<char, std::char_traits<char> >&)’ first required here 

编辑:我根据Remus的建议将函数更改为返回指针,并将main()中的行更改为“ifstream database = *getFile()”;现在我再次遇到此错误,但这次出现在main()中的一行:

main.cpp:27: note: synthesized method ‘std::basic_ifstream<char, std::char_traits<char> >::basic_ifstream(const std::basic_ifstream<char, std::char_traits<char> >&)’ first required here

3
你列出的不是编译器错误,而是“注释”。请查看实际错误。 - Kaz Dragon
4个回答

19
不会的。`ifstream` 没有复制构造函数,如果你尝试返回一个对象,那么就需要将该对象从函数中复制到需要返回的位置。
通常的解决方法是传递一个引用并在函数中修改该引用。
编辑:虽然这样可以让你的代码起作用,但它不会解决基本问题。目前,你将两个相当不同的责任混合在一个函数中:1)获取文件名,2)打开或创建文件。如果将它们分开,代码将更简单,并且更容易消除你看到的问题的根源。
编辑2:像这样使用引用没有问题,不需要 `operator=`。一般的想法是:
int open_file(char const *name, fstream &stream) { 
    stream.open(name);
}
在这种情况下,赋值运算符既不必要也没有用--我们只需通过引用使用现有的 fstream。只有当我们必须将参数传递给 ctor 时才需要 operator=。对于一个流,我们可以默认构造一个不连接到文件的流,然后在事后使用 open 连接到文件。

谢谢!我已经这样做了,但不幸的是代码仍然无法工作。使用“ifstream database = *getFile();”在那一行上出现错误:“main.cpp:29:note:合成方法‘std :: basic_ifstream <char,std :: char_traits <char >> :: basic_ifstream(const std :: basic_ifstream <char,std :: char_traits <char >>&)’首先在此处需要”。 - wrongusername
将引用传递以避免返回新对象的“技巧”仅适用于具有operator=的类型。std :: ifstream也没有这个,所以这个建议对你不起作用。 - MSalters

9
bool checkFileExistence(const string& filename)
{
    ifstream f(filename.c_str());
    return f.is_open();
}

string getFileName()
{
    string filename;
    cout << "Please enter in the name of the file you'd like to open: ";
    cin >> filename;
    return filename;
}

void getFile(string filename, /*out*/ ifstream& file)
{
    const bool file_exists = checkFileExistence(filename);
    if (!file_exists) {
        cout << "File " << filename << " not found." << endl;
        filename = getFileName();  // poor style to reset input parameter though
        ofstream dummy(filename.c_str();
        if (!dummy.is_open()) {
            cerr << "Could not create file." << endl;
            return;
        }
        cout << "File created." << endl;
    }
    file.open(filename.c_str());
}

int main()
{
    // ...
    ifstream file;
    getFile("filename.ext", file);
    if (file.is_open()) {
        // do any stuff with file
    }
    // ...
}

谢谢!经过一番调试你的代码,我终于得到了我想要的结果。 :D - wrongusername
6
虽然这段代码片段可能解决问题,但包括解释真的有助于提高您的帖子质量。请记住,您正在为未来的读者回答问题,而这些人可能不知道您的代码建议原因。 - NathanOliver

4

ifstream不支持复制构造语义(这就是错误消息的基本含义),因此您无法返回一个ifstream。相反,返回一个ifstream*,并将删除分配的指针的责任传递给调用者。


好的...我输入了"return &third",现在它警告我"返回本地变量'third'的地址"。那么在main()函数中我该怎么办? - wrongusername
1
永远不要返回 &stack_variable。使用 new 运算符分配 ifstream 并返回指针。 - Remus Rusanu
你的意思是像这样吗:"ifstream returnStream = &first; return returnStream;"?顺便说一句,这也不起作用。它会给我一个错误:"main.cpp:56: error: conversion from ‘std::ifstream’ to non-scalar type ‘std::ifstream’ requested main.cpp:57: error: invalid conversion from ‘void’ to ‘std::ifstream*’" - wrongusername
6
我强烈建议你在继续之前,认真学习一下C/C++内存管理知识。因为有很多你不了解的东西。注意保持原意,使语言更通俗易懂,不添加解释或其他信息。 - Remus Rusanu
谢谢!当然……我很久以前学过,但可能忘记了大部分 :) - wrongusername

0

作为一个选项,ifstream可以被扩展,并在新类中添加自定义的构造函数。

我已经扩展它以创建测试资源流,在其中封装了测试资源查找。

// test_utils.h
class TestResourceStream : public std::ifstream {
    public:
        TestResourceStream(const char* file_path);
};

// test_utils.cpp
namespace fs = std::filesystem;
fs::path test_resource_path(const char* file_path) {
    fs::path path{std::string{"tests/resources/"} + file_path};
    if (!fs::exists(path))
        throw std::runtime_error{std::string{"path "} + 
            fs::absolute(path).c_str() + " does not exist"};
    return path;
}
TestResourceStream::TestResourceStream(const char* file_path)
    :std::ifstream{test_resource_path(file_path).c_str()} {}

// usage in test
TEST_CASE("parse") {
    std::list<GosDump::Expertise> expertises;
    TestResourceStream stream("requests/page_response.json");
    GosDump::Json::parse(expertises, stream);
    REQUIRE(10 == expertises.size());
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接