STL 容器和内存泄漏

Posted

技术标签:

【中文标题】STL 容器和内存泄漏【英文标题】:STL containers & memory leaks 【发布时间】: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 中,您通常更喜欢额外的副本而不是卷积代码。 【参考方案1】:
while (theFile.good()) 
 getline (theFile, line);
    lines.push_back(line);

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

What's preferred pattern for reading lines from a file in C++?

所以循环应该写成:

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。

【讨论】:

以上是关于STL 容器和内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

SGI STL内存配置器:内存泄漏?

SGI STL内存配置器存在内存泄漏吗?

奇怪的容器内存泄漏

winform 容器内存泄漏

慧销平台ThreadPoolExecutor内存泄漏分析

Tomcat如何检测内存泄漏