为工会交换非活动的std :: unique_ptr数据成员

时间:2015-09-23 11:36:04

标签: c++ c++14 smart-pointers unique-ptr discriminated-union

鉴于工会:

#include <iostream>
#include <memory>
#include <type_traits>
#include <vector>

#include <cassert>
#include <cstdlib>

struct A { int a; };
struct B { int b; };

template< typename X >
struct S
{

    std::size_t tag;

    std::unique_ptr< X > x;

};

union U
{
    S< A > a;
    S< B > b;

    U(A x) : a{0, std::make_unique< A >(x)} { ; }
    U(B x) : b{1, std::make_unique< B >(x)} { ; }

    std::size_t tag() { return a.tag; }

    ~U()
    {
        switch (tag()) {
        case 0 : {
            a.~S< A >();
            break;
        }
        case 1 : {
            b.~S< B >();
            break;
        }
        default : assert(false);
        }
    }

    void
    swap(U & u) noexcept
    {
        a.x.swap(u.a.x);
        std::swap(a.tag, u.a.tag);
    }

};

static_assert(std::is_standard_layout< U >{});

int
main()
{
    U a{A{ 0}};
    U b{B{~0}};
    assert((a.tag() == 0) && (a.a.x->a ==  0));
    assert((b.tag() == 1) && (b.b.x->b == ~0));
    a.swap(b);
    assert((a.tag() == 1) && (a.b.x->b == ~0));
    assert((b.tag() == 0) && (b.a.x->a ==  0));
    return EXIT_SUCCESS;
}

U::tag()函数是正确的,因为它允许检查U中的替代数据成员的共同初始子序列 - 如工会。

U::swap()有效,但std::unique_ptr是否合法?是否允许交换std::unique_ptr的非活跃U替代数据成员 - 就​​像工会一样?

由于std::unique_ptr< X >的简单性质,它似乎是可以允许的:它只是X *的封装,对于任何AB,我确定{{1}对于所有类型,hold和指针排列是相同的(除了指向数据成员和类的成员函数的指针)。这是真的吗?

示例代码工作正常。但如果我们阅读标准,很可能会有UB。

2 个答案:

答案 0 :(得分:1)

恕我直言,你有正式的未定义行为,因为你总是访问部分联盟,即使最后写的是b。

当然它可以工作,因为除了它的管理之外,unique_ptr只包含一个原始指针和一个存储的删除器。任何类型的指针都具有相同的表示形式,除了对齐问题之外,将指向X的指针转换为指向Y和Y的指针是安全的。所以在低级别如果可以安全地交换原始指针。它可能更依赖于实现,但我认为交换存储的删除器也是安全的,因为实际存储的通常是地址。无论如何,对于类型struct Astruct B,析构函数只是无操作。

唯一可能导致代码失败的是,如果编译器强制执行只能访问联合的最后写入成员的规则,则除了公共初始子序列之外。对于当前的编译器,我很确定没有强制执行,因此它应该可以工作。

但在我曾经问过another possible UB case的一个问题中,Hans Passant给出了link关于能够检测缓冲区溢出的高级编译器的研究工作。我真的认为可以使用相同的技术来强制执行关于访问union成员的规则,因此这些编译器可以在运行时使用您的代码引发异常。

TL / DR:此代码应该适用于所有当前已知的编译器,但由于不严格符合标准,未来的编译器可能会陷入困境。因此,我称之为正式的未定义行为

答案 1 :(得分:1)

§ 9.5 Unions

特别是关于标准布局类型的说明:

  

......一个特别的保证   是为了简化联合的使用:如果标准布局联合包含几个标准布局   共享公共初始序列(9.2)的结构,以及此标准布局联合类型的对象   包含一个标准布局结构,允许检查任何一个的公共初始序列   标准布局结构成员......

因此,允许将共同的初始序列用于任何一个联盟成员。

在您的情况下,公共初始序列绝对是std::size_t tag。然后我们需要知道std::unique_ptr<T>对于所有T是否相同,因此它也可以被视为公共初始序列的一部分:

  

§20.8.1班级模板unique_ptr
  [1]唯一指针是拥有另一个对象并通过指针管理该另一个对象的对象。   更确切地说,唯一指针是一个对象u,它存储指向第二个对象的指针p ...

是的。但我们怎么知道所有指针都代表相同呢?那么,在你的情况下:

  

§3.9.2化合物类型
  [3] ...指针类型的值表示   是实现定义的。指向符合cv标准和cv不合格版本(3.9.3)的布局兼容的指针   类型应具有相同的值表示和对齐要求......

因此,我们可以依赖存储在std::unique_ptr中的指针的值,该值是在union的另一个成员中可表示的值。

所以不,这里没有未定义的行为。