将文件流插入到std::map中

3

我正在尝试使用文件流实现std::map(实际上是std::pair)。因为标准C++文件流(ifstreams ofstream和fstream)不可复制,所以选择了来自stdio的FILE

这是最简单的类包装:

#include <stdio.h>
class FileWriter
{
public:
    FileWriter(const char* fileName)  
    {
        _fs = fopen(fileName, "w");
    }
    ~FileWriter() 
    { 
        fclose(_fs); 
    }

private:
    FILE* _fs;
};

让我们尝试将这个类作为模板参数用于std::map中:

int main()
{
    std::map<int, FileWriter> a{ { 1, FileWriter("fl.fl") } };
}

代码编译没有问题,但在运行时出现了内存转储的错误。调试器显示析构函数~FileWriter()执行了两次。为什么会这样,并且如何避免这个错误?

4个回答

3
你的代码问题在于复制构造函数只是复制了 FILE*,而在将对象插入到 std::map <...> 中时创建了一个临时对象。结果,传递的 FILE*fclose() 关闭,当你尝试访问 FILE* 时出现未定义行为。

在 C++11 中,可以使用 emplace() 将流插入到 map 中:

std::map<int, std::ofstream> streams;
streams.emplace(42, std::ofstream("hello, world"));

很遗憾,我的编译器 - gcc 4.7,不支持std :: map的emplace()和流的移动赋值运算符。 - gorill
gcc-4.8 允许编译错误:使用已删除的函数 'std::basic_ofstream<char>::basic_ofstream(const std::basic_ofstream<char>&)'。 - gorill
@gorill:如果您可以使用“swap”,则可能不需要赋值:m[10].swap(mystream) - Kerrek SB
@gorill:你是否使用了“-std=c++11”编译选项?你需要支持移动语义来移动流。我不确定libstdc++何时实现了流的移动,不过。 - Dietmar Kühl
@ Dietmar Kühl,是的,我正在使用支持c++11的编译。 - gorill

1
您使用了一个临时变量创建了地图,并将其复制到需要的位置。由于您没有提供复制构造函数,因此使用默认值,它将简单地复制 _fs。临时对象的析构函数被调用,关闭文件。然后,当地图被销毁时,析构函数将再次在放置在地图中的副本上调用。
您需要提供自己的复制构造函数,并具有适当的语义。但是什么是语义?iostreams 之所以不可复制,是有原因的。您可以使您的类仅限于移动,但这时您与 iostreams 处于完全相同的情况,因为它们也是仅限于移动的。
另请参见:http://en.wikipedia.org/wiki/Rule_of_three_%28C%2B%2B_programming%29

1
因为标准的C++文件流(ifstreams、ofstream和fstream)是不可复制的。
是的,因为复制流没有意义。
流不是容器,它们是数据流。
不要试图绕过这个问题。

0

FileWriter类被构造,然后被复制到映射中(通过复制构造函数)。这就是为什么析构函数会被调用两次的原因。


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