C++ STL队列,引用和分段错误

4

新手学习C++,通过将Java程序转换为C++来学习。当执行以下代码时,会出现分段错误(SIGSEGV)。

//add web page reference to pages queue (STL)
void CrawlerQueue::addWebPage(WebPage & webpage) {
    pagesBuffer.push(webpage);
}

//remove and return web page reference from pages queue
WebPage & CrawlerQueue::getWebPage() {
    if (pagesBuffer.size() > 0) {
        WebPage & page = pagesBuffer.front();
        pagesBuffer.pop();
        return page;
    } else
        throw "Web pages queue is empty!";
}

//code that results in segmentation fault when called
void PageParser::extractLinks(){ 
    try {
        WebPage &  page =  crawlerqueue.getWebPage();
    }catch (const char * error) {
       return;
    }
}

上述代码的更改修复了分段错误问题,已用箭头标出(<====):
//return a const WebPage object instead of a WebPage reference
const WebPage CrawlerQueue::getWebPage() {          <====
    if (pagesBuffer.size() > 0) {
        WebPage page = pagesBuffer.front();         <==== 
        pagesBuffer.pop();
        return page;
    } else
        throw "Web pages queue is empty!";
}

//no segmentation fault thrown with modifications
void PageParser::extractLinks(){ 
    try {
        WebPage page =  crawlerqueue.getWebPage(); <====
    }catch (const char * error) {
       return;
    }
}

怎么回事?我还在努力理解引用和指针。


感谢所有的答案和评论。我现在明白了。 - robert
3个回答

5
pagesBuffer.pop();

这行代码会使你的引用失效。

请记住,标准容器使用值而不是“引用”进行操作,因此当您使用对对象的引用添加对象时,实际上是将对象的副本添加到容器中。

然后使用pop()函数时,会销毁该对象,使任何指向它的引用或指针无效。

也许你应该存储(共享)指针而不是对象。


我认为Java和C ++之间的一个重大区别在于理解:在Java中,一切都是引用,在C ++中,除非特别声明,否则一切都是值。但是,Java引用更类似于C ++指针而不是C ++引用。 - Craig Wright
@Craig:真正重要的部分是理解Java引用与垃圾回收的工作原理,这样你就不必考虑对象的生命周期,而C++没有垃圾回收机制,迫使你考虑对象的生命周期。 - Ken Bloom
@Ken Bloom:你可能需要考虑对象的生命周期,但这并不是一个巨大的负担。Java指针基本上等同于boost::shared_pointer<T>。如果你在代码中使用它,你将获得相同的基本功能,同时还能享受确定性销毁和能够与代码一起使用RAII的优势。 - Martin York
@Martin:我同意,一旦你理解了对象的生命周期并记住它的存在,它就不是一个巨大的负担。但是如果你从Java转过来,你需要做出一些调整。 - Ken Bloom

3

引用(也叫指针)指向某处的数据。当您使用返回引用版本的getWebPage()时,该引用指向pagesBuffer中的某个数据。在那之后运行pop()会从队列中删除该项,从而删除其内存,但是您的引用仍然指向它,因此它是一个悬垂引用。

当您修改代码以通过值返回时,您复制了要返回的对象,因此即使在运行pop()之后,这些副本仍然存在。

(C++不像Java那样,引用不能防止对象被删除 - 您必须自己进行管理。)


1
如果你想要在队列中存储数值,你需要修改你的代码:
WebPage  CrawlerQueue::getWebPage() {
    if (pagesBuffer.size() > 0) {
        WebPage  page = pagesBuffer.front();
        pagesBuffer.pop();
        return page;
    } else
        throw "Web pages queue is empty!";
}

在使用C++时,您需要对值、引用和指针之间的差异有一个非常清晰的理解。您还应该意识到,在Java中可以运行的编码样式极不可能在C++中运行——这两种语言除一些微不足道的句法相似之外几乎没有任何共同点。
此外,永远不要编写以下此类代码:
void PageParser::extractLinks(){ 
    try {
        WebPage &  page =  crawlerqueue.getWebPage();
    }catch (const char * error) {
       return;
    }
}

默默地吞咽异常总是一个非常糟糕的想法,通常在接近抛出点时捕获异常也是如此。


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