c++ 包含volatile结构的联合

xkftehaa  于 2023-05-02  发布在  其他
关注(0)|答案(2)|浏览(167)

这似乎与POD structs containing constant member类似,但有点相反。

#include <iostream>

struct A
{
    int a;
};

union U
{
    volatile A a;
    long b;
};

int main()
{
    U u1;
    U u2;

    u1.a.a = 12;
    u2 = u1;
    std::cout << u2.a.a << std::endl;

    return 0;
}

g++ 4.8.3编译了这段代码,没有错误,并且运行正常:

$ g++ -std=c++03 a.cpp -o a_gcc
$ ./a_gcc
12

但是当++ 3。5.1产生一个错误(我已经手动 Package 了错误消息,以防止代码框滚动):

$ clang++ -std=c++03 a.cpp -o a_clang
a.cpp:8:7: error: member function 'operator=' not viable: 'this'
argument has type 'volatile A', but function is not marked volatile
union U
      ^
a.cpp:3:8: note: 'operator=' declared here
struct A
       ^
a.cpp:20:5: note: implicit copy assignment operator for 'U' first
required here
        u2 = u1;
        ^
1 error generated.

C03是否允许程序对包含volatile结构体的联合体进行复制赋值?在C03标准中,我找不到任何定义联合的默认复制构造函数的东西。
我想知道哪个编译器是正确的,或者标准在这一点上是否不清楚。

**编辑:**我发现如果我使用复制构造而不是复制赋值,clang和g都可以编译程序而不会出错。具体来说,如果我将main更改为:

int main()
{
    U u1;

    u1.a.a = 12;
    U u2 = u1;
    std::cout << u2.a.a << std::endl;

    return 0;
}

那就能成功了我想知道为什么他们被clang++区别对待。

yquaqz18

yquaqz181#

在C++11中,union的复制构造函数可以被删除。我们从课堂上的一个笔记中看到了这一点。工会],N4140第9.5节:

  • [注意:* 如果联合的任何非静态数据成员具有非平凡的默认构造函数(12.1)、复制构造函数(12.8),移动构造函数(12.8)、复制赋值运算符(12.8),移动赋值运算符(12.8),或析构函数(12.4),联合体的相应成员函数必须是用户提供的,否则将被隐式删除(8.(4.3)工会。*-结束注解]*

在课堂上。copy],§12.8/25,我们看到我们的union有一个非平凡的复制构造函数:
类X的复制/移动赋值操作符是平凡的,如果它不是用户提供的,它的参数类型列表等价于隐式声明的参数类型列表,如果。..

  • 【……】
  • 类X没有volatile限定类型的非静态数据成员,并且
    但在课堂上的那一行。copy]只是添加了一个结果:Is a volatile-qualified type really a POD?在此之前,这样的类仍然会被认为有一个普通的复制构造函数。
    因此,我的理解是,在C03中,没有任何迹象表明应该删除联合的复制构造函数,而在C11中,有一些迹象表明这一点,但它是非规范的。
ecbunoof

ecbunoof2#

这只不过是GCC特定版本中的一个bug。
C++03 [class.copy]/10控制类隐式声明的复制赋值运算符的签名。对于名为X的类,它是X& operator=(const X&)(最常见)或X& operator=(X&)(不太常见)。在A的情况下,复制赋值运算符的签名是A& operator=(const A&)。请注意,这意味着您不能使用A类型的volatile或const volatile值调用此赋值运算符。
类似地,U的隐式声明的赋值运算符将具有签名U& operator=(const U&)。[class.copy]/13解释了这个隐式声明的赋值运算符是如何工作的:
X的隐式定义的复制赋值运算符执行其子对象的成员赋值。X的直接基类首先按照它们在 base-specifier- list 中声明的顺序赋值,然后X的直接非静态数据成员按照它们在类定义中声明的顺序赋值。每个子对象都以适合其类型的方式指定:

  • 如果子对象是类类型,则使用该类的复制赋值运算符(就像通过显式限定;也就是说,忽略更多派生类中任何可能的虚重写函数);
  • 如果子对象是数组,则以适合于元素类型的方式分配每个元素;
  • 如果子对象是标量类型,则使用内置赋值运算符。

未指定隐式定义的复制赋值运算符是否多次赋值表示虚拟基类的子对象。[...]
因此,U::operator=的行为就像它是这样实现的:

U& operator=(const U& other) {
    a = other.a;
    b = other.b;
    return *this;
}

a = other.a是病态的,因为other.a的类型是volatile A,而A没有可以接受volatile A的赋值运算符。
GCC的那个特定版本可能接受了它,因为它将赋值转换成了一个简单的复制操作(i。e. as if by memcpy).允许这样做,但前提是首先满足赋值的语义要求。不允许因为决定执行特定优化而无法诊断可诊断错误。
[over. match.oper]/1清楚地表明,如果操作符的至少一个操作数具有类类型,则必须执行重载解析。形式的赋值表达式中

a = other.a

两个操作数都具有类类型。[over. match.oper]/3然后解释候选项包括成员候选项、非成员候选项和内置候选项。允许非成员operator=,因此第二个集合为空。候选成员是A::operator=(const A&);。可能的内置候选列表在[over.built],但是没有左参数是对类类型的引用的内置候选项,并且[over.match.oper]/4禁止尝试对左参数执行用户定义的转换,以便将其转换为内置operator=候选对象可以接受的内容。因此重载解析规则排除了任何可能性,即赋值给A对象可以被解释为使用内置运算符。它总是必须调用赋值运算符函数,正如我前面所解释的,A的赋值运算符不能接受volatile参数。编译器必须对此进行诊断。

相关问题