【问题标题】:C++ std::tuple order of destructionC++ std::tuple 破坏顺序
【发布时间】:2016-12-28 08:33:12
【问题描述】:

是否有规定 std::tuple 的成员被销毁的顺序的规则?

例如,如果Function1 将std::tuple<std::unique_ptr<ClassA>, std::unique_ptr<ClassB>> 返回到Function2,那么我可以确定(当Function2 的范围离开时)第二个成员引用的ClassB 的实例被销毁在第一个成员引用的ClassA 实例之前?

std::tuple< std::unique_ptr< ClassA >, std::unique_ptr< ClassB > > Function1()
{
    std::tuple< std::unique_ptr< ClassA >, std::unique_ptr< ClassB > > garbage;
    get<0>(garbage).reset( /* ... */ );
    get<1>(garbage).reset( /* ... */ );
    return garbage;
}

void Function2()
{
    auto to_be_destroyed = Function1();
    // ... do something else

    // to_be_destroyed leaves scope
    // Is the instance of ClassB destroyed before the instance of ClassA?
}

【问题讨论】:

  • 我猜这主要取决于std::tuple 在您的标准库中是如何实现的。
  • 我在规范中找不到指定std::tuple 销毁顺序的任何内容。可能应该以未指定的形式归档。
  • 这个问题最近出现在 Antony Polukhin 的 excellet CPPCon 2016 talk 在没有适当语言支持的情况下在 C++14 中实现部分反射。 IIRC 他说他必须做的唯一可怕的事情是重新实现 std::tuple 以强制执行某种初始化顺序。另外,在此上 ping @101010。

标签: c++ c++11 std stdtuple


【解决方案1】:

我将提供我学到的人生教训,而不是直接回答您的问题:

如果您可以针对多种替代方案制定合理的论据,说明为什么该替代方案应该是标准规定的替代方案 - 那么您不应该假设 任何 它们是强制性的(即使其中之一他们碰巧是)。

在元组的上下文中 - 请善待维护您的代码的人,不要让元组元素的破坏顺序可能会破坏其他元素的破坏。那只是邪恶的......想象一下需要调试这个东西的倒霉程序员。事实上,那个可怜的灵魂可能会在几年后成为你自己,那时你已经忘记了你过去的聪明伎俩。

如果你绝对必须依赖销毁顺序,也许你应该使用一个适当的类,将元组的元素作为它的数据成员(你可以为它编写一个析构函数,明确需要以什么顺序发生什么),或其他有助于更明确地控制销毁的安排。

【讨论】:

  • 这是非常重要的一课!使用 C++ 编程时,开发人员必须始终牢记这个技巧。
  • 我不太明白这一点。由于 C++11 字符串,需要有连续的存储。但是有一个合理的论点(编写 C++03 时使用的论点)不需要这样做。或者,如果您对这个例子有异议,请选择另一个标准做出判断的地方,可以合理地采用任何一种方式。那么我不应该编写依赖于 C++11 或 C++14 加强的东西的代码吗?还是你的意思是,如果标准不明确,那么就不要指望下一个程序员像你一样吐毛?
  • 或者对于一个故意愚蠢的例子,有一个合理的论点是std::vector应该被称为std::dynamic_array,而std::valarray应该被称为std::vector。但我认为编写代码是非常合理的,因为std::vector 恰好是标准所说的:我们不能在没有假设的情况下使用它。所以我想我不明白你的建议适用于什么情况;-)
  • @SteveJessop:关于字符串示例 - 请注意,所有不假定字符串具有连续存储的代码在 C++03 中也有效(好吧,假设它不依赖于 C++11当然是功能)。如果您想依赖连续存储,那么这样做是有充分理由的。想想代码&lt;algorithm&gt; 的假设有多么少,它的适用范围有多大。仍然可以,我承认你可以在我的人生课前加上“所有其他条件都一样......”。
  • 作为附录:如果您确实最终依赖于这样的巧妙技巧,请请记录完全正确 你在用 cmets 做什么。尽可能详细地描述你正在利用的行为,以及为什么你正在利用它,以便任何需要维护它的人都知道他们在处理什么。
【解决方案2】:

标准没有指定std::tuple 的销毁顺序。 §20.4.1/p1 规定:

具有两个参数的元组实例化类似于 使用相同的两个参数实例化对。

类似此处不被解释为相同,因此并不暗示std::tuple 应具有其参数的反向销毁顺序。

鉴于std::tuple 的递归性质,最有可能的是销毁顺序与其参数的顺序一致。

我的假设也基于GCC BUG 66699 的错误报告,在讨论中我的上述假设是合理的。

也就是说,std::tuple 的销毁顺序未指定。

【讨论】:

    【解决方案3】:

    在 Clang 3.4 中,std::pair 和 2 个元素 std::tuple 的销毁顺序相同,而在 g++ 5.3 中,我得到相反的顺序,这可能主要是由于 libstd++ 中 std::tuple 的递归实现。

    所以,它基本上归结为我在评论中所说的,它是实现定义的。

    来自BUG 报告:

    Martin Sebor 的评论

    由于 std::pair 成员的布局是完全指定的,所以也是 它们的初始化和销毁​​顺序。测试的输出 case 反映了这个顺序。

    std:stuple 子对象的初始化(和销毁)顺序 没有明确规定。至少它不是立即显而易见的 如果需要任何特定订单,我会阅读规范。

    std::tuple 与 libstdc++ 的输出是相反的原因 std::pair 是因为实现,它依赖于递归 反向继承、存储和构造元组元素 order:即存储最后一个元素的基类 并首先构造,然后是每个派生类(每个派生类 存储最后一个 - 第 N 个元素)。

    错误报告者引用的标准 [第 20.4.1 节] 中的引用

    1 本小节描述了提供元组的元组库 类型作为类模板元组,可以用任何实例化 参数的数量。每个模板参数指定一个类型 元组中的元素。因此,元组是异构的, 固定大小的值集合。 具有两个元组的实例化 arguments 类似于具有相同两个的 pair 的实例化 论据。见 20.3。

    在链接的错误中对此提出的反对意见是:

    被描述为相似并不意味着它们在每个方面都相同 细节。 std::pair 和 std::tuple 是不同的类,具有不同的 对每一个的要求。如果您认为必须遵守 在这方面相同(即,将它们的子对象定义在 相同的顺序)你需要指出具体的措辞 保证。

    【讨论】:

    • 你怎么知道std::pair的元素的销毁顺序是相反的?
    • 类似!= 相同。
    • @deepmax 基于 libcxx 和 libstd++ 的 std::pair 实现。
    • @101010 我不知道,我认为相同是相似的。
    • @yakk 非常类似于相同但不完全相同 :)
    猜你喜欢
    • 2011-09-04
    • 1970-01-01
    • 2015-09-30
    • 1970-01-01
    • 2011-03-31
    • 2018-10-16
    • 2016-01-18
    • 1970-01-01
    • 2019-06-15
    相关资源
    最近更新 更多