【问题标题】:STL containers & memory leaksSTL 容器和内存泄漏
【发布时间】:2012-08-30 19:56:02
【问题描述】:

C# coder 刚刚编写了这个简单的 C++ 方法来从文件中获取文本:

static std::vector<std::string> readTextFile(const std::string &filePath) {
    std::string line;
    std::vector<std::string> lines;
    std::ifstream theFile(filePath.c_str());
    while (theFile.good()) {
    getline (theFile, line);
        lines.push_back(line);
    }
    theFile.close();
    return lines;
}   

我知道这段代码效率不高;文本行在读取时复制一次,在按值返回时复制第二次。

两个问题:

(1) 这段代码会泄漏内存吗? (2) 更一般地说,按值返回对象容器是否会泄漏内存? (假设对象本身不泄漏)

【问题讨论】:

  • 你可能不希望 static 像在 C# 中那样无处不在。此外,容器会在它们被销毁时释放内存,当它们像你一样在堆栈上分配时,就是它们超出范围时。
  • 实际上按值返回并不一定会导致副本。由于RVO,它可以避免,或者,如果使用 c++11 并且 RVO 是不可能的,则返回值被移动而不是被复制(移动 vector 大约需要三个指针分配,所以这并不是真的花费很多)。不,如果对象本身是正确的,按值返回对象不会泄漏内存。在 c++ 中,除非您开始使用 new
  • 更好:while (std::getline(theFile, line)),否则你会在 eof 处得到一个额外的空行。除此之外,代码很好。在 C++11 中,您有惯用的方法来避免额外的副本。在 C++03 中,您通常更喜欢额外的副本而不是卷积代码。

标签: c++ stl


【解决方案1】:
while (theFile.good()) {
 getline (theFile, line);
    lines.push_back(line);
}

忘了效率吧,这段代码是不正确的。它不会正确读取文件。请参阅以下主题了解原因

所以循环应该写成:

while (getline (theFile, line)) {
    lines.push_back(line);
}

现在这是正确的。如果您想提高效率,请先分析您的应用程序。尝试查看占用 CPU 周期最多的部分。


(1) 这段代码会泄漏内存吗?

没有。

(2) 更一般地说,按值返回对象容器是否会泄漏内存?

取决于容器中对象的类型。在您的情况下,std::vector 中对象的 typestd::string,这确保不会泄漏任何内存。

【讨论】:

    【解决方案2】:

    没有,也没有。按值返回永远不会泄漏内存(假设容器和包含的对象写得很好)。如果换成其他方式,那就太没用了。

    我赞同 Nawaz 所说的,你的 while 循环是错误的。坦率地说,令人难以置信的是,我们看到了多少次,肯定有很多糟糕的建议。

    【讨论】:

    • 谢谢 - 我在“www.cplusplus.com”得到了循环结构。我想即使是这样的名字,也必须小心剪裁 sn-ps。
    • @tpascale 想知道确切的地址。
    • cplusplus.com/doc/tutorial/files 并查看“文本文件”部分中的第二个示例。
    • 有趣,文字其实是OK的,它暗示good()是假的最后一次I/O操作失败之后。但如果作者知道他们为什么要这样写循环呢?
    【解决方案3】:

    (1) 这段代码会泄漏内存吗?

    没有

    (2) 一般情况下,按值返回对象容器是否会泄漏内存?

    没有。您可能会泄漏通过指针或泄漏对象存储在容器中的内存。但这不会是由按值返回造成的。

    我知道这段代码效率不高;文本行在读取时复制一次,在按值返回时复制第二次。

    很可能不是。该字符串有两个副本,但不是您正在考虑的那些。返回的副本很可能会在 C++03 中进行优化,并且在 C++11 中将被优化掉或转换为移动(廉价)。

    这两个应对措施相当:

    getline (theFile, line);
    lines.push_back(line);
    

    第一行从文件复制到line,第二行从line复制到容器。如果您使用的是 C++11 编译器,则可以将第二行更改为:

    lines.push_back(std::move(line));
    

    to move 将字符串的内容移动到容器中。或者(在 C++03 中也有效),您可以更改这两行:

    lines.push_back(std::string()); // In most implementations this is *cheap*
                                    // (i.e. no memory allocation)
    getline(theFile, lines.back());
    

    并且你应该测试读取的结果(如果读取失败,在最后一个替代方案中,确保resize 减少一个元素以删除最后一个空字符串。

    【讨论】:

    • 要删除最后一个元素,可以使用pop_back。这比得到长度要简单得多,减去 1 和 resizeing。
    【解决方案4】:

    在 C++11 中,你可以这样做:

    std::vector<std::string> 
    read_text_file(const std::string& path) 
    {
        std::string line;
        std::vector<std::string> ans;
        std::ifstream file(path.c_str());
    
        while (std::getline(file, line))
           ans.push_back(std::move(line));
    
        return ans;
    }
    

    并且不会制作额外的副本。

    在 C++03 中,您接受额外的副本,并且只有在分析要求时才痛苦地删除它们。

    注意:您不需要手动关闭文件,std::ifstream 的析构函数会为您完成。

    注意2:您可以在 char 类型上进行模板化,这在某些情况下可能很有用:

    template <typename C, typename T>
    std::vector<std::basic_string<C, T>>
    read_text_file(const char* path)
    {
        std::basic_string<C, T> line;
        std::vector<std::basic_string<C, T>> ans;
        std::basic_ifstream<C, T> file(path);
    
        // Rest as above
    }
    

    【讨论】:

      【解决方案5】:

      不,按值返回容器不应泄漏内存。标准库被设计为在任何情况下都不会泄漏内存。如果其实现中存在错误,它只能泄漏内存。至少在旧的 MSVC 中曾经有一个 bug in vector of strings

      【讨论】:

        猜你喜欢
        • 2015-01-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-02-22
        • 1970-01-01
        • 1970-01-01
        • 2015-08-04
        相关资源
        最近更新 更多