为什么这不会创建悬空引用?

6

C26830: Assigning by value when a const-reference would suffice

我认为VS2019的建议可能会导致悬空引用的情况,但我进行了测试,它似乎可以正常工作。这里发生了什么?


    template<typename MessageType>
    class Queue {
      inline static std::vector<MessageType> messages;
    public:
      static bool isEmpty() {
        return messages.size() == 0;
      }

      template <typename... Args>
      static void emplace(Args&&... args) {
        messages.emplace_back(std::forward<Args>(args)...);
      }

      static MessageType pop() {
        auto const& val = messages.back();
        messages.pop_back();
        return val;
      }
    };

似乎最后一条消息存活的时间足够长,以至于可以被复制到返回值中。这是一个好的做法吗?

6
编译器没有义务找出编译代码中的每个未定义行为。每次它能够提醒你未定义行为时,都可以将其视为额外的奖励。编译器错过这里的事实仅意味着您不能总是期望编译器为您捕获所有错误。如果能做到这一点那就太好了,但我们并不总能拥有美好的东西…… - Sam Varshavchik
1
VS在这里是错误的:随着这些更改,这绝对是未定义的行为。 - Etienne de Martel
1
您可以更改代码以避免与C++核心指南相关的警告,方法是不使用auto来定义类型:messageType val = messages.back(); - 1201ProgramAlarm
1
auto const& val = messages.back(); messages.pop_back(); return val; 是未定义行为,因为在到达 return 时,val 引用的对象已被销毁。但是 auto const val = messages.back(); messages.pop_back(); return val; 完全没有问题,因为 val 是向量的最后一个元素的 副本,所以原始元素是否被销毁并不重要。 - Remy Lebeau
1
@alfC:在截图中,val不是一个引用。但在随后的代码中,它是一个引用。 - Mooing Duck
显示剩余3条评论
2个回答

4
最后一条消息似乎存活时间足够长,以至于可以复制到返回值中。这是好的实践吗?不幸的是,它并不是,std::vector<T>::back的返回类型是一个左值引用。也许Intellisense认为它是右值引用,在这种情况下,由于此处的规则,它的生命周期会被延长。但对于这种情况并不适用,使用方式是未定义行为。这是因为引用所指的列表项已经被销毁。之所以仍然有效,是因为该项的内存仍然存在,因此可以正确读取。这只是运气(或者如果您想找到这些错误,就是不幸)。如果被pop_back销毁的项持有其他内存,则可能会看到不同的结果,如SEGFAULT。

我很好奇为什么当我弹出然后再次插入时,我仍然得到预期的值,这应该会覆盖上一个值。 - ratiotile

0

我认为这个建议是正确的,但只有在非常牵强的情况下才能解释。

首先,它告诉你的唯一一件事就是.back()返回一个引用,而且不需要被赋值。 当然,这个引用后来可能会变得无效,实际上它可能取决于std::vector的具体实现。 你不想要那样。

回答你的问题,是的,.pop_back()有时会创建一个悬空引用,这取决于实现和运行时条件。形式上,也就是未定义行为。

(来自https://en.cppreference.com/w/cpp/container/vector/pop_back)

迭代器和对最后一个元素的引用以及end()迭代器都将失效。

其次,你无论如何都要返回一个副本,所以"something"很可疑,与IDE的建议相符。 你不想要一个副本(IDE也不想要),但你也不想要一个引用。 你可能想要的是一个移动的副本,很有可能。

如果Message可以被移动并且应该是通用正确的,那么这是高效的,IDE不应该建议任何改进。最坏的情况是它会复制,这是正确的行为。

      static MessageType pop() {
        auto val = std::move(messages.back());
        messages.pop_back();
        return val;
      }

总之,不管有没有UB或者讨论,这是我认为最概念化的表达方式。

注意:我认为auto valauto const val更好,因为这样可以允许NRVO。但我不是100%确定。



我喜欢你关于std::move的建议,但我很确定const不会影响NRVO/复制省略。 - ratiotile
@ratiotile,是的,我不确定(就像我说的那样)。也许我误解了最近看到的一次演讲。 - alfC

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