我有一个结构体,我创建了一个自定义构造函数将其成员初始化为 0。在旧的编译器中,如果不对值进行memset操作,处于发布模式时这些值就没有被初始化。
现在我想在联合中使用这个结构体,但由于它具有非平凡构造函数,所以会出错。
问题1:默认编译器实现的构造函数是否保证所有结构体成员都将被空初始化?非平凡构造函数只是对所有成员执行memset操作以确保结构干净。
问题2:如果必须在基础结构上指定构造函数,如何实现联合以包含该元素并确保基本元素被初始化为 0?
我有一个结构体,我创建了一个自定义构造函数将其成员初始化为 0。在旧的编译器中,如果不对值进行memset操作,处于发布模式时这些值就没有被初始化。
现在我想在联合中使用这个结构体,但由于它具有非平凡构造函数,所以会出错。
问题1:默认编译器实现的构造函数是否保证所有结构体成员都将被空初始化?非平凡构造函数只是对所有成员执行memset操作以确保结构干净。
问题2:如果必须在基础结构上指定构造函数,如何实现联合以包含该元素并确保基本元素被初始化为 0?
问题1:根据C++标准, 默认构造函数会将POD成员初始化为0。请参见下面引用的文本。
问题2:如果一个基类必须指定构造函数,则该类不能是联合体的一部分。
最后,您可以为您的联合提供一个构造函数:
union U
{
A a;
B b;
U() { memset( this, 0, sizeof( U ) ); }
};
针对问题1:
来自C++03标准,12.1构造函数,第190页
隐式定义的默认构造函数执行了该类的初始化集合,这些初始化将由一个用户编写的空的mem-initializer-list(12.6.2)和空函数体的默认构造函数执行。
来自C++03标准,8.5初始化器,第145页
默认初始化类型T的对象意味着:
零初始化类型T的对象意味着:
针对问题2:
来自C++03标准,12.1构造函数,第190页
如果一个构造函数是隐式声明的默认构造函数,并且:
来自C++03标准,9.5联合体,第162页
联合体可以有成员函数(包括构造函数和析构函数),但不能有虚函数(10.3)。联合体不得具有基类。不能将联合体用作基类。带有非平凡构造函数(12.1)、非平凡复制构造函数(12.8)、非平凡析构函数(12.4)或非平凡复制赋值运算符(13.5.3、12.8)的类的对象不能是联合体的成员,也不能是这些对象的数组。
C++11带来了更好的变化。
如Stroustrup亲自描述的,现在你可以合法地进行下面的操作(我从C++11 Wikipedia文章上找到了这个链接)。
维基百科上给出的示例代码如下:
#include <new> // Required for placement 'new'.
struct Point {
Point() {}
Point(int x, int y): x_(x), y_(y) {}
int x_, y_;
};
union U {
int z;
double w;
Point p; // Illegal in C++03; legal in C++11.
U() {new(&p) Point();} // Due to the Point member, a constructor
// definition is now *required*.
};
Stroustrup提供了更详细的说明。
U
提供一个用户定义的构造函数,例如:U() {}
,它的成员p
将被默认初始化为不确定的值。显然,这并不使p
成为活动成员,因此从其任何成员读取都将是UB,有多种原因。成员p
也可以在不使用placement new的情况下激活,因为它的复制构造函数是平凡的,如:U u; u.p = {1, 2};
。 - 303U u; u.p.x_ = 3;
不过,我不确定这是否符合您想要分享的示例代码。无论如何,回顾我的先前评论,由于部分不正确,我很快就会将其删除。我可能期望赋值表达式 U u; u.p = {1, 2};
被视为一种复制列表初始化形式,并在重载决议期间考虑 p
的平凡复制构造函数。 - 303U u; u.p = {1, 2};
实际上调用了 p
的自动生成的复制赋值运算符,而不是任何构造函数。由于赋值操作只能在已存在的对象上执行,因此行为是未定义的。需要使用放置 new 来启动非平凡的非活动联合成员的生命周期,例如:U u; new (&u.p) Point{1, 2};
。 - 303struct foo
{
int a;
int b;
};
union bar
{
int a;
foo f;
};
bar b = { 0 };
struct foo
{
int a;
int b;
};
union bar
{
bar() { memset(this, 0, sizeof(*this)); }
int a;
foo f;
};
你能做类似这样的事情吗?
class Outer
{
public:
Outer()
{
memset(&inner_, 0, sizeof(inner_));
}
private:
union Inner
{
int qty_;
double price_;
} inner_;
};
...或者类似这样的东西?
union MyUnion
{
int qty_;
double price_;
};
void someFunction()
{
MyUnion u = {0};
}
这是一个有趣的问题,其他答案中有很多有用的信息。此外,了解通过=default
语法指定默认构造函数的效果将非常有用。
对于作为联合成员的类,这样的“默认”默认构造函数优于没有初始化列表和空主体的用户定义的默认构造函数。注意:如果用户定义的默认构造函数是非平凡的,例如调用memset
等,则dan-man的答案显示需要做什么(即使e.g.将默认构造函数定义为没有初始化列表和空主体)。
关于问题1,“默认”默认构造函数将突出显示默认初始化和值初始化之间的区别。
对于一个名为C
的类,如果默认构造函数被用户显式定义为C() {}
(即,具有空体和无初始化列表),那么当通过这种方式创建对象时,它将导致默认初始化:C c_obj{};
。然而,如果默认构造函数被指定为C()=default;
,那么C c_obj{};
将导致c_obj
的值初始化。
关于问题2,dan-man的答案非常有用。使用“defaulted”默认构造函数可以简化如下:
#include <new> // Required for placement 'new'.
struct Point {
Point()=default; // not `Point() {};`
Point(int x, int y): x_(x), y_(y) {}
int x_, y_;
};
union U {
int z;
double w;
Point p;
// No need to specify a default constructor.
// It is needed with `Point() {};` which is considered
// as a user defined default constructor.
};
int main() {
...
U u; // implicitly generated default constructor of U is called.
new(&u.p)Point(); // activate the Point member of U
// using placement new.
...
}
您需要等待编译器支持C++0x才能获得这个功能。在此之前,请见谅。