包含 glm::vec2 的联合出现问题(非平凡的默认构造函数)

Problem with union containing a glm::vec2 (non-trivial default constructor)

我有以下结构:

struct Foo
{
    union
    {
        glm::vec2 size;
        struct { float width, height; };
    };

    Foo() = default;
};

如果我使用 new 创建 Foo 的实例,我会收到以下错误:

call to implicitly-deleted default constructor of 'Foo'

请注意,我已经阅读了一些关于 SO 的答案,所有这些都在谈论使用 placement-new,但我没有完全理解如何将其应用于我的简单测试用例。

At most one non-static data member of a union may have a brace-or-equal-initializer. [Note: if any non-static data member of a union has a non-trivial default constructor (12.1), copy constructor (12.8), move constructor (12.8), copy assignment operator (12.8), move assignment operator (12.8), or destructor (12.4), the corresponding member function of the union must be user-provided or it will be implicitly deleted (8.4.3) for the union. — end note ]

相关部分是

if any non-static data member of a union has a non-trivial [member function] [...], [then] the corresponding member function of the union must be user-provided or it will be implicitly deleted.

因此您将不得不手动实现构造函数和析构函数(如果您需要它们,还需要复制构造函数、移动构造函数、复制赋值运算符和移动赋值运算符)。

编译器无法提供默认构造函数,因为它无法知道哪些联合成员应该由默认构造函数初始化,或者哪个联合成员需要析构函数析构。

因此使用默认构造函数或尝试 =default; 它不会起作用,您必须提供用户指定的实现。


基本实现可能如下所示:

struct Foo
{
    bool isVector;
    union
    {
        std::vector<float> vec;
        struct { float width, height; };
    };

    // by default we construct a vector in the union
    Foo() : vec(), isVector(true) {

    }

    void setStruct(float width, float height) {
        if(isVector) {
            vec.~vector();
            isVector = false;
        }
        this->width = width;
        this->height = height;        
    }

    void setVector(std::vector<float> vec) {
        if(!isVector) {
            new (&this->vec) std::vector<float>();
            isVector = true;
        }

        this->vec = vec;
    }

    ~Foo() {
        if(isVector) vec.~vector();
    }
};

godbolt example

请注意,您需要手动管理联合成员的生命周期,因为编译器不会为您做这些。

如果活跃的union成员是struct,但是你想激活vector成员,你需要使用placement new在union中构造一个新的vector。

反之亦然:如果要将活跃的联合成员从vector切换到struct,需要先调用vector的析构函数.

我们不需要清理 struct,因为它很简单,所以不需要调用它的构造函数/析构函数。


如果可能的话,我建议改用 std::variant or boost::variant,因为它们正是这样做的:它们跟踪活跃的联合成员并根据需要调用所需的构造函数/析构函数。