【问题标题】:pop_back() return value?pop_back() 返回值?
【发布时间】:2012-09-26 11:06:47
【问题描述】:

为什么pop_back() 没有返回值?我对此进行了谷歌搜索,发现它可以提高效率。这是在标准中这样做的唯一原因吗?

【问题讨论】:

    标签: c++ vector


    【解决方案1】:

    效率与它几乎没有关系(或根本没有关系)。

    这个设计是 an important paper by Tom Cargill 的成果,发表于 90 年代,当时引起了很多人的注意。 IIRC,在其中嘉吉表明不可能设计一个异常安全的堆栈弹出功能。

    【讨论】:

    • +1 参考 Tom Cargill 论文“异常处理:一种虚假的安全感”。必读。 :-)
    • 如果我没记错的话,“C++ 语言”或某些版本的“Effective C++”实际上声称这是出于效率原因,因为当时他们还没有弄清楚移动构造和 NRVO。
    • @Konrad:AFAIR,迈耶斯甚至指着嘉吉的论文。 (想想看,我敢打赌这就是它存在副本的真正原因,在 Pearson 的网站上。)
    • 我们怎样才能确定一个被移除的对象不会抛出析构函数?我知道它不应该,但是我们如何控制它呢?
    • @Mikhail:如果析构函数抛出,你不能保证任何事情。这就是他们不能这样做的原因。再次重申:析构函数永远不能抛出。句号。如果你写了一个可以转义异常的dtor,你也可以取消引用NULL
    【解决方案2】:

    我认为复制最后一个对象的实例可能会引发异常这一事实与此有关。这样做时,您将丢失对象,因为 pop_back() 确实将其从容器中删除。几行代码就更好了:

    std::vector<AnyClass> holds = {...} ;
    try {
      const AnyClass result = holds.pop_back(); // The copy Ctor throw here!
    } catch (...)
    { 
     // Last value lost here. 
    }
    

    【讨论】:

    • +1 T container&lt;T&gt;::pop()无法实现异常安全 (gotw.ca/gotw/008.htm)
    • +1 这是正确答案。它告诉我们导致pop_back 没有返回值的设计的基本原理。
    • 对。我对 c++11 还不是很了解。但我想答案仍然成立。我的意思是,不是使用编译器提供的移动 ctor,而是使用用户定义的移动 ctor。此外,afaik,我记得在某处读过一个类可能被定义为逃避任何移动 ctor(= 删除?)。如果是这样,那么我们将退回到复制 ctor 的情况下(如果提供了这种情况,否则我猜使用向量将无法编译)。
    • @DeadMG 移动构造函数可能也不是 noexcept,如果我没记错的话
    • @DeadMG: movecopy,这取决于 value 的 type,因为并非每种类型都是可移动的, 加上 move 也可能抛出异常。
    【解决方案3】:

    【讨论】:

      【解决方案4】:

      效率是一回事。 pop_back() 不返回元素的另一个原因是异常安全。
      如果pop()函数返回值,拷贝构造函数抛出异常,则可能无法保证容器与调用pop()之前的状态相同。

      您可以在 Herb Sutters 书籍中找到有关异常的更多信息。我认为该主题已涵盖here。但我不确定。

      【讨论】:

      • 问题不在于容器被改变了,问题在于对象可能被移除了,但你还没有取回它——所以它丢失了。
      【解决方案5】:

      原因与其说是效率不如说是异常安全。容器类可用于存储任何类型的对象。如果函数在从容器中删除对象后返回对象,则不可能以异常安全的方式实现 pop_back(),因为返回对象的值涉及复制构造。

      这是 GNU C++ 标准库中 vector::pop_back() 的实际实现:

        void
        pop_back()
        {
          --this->_M_impl._M_finish;
          this->_M_impl.destroy(this->_M_impl._M_finish);
        }
      

      如果它最后返回最后一个元素,这就是它的样子:

        value_type
        pop_back()
        {
          value_type save = back();
          --this->_M_impl._M_finish;
          this->_M_impl.destroy(this->_M_impl._M_finish);
          return save;
        }
      

      这涉及两个复制构造,在save = back() 语句和返回对象的副本时。无法保证在元素从容器中销毁后返回表达式不会引发异常。

      【讨论】:

        【解决方案6】:

        嗯,有多少个理由?

        当您只想将其从容器中移除时,这可以避免复制对象的成本可能很高。 C++ 的理念是不为不需要的东西买单。

        【讨论】:

        • 我认为这是错误的。见my comment to Ravindra's answer
        • 好吧,我很确定异常相关问题可能是一个的原因,但我也很确定这是另一个原因。我还没有看到任何证据表明这种设计与性能问题无关(因为移动语义是一个相当新的东西)并且我没有声称没有另一个(好吧,我的第一句话可能听起来像这样,但是它旨在澄清为什么一个原因还不够)。 Sutter 在“Exceptional C++”中声称“这样做的一个理由:它避免了削弱异常安全性。”。
        • “我还没有看到任何证据表明这种设计与性能问题无关。”您还没有看到任何证据表明这种设计与香蕉无关。 所以? 是你提出了主张(“这是关于性能的!”),是我提出了挑战。因此,需要支持声明的是您。
        • 好的,你让我在那里工作:) Stroustrup 在“C++ prog”中说。 lang' [16.3.5]:It just pops, and if we want to know what was on the top of the stack before pop, we must look. This happens not to be my favorite style of stack, but it's arguably more efficient and it's the standard。 OTOH,Josuttis 的书也提到了嘉吉的论文。我的结论是性能的原因之一,但我并不声称这是唯一的原因。但是,在阅读所有参考资料后,我也同意异常安全可能是决定这种方式的更重要因素。
        • 这是最好的答案,为什么不避免不必要的复制?
        【解决方案7】:

        在计算机编程中,正交性意味着操作会发生变化 只做一件事,不影响其他人。

        pop_back() 只做一件事,它不复制,因此它是正交的。

        【讨论】:

          【解决方案8】:

          为什么它会返回值?您始终可以在弹出之前随时访问该值 - pop_back 无需提供此功能。

          【讨论】:

          • -1。它没有回答这个问题。 pop_back 不返回值是有原因的,这篇文章没有讨论这个原因。
          • 它没有理由存在的事实是它不存在的一个很好的理由,这确实回答了这个问题。
          • @DeadMG:我相信有一个很好的可用性案例 pro pop_back() 返回一个值。传统上,这就是堆栈的弹出函数所做的。不,原因纯粹是为了异常安全。
          猜你喜欢
          • 2023-03-07
          • 1970-01-01
          • 2011-02-24
          • 1970-01-01
          • 2017-08-20
          • 2020-07-08
          • 1970-01-01
          • 2022-12-19
          • 2014-01-16
          相关资源
          最近更新 更多