使用与unique_ptr的联合

时间:2014-07-12 14:21:27

标签: c++ c++11 struct unions unique-ptr

当我尝试使用std :: move或std :: make_unique时,尝试在union中使用unique_ptr会给我一个段错误。

#include <iostream>
#include <memory>

union myUnion{
    struct{std::unique_ptr<float> upFloat;}structUpFloat;
    struct{std::unique_ptr<int> upInt;}structUpInt;
    myUnion(){}
    ~myUnion(){}
};
struct myStruct{
    int x;
    myUnion num;

};
int main()
{
    myStruct aStruct, bStruct;
    aStruct.x = 1;
    bStruct.x = 2;

    auto upF = std::make_unique<float>(3.14);
    auto upI = std::make_unique<int>(3);

    aStruct.num.structUpFloat.upFloat = std::move(upF);
    bStruct.num.structUpInt.upInt = std::move(upI);

    std::cout << "aStruct float = " << *aStruct.num.structUpFloat.upFloat << std::endl;
    std::cout << "bStruct int = " << *bStruct.num.structUpInt.upInt << std::endl;
    return 0;
}

但是,使用普通指针按预期工作:

#include <iostream>
#include <memory>

union myUnion{
    struct{float *pFloat;}structPFloat;
    struct{int *pInt;}structPInt;
    myUnion(){}
    ~myUnion(){}
};
struct myStruct{
    int x;
    myUnion num;

};
int main()
{
    myStruct aStruct, bStruct;
    aStruct.x = 1;
    bStruct.x = 2;

    auto upF = std::make_unique<float>(3.14);
    auto upI = std::make_unique<int>(3);

    aStruct.num.structPFloat.pFloat = upF.get();
    bStruct.num.structPInt.pInt = upI.get();

    std::cout << "aStruct float = " << *aStruct.num.structPFloat.pFloat << std::endl;
    std::cout << "bStruct int = " << *bStruct.num.structPInt.pInt << std::endl;
    return 0;
}

这是使用clang.3.4.2或gcc.4.9.0。所以我假设我在这里做错了。任何帮助将不胜感激。

编辑:

好的,分享我确定的代码可能是件好事。非常感谢所有指导我使用placement new管理变体成员指针生命周期的人。

#include <memory>
#include <iostream>
#include <vector>
struct myStruct
{
public:
    union
    {
        std::unique_ptr<float> upFloat;
        std::unique_ptr<int> upInt;
    };
    enum class unionType {f, i,none} type = unionType::none; // Keep it sane
    myStruct(){}
    myStruct(std::unique_ptr<float> p)
    {
        new (&upFloat) std::unique_ptr<float>{std::move(p)};
        type = unionType::f;
    }
    myStruct(std::unique_ptr<int> p)
    {
        new (&upInt) std::unique_ptr<int>{std::move(p)};
        type = unionType::i;
    }
    ~myStruct()
    {
        switch (type)
        {
            case unionType::f: upFloat.~unique_ptr<float>(); break;
            case unionType::i: upInt.~unique_ptr<int>(); break;
        }
    }
};

int main()
{
    std::vector<std::unique_ptr<myStruct>> structVec;
    structVec.push_back(std::make_unique<myStruct>(std::make_unique<float>(3.14f)));
    structVec.push_back(std::make_unique<myStruct>(std::make_unique<int>(739)));
    structVec.push_back(std::make_unique<myStruct>());
    structVec.push_back(std::make_unique<myStruct>(std::make_unique<float>(8.95f)));
    structVec.push_back(std::make_unique<myStruct>(std::make_unique<int>(3)));
    structVec.push_back(std::make_unique<myStruct>());

    for(auto &a: structVec)
    {
        if(a->type == myStruct::unionType::none)
        {
            std::cout << "Struct Has Unallocated Union" << std::endl;
        }
        else if(a->type == myStruct::unionType::f)
        {
            std::cout << "Struct float = " << *a->upFloat << std::endl;
        }
        else
        {
            std::cout << "Struct int = " << *a->upInt << std::endl;
        }
        std::cout << std::endl;
    }

    return 0;
}

输出:

Struct float = 3.14

