【问题标题】:G++-11 destruction order changed from G++9G++-11 销毁顺序由 G++9 更改
【发布时间】:2021-06-21 13:18:12
【问题描述】:

我们有以下代码(ofc比较复杂,我试着做一个最小的例子)。

#include <iostream>
#include <vector>
#include <string>
#include <memory>

template<typename T>
struct use_type
{
    use_type(T& v) : value(v) {}
    T& value;
};

template<typename T>
use_type<T> use(T& value) { return use_type<T>(value); }

template<typename T>
use_type<T> use(const T& value) { return use_type<T>(const_cast<T&>(value)); }

template<typename T>
struct printer_helper
{
    void use(const use_type<T>& use) { uses.push_back(use); }
    void final_action()
    {
        for (const auto& use : uses)
        {
            std::cout << use.value.size() << std::endl;
            for (const auto& v : use.value)
            {
                std::cout << v << ",";
            }
            std::cout << std::endl;
        }
    }
    std::vector<use_type<T>> uses;
};

template<typename T>                                        
struct printer
{
    printer() { helper = new printer_helper<T>(); }
    printer<T>& operator , (const use_type<T>& t)
    {
        helper->use(t);
        return *this;
    }
    ~printer()
    {
        helper->final_action();
        delete helper;
    }
    printer(const printer&) = delete;
    printer& operator =(const printer&) = delete;
    printer(printer&&) = default;
    printer& operator =(printer&&) = default;
    printer_helper<T>* helper;
};

template<typename T>
struct printer_creator
{
    printer<T> operator << (const char*) { return {}; }
};

int main()
{
    using vec = std::vector<std::string>;
    {
    /*vec v1{"abc", "bcd"};
    vec v2{"new", "old", "real"};*/
    printer_creator<vec> p;
    p << "", use(vec{"abc", "bcd"}), use(vec{"new", "old", "real"});
    //p << p, use(v1), use(v2);
    }
}

此代码在 g++-11 之前的 g++ 上运行良好。使用 g++-11 时,输出中要么是段错误,要么是垃圾。是UB吗?如果是的话,也许你可以告诉我,哪个 gcc 更改使它失败并尽可能引用标准?

活生生的例子:

g++-10 g++-11

【问题讨论】:

  • Rule of 3/5/0 for printer 坏了。
  • @Jarod42,是的,但这里不需要。在实际代码中它存在。
  • 如果它曾经工作过,但现在不再工作,我敢打赌,这不是将代码变为 UB 的重大更改,而是代码始终是 UB,而编译器更改只是成功了更明显。这段代码中有很多危险信号,很难知道应该关注哪里。
  • 我建议在编译时使用-g -fsanitize=addressexample
  • use_type 更改为T value; 而不是T&amp; value; 以停止在作用域后使用堆栈 崩溃。

标签: c++ g++ object-lifetime


【解决方案1】:

临时向量在创建它们的行的末尾被销毁。 p 在其作用域的末尾被破坏(即在向量之后)。由于p 的析构函数使用了对现在悬空的向量的引用,因此您的代码具有未定义的行为。

如果它曾经奏效,那纯属“幸运”,向量析构函数使向量处于可用状态。

【讨论】:

【解决方案2】:

p << "", use(vec{"abc", "bcd"}), use(vec{"new", "old", "real"});

您创建 3 个临时对象

  • printer&lt;vec&gt; 来自p &lt;&lt; "")
  • vec{"abc", "bcd"}
  • vec{"new", "old", "real"}

它们之间没有顺序(参见下面的注释),因此它们可以按任何顺序创建。

只有从左到右的破坏是正确的(所以从右到左构造),其他顺序将使用悬空引用并且是UB。而且由于您没有订单保证...

注意:
我们因过载而失去短路operator &amp;&amp;/operator ||,
我们通过重载operator,而失去了从左到右的评估顺序

【讨论】:

  • 谢谢。现在它是有道理的。我知道,重载运算符很不好,但它来自一个库。
  • 依赖(非保证的)评估顺序是最糟糕的。
  • 是的......但它没有被注意到并且 c-tor 接受 const 引用。感谢您的解释。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-09
  • 2021-01-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-08
相关资源
最近更新 更多