首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >为什么删除联合类或类工会类的默认构造函数?

为什么删除联合类或类工会类的默认构造函数?
EN

Stack Overflow用户
提问于 2020-12-22 06:47:03
回答 2查看 828关注 0票数 15
代码语言:javascript
运行
复制
struct A{
    A(){}
};
union C{
   A a;
   int b = 0;
};
int main(){
    C c;
}

在上面的代码中,GCC与宗族都抱怨联合C的默认构造函数被定义为已删除。

然而,有关规则规定:

在以下情况下,类X的默认构造函数被定义为已删除:

  • X是一个具有非平凡的默认构造函数的变量成员的联合,而X的任何变体成员都没有默认的成员初始化器
  • X是一个非联合类,它的变体成员M具有一个非平凡的默认构造函数,而包含M的匿名联合的任何变量成员都没有默认的成员初始化器

注意强调的措辞。在本例中,由于变体成员b具有默认成员初始化器,因此不应将默认的默认构造函数定义为“删除”。为什么这些编译器将这些代码报告为格式错误?

如果将C的定义更改为

代码语言:javascript
运行
复制
union C{
   A a{};
   int b;
};

然后所有的编译器都可以编译这段代码。该行为暗示,该规则实际上意味着:

X是一个具有一个具有非平凡的默认构造函数的变体成员的联合,并且没有为提供默认的成员初始化器,即变体成员

这是编译器的错误还是该规则含糊的措辞?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-12-22 09:24:10

这在C++14和C++17之间进行了更改,通过CWG 2084添加了允许NSDMI在(任意)联合成员上恢复默认构造函数的语言。

不过,随附的CWG 2084示例与您的示例略有不同:

代码语言:javascript
运行
复制
struct S {
  S();
};
union U {
  S s{};
} u;

在这里,NSDMI位于非平凡的成员上,而C++17的措辞允许任何成员上的NSDMI恢复默认的构造函数。这是因为,正如博士所写的,

NSDMI基本上是mem初始化器的语法糖。

也就是说,int b = 0;上的NSDMI基本上相当于用mem初始化器和空体编写构造函数:

代码语言:javascript
运行
复制
C() : b{/*but use copy-initialization*/ 0} {}

顺便说一句,确保联盟中最多有一个变体成员有NSDMI的规则在class.union.anon的一个子子句中有些隐藏。

4-.联合的一个变体成员最多可以有一个默认的成员初始化器。

我的假设是,,因为gcc和Clang已经允许上面的(非平凡的工会成员的NSDMI ),他们没有意识到他们需要改变他们的实现以获得完全的C++17支持。

这是在名单上讨论性病-2016年讨论,有一个非常类似于您的例子:

代码语言:javascript
运行
复制
struct S {
    S();
};
union U {
    S s;
    int i = 1;
} u;

其结论是,clang和gcc在拒绝上有缺陷,尽管当时有一个误导性的注解,结果是已修订

对于Clang来说,bug是bug.cgi?id=39686,它将我们循环回隐式定义构造函数因变量成员而被删除,N 3690/N 4140 vs N 4659/N 4727。我找不到gcc相应的窃听器。

注意MSVC正确接受,并将c初始化为.b = 0,即每dcl.init.aggr正确

5-.如果聚合是一个联合并且初始化程序列表是空的,那么

  • 5.4 -如果任何变体成员都有默认的成员初始化器,则该成员将从其默认成员初始化器初始化;.
票数 6
EN

Stack Overflow用户

发布于 2020-12-22 10:30:38

联盟是一件棘手的事情,因为所有成员共享相同的内存空间。我同意,规则的措辞不够清楚,因为它忽略了显而易见的内容:为一个联合的多个成员定义默认值是未定义的行为,或者应该导致编译器错误。

请考虑以下几点:

代码语言:javascript
运行
复制
union U {
    int a = 1;
    int b = 0;
};

//...
U u;                 // what's the value of u.a ? what's the value of u.b ? 
assert(u.a != u.b);  // knowing that this assert should always fail. 

这显然不应该编译。

此代码确实编译,因为A没有显式的默认构造函数。

代码语言:javascript
运行
复制
struct A 
{
    int x;
};

union U 
{
    A a;        // this is fine, since you did not explicitly defined a
                // default constructor for A, the compiler can skip 
                // initializing a, even though A has an implicit default
                // constructor
    int b = 0;
};

U u; // note that this means that u.b is valid, while u.a has an 
     // undefined value.  There is nothing that enforces that 
     // any value contained by a struct A has any meaning when its 
     // memory content is mapped to an int.
     // consider this cast: int val = *reinterpret_cast<int*>(&u.a) 

这段代码无法编译,因为A::x确实有一个显式默认值,这与U::b (双关意)的eplicit默认值发生了冲突。

代码语言:javascript
运行
复制
struct A 
{
    int x = 1;
};

union U 
{
    A a;
    int b = 0;
};

//  Here the definition of U is equivalent to (on gcc and clang, but not for MSVC, for reasons only known to MS):
union U
{
    A a = A{1};
    int b = 0;
};
// which is ill-formed.

由于大致相同的原因,这段代码在gcc上也不会编译,但是会在MSVC上工作(MSVC总是比gcc严格一点,所以也就不足为奇了):

代码语言:javascript
运行
复制
struct A 
{
    A() {}
    int x;
};

union U 
{
    A a;
    int b = 0;
};

//  Here the definition of U is equivalent to:
union U
{
    A a = A{};  // gcc/clang only: you defined an explicit constructor, which MUST be called.
    int b = 0;
};
// which is ill-formed.

至于在哪里报告错误,无论是在声明点还是实例化点,这取决于编译器,gcc和msvc在初始化点报告错误,当您尝试实例化联合时,clang将报告错误。

注意,这是非常不可取的,有一个联盟的成员是不兼容的,或至少比特相关。这样做破坏了类型安全性,并且是对程序中bug的公开邀请。类型双关是可以的,但是对于其他用例,应该考虑使用std::variant<>。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65404305

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档