【问题标题】:Problem with union containing a glm::vec2 (non-trivial default constructor)包含 glm::vec2 的联合问题(非平凡的默认构造函数)
【发布时间】:2022-01-08 08:49:10
【问题描述】:

我有以下结构:

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,但我并不完全了解如何将其应用于我的简单测试用例。

【问题讨论】:

  • std::variant 怎么样?
  • @Jarod42 我认为它是 C++17。我现在只能访问 C++14
  • 确实是 C++17,但是有一些库(作为 boost)为 C++14 提供它。

标签: c++ unions glm-math


【解决方案1】:

联合的最多一个非静态数据成员可以有一个大括号或等号初始化器。 [注意:如果联合的任何非静态数据成员具有非平凡的默认构造函数 (12.1)、复制构造函数 (12.8)、移动构造函数 (12.8)、复制赋值运算符 (12.8)、移动赋值运算符 (12.8),或析构函数(12.4),联合的相应成员函数必须是用户提供的,否则它将为联合隐式删除(8.4.3)。 ——尾注]

相关部分是

如果联合的any非静态数据成员具有非平凡的[成员函数] [...],则[则]联合的相应成员函数必须是用户提供,否则将被隐式删除

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

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

因此使用默认构造函数或尝试=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

请注意,您需要手动管理联合成员的生命周期,因为编译器不会为您执行此操作。

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

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

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


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

【讨论】:

  • 我要添加到答案中的一件事是,默认构造函数和默认构造函数之间的主要区别之一是后者始终为成员执行初始化,而前者可以省略成员的初始化并允许本例中的代码进行编译。
  • @DamirTenishev 在这种情况下都不会编译,因为当一个联合成员具有非平凡的构造函数时,需要删除默认构造函数。所以不指定它或默认它会导致它被=delete;'ed 不管(除非我错过了什么)defaulted constructor - default constructor
猜你喜欢
  • 2018-10-28
  • 2015-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多