STL容器中传递引用的对象的生命周期

3

我是一名经验丰富的程序员,但对STL仍然比较新,最近遇到了这个问题:

据我所知,STL容器不应该复制它们包含的对象,或者以其他方式影响它们的生命周期,但实验结果却不同。

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

使用命名空间std;

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()会产生未定义的结果,因为重复分配来自额外调用的内存已被覆盖,但两者都返回适当存储的字符串,毫无失败。
我想知道为什么会发生这种情况,但如果不能解释,我很乐意知道是否可以依靠任何旧对象发生这种效果。
谢谢

2
我想补充一点,不要返回一个const char*。使用std::string和std::cout。这就是它们的作用。在我看来,你正在从C转换到C++。字符串类不应该做任何清零其第一个字符的事情。那将是C语言的做法。在C++中,安全性远远超过了那个。 - Puppy
谢谢你的建议 - 我通常使用std :: string和std :: cout(正如您在useStr中看到的那样),printf和返回const char *只是我想证明底层数据仍然良好,我可以依赖它的恐慌尝试。 - Lachlan Pease
4个回答

8
据我所知,STL容器通常会复制它们包含的对象。当插入时,当容器自动或显式调整大小时以及在复制容器本身时,它们都会复制它们的内容。大量的复制。
我不确定您从哪里得到STL容器不复制其内容的想法。我能想到的唯一接近的事情是,如果将指针插入STL容器中,则会复制指针本身而不是指向的数据。
此外,您的代码中根本没有涉及引用,因此我对这个问题的标题指的是什么感到困惑。

谢谢 - 我的理解是STL容器存储时使用引用而不是复制数据,因为所有存储方法都采用引用。回想起来,这是一个错误的假设,但是为了辩护一下,当时已经很晚了。关于引用的事情,我认为编译器在调用"strQueue.push_back(os.str().data()"时会隐式传递一个指向队列的引用 - 因为push_back只接受引用,我仍然相信这是正确的,但如果我错了,我希望被纠正。 - Lachlan Pease
在C++中,常见的做法是函数和方法通过const引用来接受对象参数(即除了int、float、char之外的东西)。这样在调用函数时避免了复制,但实际上几乎从不意味着该函数将保留对该对象的引用。在容器情况下,容器对象将通过const引用接受要插入的对象,然后将其复制到其永久存储中。这样只需要一次复制而不是两次复制(一次进入参数,然后另一次进入存储)。 - Tyler McHenry

5
STL容器并不意味着复制它们所包含的对象。STL旨在制作副本。当您插入对象时,它将进行副本,并且有时会在基础存储器调整大小时进行副本。如果您复制的对象在函数超出范围时无效(例如,如果您添加指向局部变量的指针而不是复制局部变量),则可能会导致代码错误。在您的情况下,您不是复制对字符串的引用,而是复制字符串。然后,此复制的字符串存在于strQueue的范围内,因此您看到的行为是完全有效和可靠的。
这里还有一个需要澄清的误解:
特别是,字符串类在销毁时会将其基础存储器的第一个字符清零。
C++通常不会做这种事情。这将是一个隐藏成本,而C ++讨厌隐藏成本:)字符串析构函数不会触及内存,因为一旦析构函数退出,对象就不存在了。访问它是未定义行为,因此C ++实现将在定义良好的代码中执行最快和最节省资源的操作。

+1给最佳答案,但是有一点措辞上的问题:“超出作用域的对象”不再是对象,只是原始内存... - hjhill
@hjhill:花括号和析构函数哪个先执行?如果一棵树在森林里倒下……我会修正措辞,以消除歧义 :) - Merlyn Morgan-Graham

0

容器中放入的是对象的副本而不是实际对象。同样地,你得到的也是一个副本。只要你的容器在作用域内,就可以访问这些对象。


你得到的不一定是一个副本,除非你自己复制它。int& i = vec.front(); //引用第一个项目 - UncleBens

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

出于好奇,你为什么讨厌“STL”这个术语? - Pedro d'Aquino
@Pedro:我并不讨厌它,但这个名字有点糟糕。标准、模板和库这些词并没有给你任何关于该库有用或相关的指示,而且让人觉得模板是一种专门的技术,而不是你应该在C++应用程序中始终使用的东西。 - Merlyn Morgan-Graham
1
@Pedro:因为不同的人对它的理解不同,这往往会导致很多误解。甚至“STL”这个词的含义也有所不同。有些人指的是“STandard Library”,有些人则是“Standard Template Library”,等等。即使你指的是标准模板库,你是指委员会15年前提出的库,还是被纳入标准的那部分库,或者是现在以那个名字命名的库?如果你指的是标准库,那么是哪些部分呢? - Jerry Coffin
根据斯特潘诺夫的说法,“STL”代表“标准模板库”,因此那些认为STL代表“标准库”的人就像把911称作法拉利一样荒谬。http://www.stlport.org/resources/StepanovUSA.html - John Dibling
@John:虽然我非常尊重Alexander Stepanov,但我认为他并没有权力决定特定TLA的唯一解释。更糟糕的是,即使他有这个权力,我们仍然有至少三种不同的解释可供使用。 - Jerry Coffin

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