Struct int = 739

Struct具有未分配的联盟

Struct float = 8.95

Struct int = 3

Struct具有未分配的联盟

4 个答案:

答案 0 :(得分:13)

更改联合的活动成员需要特别注意对象的生命周期。 C ++标准说(9.5p4):

  

注意:通常,必须使用显式析构函数调用和放置新运算符来更改活动   工会会员。

当成员是普通的旧数据时,即使您没有调用构造函数(使用放置new)和析构函数,它通常也可以正常工作。这是因为具有简单初始化的对象的生命周期开始&#34;当获得存储时#34;足够的大小和正确的对齐,联盟提供了。

现在你已经拥有了非平凡的构造函数和析构函数的成员。获得存储时,它们的生命周期不会开始, 也必须导致初始化完成。这意味着安置新的。跳过析构函数调用也不安全,如果这些析构函数会产生程序所依赖的副作用,则会得到未定义的行为(并且unique_ptr析构函数具有释放其目标的副作用)。

因此,您在生命周期尚未开始的成员上调用移动赋值运算符。这是未定义的行为。

答案 1 :(得分:9)

对于不受限制的联盟,你必须自己管理一些构造/破坏。

以下可能会有所帮助:

union myUnion{
    std::unique_ptr<float> upFloat;
    std::unique_ptr<int> upInt;

    myUnion(){ new (&upFloat) std::unique_ptr<float>{};}
    ~myUnion() {}
};

class myStruct
{
public:
    ~myStruct()
    {
        destroy();
    }

    void destroy()
    {
        switch (type)
        {
            case unionType::f: num.upFloat.~unique_ptr<float>(); break;
            case unionType::i: num.upInt.~unique_ptr<int>(); break;
        }
    }

    void set(std::unique_ptr<int> p)
    {
        destroy();
        new (&num.upInt) std::unique_ptr<int>{std::move(p)};
        type = unionType::i;
    }
    void set(std::unique_ptr<float> p)
    {
        destroy();
        new (&num.upFloat) std::unique_ptr<float>{std::move(p)};
        type = unionType::f;
    }

public:
    enum class unionType {f, i} type = unionType::f; // match the default constructor of enum
    myUnion num;
};

int main()
{
    myStruct aStruct, bStruct;

    aStruct.set(std::make_unique<float>(3.14f));
    bStruct.set(std::make_unique<int>(3));

    std::cout << "aStruct float = " << *aStruct.num.upFloat << std::endl;
    std::cout << "bStruct int = " << *bStruct.num.upInt << std::endl;
    return 0;
}

在C ++ 17中,您可以使用std::variant而不是自己的struct

答案 2 :(得分:4)

来自this reference

  

如果一个union包含一个带有非平凡特殊成员函数的非静态数据成员(复制/移动构造函数,复制/移动赋值或析构函数),那么该函数在union中默认被删除,需要明确定义程序员。

我认为你将指针包装在简单结构中的原因是因为上段所施加的限制,否则你无法构建它。

您所做的是绕过了编译器的安全防护,并且代码中可能有undefined behavior

答案 3 :(得分:2)

来自标准的§12.6.2[class.base.init] / p8(重点补充):

  

在非委托构造函数中,如果给定的非静态数据成员或   基类不是由a指定的    mem-initializer-id (包括没有 mem-initializer-list 的情况,因为构造函数没有    ctor-initializer )并且该实体不是抽象类的虚拟基类(10.4),那么

     
      
  • 如果实体是具有大括号或等于初始值的非静态数据成员,则按照8.5中的规定初始化该实体;
  •   
  • 否则,如果实体是变体成员(9.5),则不执行初始化;
  •   
  • [...]
  •   

联盟成员是变体成员,这意味着unique_ptr未被初始化。特别是,不会调用任何构造函数,甚至不是默认构造函数。从技术上讲,这些unique_ptr的生命周期从未开始。

unique_ptr移动分配操作符必须删除unique_ptr当前持有的内容,但您要移动分配给包含垃圾值的未初始​​化的“unique_ptr”。因此,您的移动分配可能导致尝试删除垃圾指针,从而导致段错误。