【问题标题】:Why can't I make a union containing a vec3 object?为什么我不能创建一个包含 vec3 对象的联合?
【发布时间】:2018-11-07 16:54:03
【问题描述】:

我似乎无法创建一个成员是或包含 glm::vec3 对象(用于表示坐标的对象,在本例中包含 3 个浮点数)的联合。 (source code for glm::vec)

在以下代码中使用:

struct Event {
    enum Type{
        tRaw,
        tAction,
        tCursor,
    } type;
    union {
        SDL_Event raw;
        struct {
            uint16 actionID;
            bool released;
        } action;
        struct {
            glm::vec3 prevPos;
            glm::vec3 pos;
        } cursor; // offending object, compiles if this is removed
    } data;
};

Visual Studio 给我以下智能感知错误。

"Error: the default constructor of "union Event::<unnamed>" cannot be referenced -- it is a deleted function"

如果删除,联合编译没有任何问题。什么可能导致这个问题,我可以做些什么来解决它?

【问题讨论】:

  • 请显示产生此错误消息的代码。
  • @BDL - 添加它
  • @Ext3h:错误。从 C++11 开始就有可能,但您必须明确编写一个构造函数来执行相关放置 new
  • @AnneQuinn:这有点复杂。如果您有一个具有构造函数的非平凡类,则 C++ 语言不允许在不调用构造函数的情况下创建实例。 C++11 允许联合中的非平凡类型,但它不能生成默认构造函数 & co。在包含union 中,因为必须调用哪个成员的构造函数是不明确的,所以你必须自己编写构造函数,显式调用(使用放置new 语法)你想要的一个成员的构造函数激活。
  • @AnneQuinn: 呃...如果你的union 包含非平凡的类型,你不应该真的有一个空的union 构造函数,这就是标准不这样做的确切原因本身。非平凡类型的隐含契约是它们的构造函数/析构函数将始终被调用以使实例处于可用状态,因此您不应该真正解决它。话虽如此,我意识到对于glm::vec3 来说,这可能是一种小罪——因为它只是一个由三个标量组成的“盒子”,我认为不会有什么问题——事实上,这并不重要的是可能是偶然的。

标签: c++ unions glm-math


【解决方案1】:

一旦你的union 中有一个重要的类型(因此,语言强制“正确”初始化的类型,即构造函数调用),你必须明确地编写你的构造函数/析构函数:

#include <SDL/SDL.h>
#include <glm/vec3.hpp>
#include <stdint.h>
#include <new>
#include <vector>

struct Event {
    enum Type{
        tRaw,
        tAction,
        tCursor,
    } type;
    struct Cursor {
        glm::vec3 prevPos;
        glm::vec3 pos;
    };
    union {
        SDL_Event raw;
        struct {
            uint16_t actionID;
            bool released;
        } action;
        Cursor cursor;
    };
    Event(const SDL_Event &raw) : type(tRaw) {
        new(&this->raw) SDL_Event(raw);
    }
    Event(uint16_t actionID, bool released) : type(tAction) {
        this->action.actionID = actionID;
        this->action.released = released;
    }
    Event(glm::vec3 prevPos, glm::vec3 pos) : type(tCursor) {
        new(&this->cursor) Cursor{prevPos, pos};
    }
    Event(const Event &rhs) : type(rhs.type) {
        switch(type) {
        case tRaw:      new(&this->raw) SDL_Event(raw); break;
        case tAction:   memcpy((void *)&action, (const void *)&rhs.action, sizeof(action)); break;
        case tCursor:   new(&this->cursor) Cursor(rhs.cursor);
        }
    }

    ~Event() {
        if(type == tCursor) {
            this->cursor.~Cursor();
        }
        // in all other cases, no destructor is needed
    }
};

int main() {
    // Construction
    Event ev(1, false);
    SDL_Event foo;
    Event ev2(foo);
    glm::vec3 pos;
    Event ev3(pos, pos);
    // Copy construction & destruction
    std::vector<Event> events;
    events.push_back(ev);
    events.push_back(ev2);
    events.push_back(ev3);
    events.clear();
    return 0;
}

一些注意事项:

  • 我避免使用data 成员,而是选择匿名union;这避免了很多样板,否则我必须在union 内编写这些构造函数(因为它是 union 构造函数已被删除并且必须明确定义),然后在外部添加转发器;它还大大简化了析构函数的编写(同样,它必须写在union 中,但union 不知道外部type;你可以解决这个,但它很乏味和冗长);
  • 我必须明确命名 Cursor 联合,否则在语法上不可能调用它的析构函数(除非使用模板技巧);
  • 我没有实现赋值运算符;这并不复杂,但老实说,这很乏味。您可以找到一个基本蓝图(检查是否相同type;如果相同,则执行常规分配;否则,销毁活动成员并将-new 放置到新成员上)in the link I posted before in the comments

话虽如此,这些东西已经在 C++17 中以更通用的方式实现为std::variant,所以,如果你有足够新的编译器,你可以考虑使用它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多