【问题标题】:Lifecycle of objects passed by reference to STL containers通过引用传递给 STL 容器的对象的生命周期
【发布时间】:2026-02-01 20:10:01
【问题描述】:

我是一名经验丰富的编码员,但对 STL 还是比较陌生,并且刚刚遇到这个问题:

据我所知,STL 容器并不意味着复制它们所包含的对象,或者以其他方式影响它们的生命周期,但在实验中我看到了不同的结果。

特别是,如果字符串类在超出范围之前存储在容器中,则它们在销毁时将其底层存储的第一个字符清零,仍然可以访问它们。例如,考虑以下示例:

使用命名空间标准;

queue<string> strQueue;

const char *genStr(int i)
{
    ostringstream os;
    os << "The number i is " << i;
    strQueue.push(os.str());
    return strQueue.back().data();
}

void useStr()
{
    while(!strQueue.empty())
    {
        cout << strQueue.front() << endl;
        strQueue.pop();
    }
}

int main(int argc, char **argv)
{
    for(int i = 0; i < 40; i++)
    {
        printf("Retval is: %s\n", genStr(i));
    }
    useStr();

    return 0;
}

当 genStr() 退出时字符串超出范围,我希望 printf 只输出“Retval is:”,或者至少调用 useStr() 会给出未定义的结果,因为内存被额外调用的重复分配压得喘不过气来,但两者都返回适当的存储字符串,没有失败。

我想知道为什么会发生这种情况,但除此之外,我很高兴知道我是否可以依赖任何旧对象发生的这种效果。

谢谢

【问题讨论】:

  • 我想补充,不要返回 const char*。使用 std::string 和 std::cout。这就是他们的目的。在我看来,您正处于从 C 转换为 C++ 的过程中。字符串类不应该做任何将它们的第一个字符归零的事情。那将是一个 C 主义。在 C++ 中,安全性遥遥领先。
  • 感谢您的建议 - 我通常使用 std::string 和 std::cout (正如您在 useStr 中看到的那样),printf 并返回 const char * 只是为了证明而惊慌失措对我自己来说,基础数据仍然很好,我可以依赖它。

标签: c++ stl reference


【解决方案1】:

据我所知,STL 容器 并不是要复制对象 它们包含

好的,我们就到此为止吧。 STL 容器确实经常复制它们的内容。它们在插入时复制它们,在容器自动或显式调整大小时复制它们,在容器本身被复制时复制它们。大量抄袭。

我不确定您从哪里得到 STL 容器不复制其内容的想法。唯一我能想到的更接近的是,如果您将指针插入 STL 容器,它将复制指针本身,但不会复制指向的数据。

此外,您的代码中没有任何引用,所以我对这个问题的标题所指的内容感到困惑。

【讨论】:

  • 谢谢 - 我的印象是 STL 容器通过引用存储,而不是从所有存储方法都引用引用的事实中复制数据。回想起来,这是一个糟糕的假设,但在我的辩护中,这已经很晚了,呵呵。至于引用的事情,我假设编译器在我调用“strQueue.push_back(os.str().data()”时隐式传递了对队列的引用——因为 push_back 只接受引用,我仍然相信这是是这样,但如果我错了,我会很高兴得到纠正
  • 在 C++ 中,一个常见的做法是让函数和方法通过 const 引用获取对象参数(即除 int、float、chars 之外的东西)。这避免了在调用函数时进行复制,但它并不(实际上几乎从不)意味着函数将保留对该对象的引用。在容器情况下,容器对象将通过 const 引用接受要插入的对象,然后将其复制到其永久存储中。这会导致一个副本而不是两个副本(一个进入参数,然后另一个进入存储)。
【解决方案2】:

STL 容器并不意味着复制它们所包含的对象

STL都是关于复制的。它会在您插入对象时生成它们,并且有时会在底层存储被调整大小时生成它们。如果您正在复制的对象在您的函数超出范围时失效(例如,如果您添加指向局部变量的指针,而不是复制局部变量),您可能会得到损坏的代码。

在您的情况下,您不是在复制对字符串的引用,而是在复制字符串。这个复制的字符串然后存在于 strQueue 的范围内,所以你看到的行为是完全有效和可靠的。

这里还有一个误区需要澄清:

特别是字符串类,它们旨在在销毁时将其底层存储的第一个字符清零

C++ 从来不会做那种事情。这将是一个隐藏成本,C++ 讨厌隐藏成本 :) 字符串析构函数不会触及内存,因为一旦析构函数退出,对象就不再存在。访问它是未定义的行为,因此 C++ 实现将在定义明确的代码中执行最快且最不浪费的操作。

【讨论】:

  • +1 以获得最佳答案,但有一件事是:“超出范围的对象”不再是对象,只是原始内存......
  • @hjhill:哪个先发生 - 花括号还是析构函数?如果一棵树倒在森林里......我会修正措辞,这样就不会有歧义了:)
【解决方案3】:

所有“STL”(我讨厌这个术语)集合都存储传递给它们的对象的副本,因此集合中对象的生命周期完全独立于原始对象。在正常情况下,对象的集合副本将保持有效,直到您将其从集合中删除或销毁集合。

【讨论】:

  • 出于好奇,你为什么讨厌“STL”这个词?
  • @Pedro:我并不讨厌它,但它是一个坏名字。标准、模板和库这三个词并没有给您任何关于库有用或相关的任何指示,并且让您看起来模板是一些专门的技术,而不是您应该始终在 C++ 应用程序中使用的东西。
  • @Pedro:因为不同的人用它来表达不同的意思,所以容易引起很多误解。甚至“STL”的确切含义也各不相同。有些人的意思是“标准库”,其他人的意思是“标准模板库”,等等。即使那样,如果您指的是标准模板库,您是指大约 15 年前提交给委员会的库,该库中被标准接受的部分,还是该名称的当前库?如果你指的是标准库,是哪些部分?
  • 根据 Stepanov 的说法,“STL”的意思是“标准模板库”,所以说 STL 的意思是“标准库”的人不妨将 911 称为法拉利。 stlport.org/resources/StepanovUSA.html
  • @John:尽管我对 Alexander Stepanov 的评价很高,但我不认为他完全有能力决定对特定 TLA 的唯一解释。更糟糕的是,即使他是,我们仍然可以使用至少三种不同的解释。
【解决方案4】:

进入容器的是对象的副本,而不是实际对象。同样,您返回的也是副本。只要您的容器在范围内,您就可以访问这些对象。

【讨论】:

  • 你得到的不一定是副本,除非你自己复制。 int&amp; i = vec.front(); //reference to the first item