Использование объединения с unique_ptr

Попытка использовать unique_ptr внутри объединения дает мне segfault, когда я пытаюсь выполнить std:: move или std:: make_unique.

#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. Поэтому я предполагаю, что здесь что-то не так. Любая помощь будет оценена.

EDIT:

Хорошо, так что, наверное, неплохо сделать, чтобы поделиться кодом, на котором я остановился. Большое спасибо всем, кто указал мне на управление временем жизни моих указателей в вариантах с использованием нового места размещения.

#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 float = 8.95

Struct int = 3

Структура нераспределенного союза

Ответ 1

Для неограниченного объединения вы должны сами управлять конструкцией/уничтожением.

Следующее может помочь:

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;
}

В С++ 17 вы можете использовать std::variant вместо своей собственной структуры

Ответ 2

Изменение активного члена союза требует особой заботы о жизни объекта. В стандарте С++ говорится (9.5p4):

Примечание. В общем случае необходимо использовать явные вызовы деструктора и размещение новых операторов для изменения активного член союза.

Когда члены представляют собой простые старые данные, они обычно "просто работают", даже если вы не вызываете конструкторы (используя размещение new) и деструкторы. Это потому, что время жизни для объектов с тривиальной инициализацией начинается "при получении хранилища" достаточного размера и правильного выравнивания, и объединение обеспечивает это.

Теперь у вас есть члены с нетривиальным конструктором и деструктором. Их срок службы не начинается, когда хранилище получено, вам также необходимо завершить инициализацию. А это значит, что новое место. Пропуск вызовов деструктора также небезопасен, вы получаете поведение undefined, если эти деструкторы будут иметь побочные эффекты, на которые зависит ваша программа (и деструктор unique_ptr имеет побочный эффект освобождения его цели).

Таким образом, вы вызываете оператор move-assign на члене, чье жизненное время еще не началось. Это поведение undefined.

Ответ 3

От эта ссылка:

Если объединение содержит нестатический элемент данных с нетривиальной специальной функцией-членом (copy/move constructor, copy/move assign или destructor), эта функция по умолчанию удаляется в объединении и должна быть явно определена программистом.

Я предполагаю, что причина, по которой вы завернули указатели в простых структурах, состоит в том, что вы не могли построить ее в противном случае из-за ограничений, налагаемых вышеуказанным абзацем.

Вместо этого вы обошли защитников безопасности компиляторов и, возможно, undefined поведение в вашем коде.

Ответ 4

Из §12.6.2 [class.base.init]/p8 стандарта (выделено мной):

В конструкторе без делегирования, если данный нестатический член данных или базовый класс не обозначается mem-initializer-id (включая случай, когда нет списка mem-initializer-list, поскольку у конструктора нет ctor-initializer), и сущность не является виртуальным базовым классом абстрактного класса (10.4), то

  • если объект является нестатическим членом данных, у которого есть инициализатор скобок или равный, объект инициализируется, как указано в 8.5;
  • в противном случае, если объект является вариантом (9.5), инициализация не выполняется;
  • [...]

Члены союза являются вариантными членами, что означает, что unique_ptr остаются неинициализированными. В частности, не вызывается никакой конструктор, даже не используемый по умолчанию. Технически время жизни этих unique_ptr никогда не начиналось.

Оператор присваивания unique_ptr должен удалить то, что удерживает unique_ptr, но вы назначаете перемещение к неинициализированному значению "unique_ptr", содержащему значения мусора. В результате ваше назначение переноса, вероятно, вызвало попытку удалить указатель на мусор, вызвав segfault.