【问题标题】:Assignment to deleted/uninitialized object分配给已删除/未初始化的对象
【发布时间】:2019-01-28 21:28:18
【问题描述】:

我有这样的课

struct foo {
    bool type;
    union {
        struct { std::string s; };
        struct { std::function<std::string(void)> f; };
    };
};

我需要定义赋值运算符,但是当我分配不同的类型时,我基本上是分配给未初始化的字段(我知道我必须先显式调用析构函数)。

我的猜测是这样的赋值是未定义的行为,因为我不知道字符串或函数的赋值是否不会重用某些字段。

如何在不调用任何未定义行为的情况下正确执行此操作?

我仅限于 C++11。

【问题讨论】:

  • type不是专门告诉你哪个字段是活跃的吗?
  • @FrançoisAndrieux 当我想分配给与我分配的活动类型不同的对象时,就会出现问题
  • 你能把你相信的作业展示给UB吗?
  • 为什么不使用boost::variant

标签: c++ c++11 initialization destructor unions


【解决方案1】:

我的猜测是这样的赋值是未定义的行为,因为我不知道字符串或函数的赋值是否不会重用某些字段。

猜对了!赋值总是以左侧实际上是该类型的对象为前提。如果我们在左手边没有对象,则所有赌注都关闭。

如何在不调用任何未定义行为的情况下正确执行此操作?

建设!当您没有对象时,获得对象的唯一方法是创建它。既然我们想在特定位置创建它,那就是新的展示位置。

我不确定你为什么将你的类型包装在一个额外的结构中,你想这样做:

union U {
    U() { }
    ~U() { }

    std::string s;
    std::function<std::string(void)> f;
} u;

这样,您的赋值运算符将是:

foo& operator=(foo const& rhs) {
    if (type) {
        if (rhs.type) {
            u.s = rhs.u.s;
        } else {
            u.s.~string();
            new (&u.f) std::function<std::string(void)>(rhs.u.f);
        }
    } else {
        if (!rhs.type) {
            u.f = rhs.u.f;
        } else {
            u.f.~function();
            new (&u.s) std::string(rhs.u.s);
        }
    }

    return *this;
}

您需要重构其中的一些内容以支持移动分配而无需大量重复,但这是一个粗略的想法。

【讨论】:

  • 我将类型包装在结构中,因为 c++11 或仅 vs2012 不允许我与具有复制构造函数的类型进行联合
  • @Ryba vs2012 不是很抱怨 C++11。您应该升级以获得更好的一致性。
  • @NathanOliver 如果我不坚持使用 vs2012,我会使用 gcc 和 c++17 以及适当的 std::variant
  • @Ryba 你被 MS 支持的 c++03 限制锁定了。有趣的是,你在这里作弊,因为包含非平凡成员的结构是非平凡的,但匿名结构是 MS C++ 的非标准特性,却不是。这听起来像错误\错误功能使用。
  • @Swift-FridayPie 那么是否有适当的 c++11 方法来制作支持非平凡成员的变体?
【解决方案2】:

首先,匿名结构在 C++ 中的格式不正确。例如,您可以改用:

union {
    std::string s;
    std::function<std::string(void)> f;
};

我的猜测是这样的赋值是未定义的行为

正确。

如何在不调用任何未定义行为的情况下正确执行此操作?

您可以使用标记联合模式。您似乎已经有一个 type 成员。您可以使用它来指定哪个成员处于活动状态。如果您对该成员有其他计划,只需添加一个新计划即可。

一旦您可以使用结构成员检查哪个联合成员处于活动状态,您就可以销毁活动成员,然后复制构造以前的非活动联合成员,最后更新type 以表示新的活动成员。


也就是说,您可以通过使用预先存在的标记联合的通用实现来节省一些时间。例如,Boost 中有一个。

【讨论】:

    【解决方案3】:

    C++11 非常友好,甚至不允许这样做。由于您的联合包含具有非平凡构造函数和析构函数的字段,struct foo 已隐式删除了默认构造函数和析构函数。

    当我尝试使用以下命令创建 struct foo 时,我的旧 clang 版本 3.4.1(带有选项 std=c++11)阻塞:

    error: call to implicitly-deleted 
    

     note: default constructor of 'foo' is implicitly deleted because variant field has a non-trivial default constructor.
    

    如果我尝试手动初始化 struct foo (我稍微更改了 foo 以避免匿名字段):

    ...
    struct foo {
        bool type;
        union {
            struct { std::string s; } s;
            struct { std::function<std::string(void)> f; } f;
        };
    };
    
    int main() {
        foo bar { .s = {"a"}};
        ...
    

    我收到另一个错误(如预期的那样):

    error: attempt to use a deleted function
        foo bar { .s = {"a"}};
    

    note: destructor of 'foo' is implicitly deleted because variant field 's' has a non-trivial destructor
            struct { std::string s; } s;
                                      ^
    

    【讨论】:

    • 不,首先你应该得到anonymous types declared in an anonymous union are an extension [-Wnested-anon-types],因为标准不允许这样做。使用-std=c++11 -pedantic 代码应该可以在没有匿名结构的情况下工作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-09
    • 2021-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-30
    • 2012-11-17
    相关资源
    最近更新 更多