工会活跃成员背后的理由
Rationale behind active members of unions
C++ 的联合比 C 的联合更具限制性,因为它们引入了 "active member"(最后分配给的那个)作为唯一可以安全访问的概念。在我看来,工会的这种行为完全是负面的。有人可以解释一下这个限制有什么好处吗?
简答
在C中,并集只是一个如何解释存储在给定位置的数据的问题。数据是被动的。
在 C++ 中,联合可以具有不同 classes 的成员。而class对象不仅有数据,还有一个行为。由于您依赖于这种(可访问的)行为(甚至可能无法访问私有成员和受保护成员),因此必须确保对象从构造到销毁保持一致。活跃成员的概念就是为了这个目的而存在的:确保对象生命周期是一致的。
更长的解释
想象一下以下联合:
union U {
string s;
int i;
// due to string, I need to define constructor and destructor
U (string s) : s(s) { cout << "s is active"<<endl;}
U (int i) : i(i) { cout << "i is active"<<endl;}
U() : s() { cout << "s is active by default" <<endl; }
~U() { cout << "delete... but what ?"<<endl; }
};
现在假设我初始化它:
U u("hello");
当时的活跃成员是s
。我现在可以毫无风险地使用这个活跃的成员:
u.s += ", world";
cout << u.s <<endl;
在更改活动成员之前,我必须确保成员的生命周期结束(C++ 标准要求)。如果我忘了这个,例如使用另一个成员:
u.i=0; // ouch!!! this is not the active member : what happens to the string ?
然后我有未定义的行为(实际上,s 现在已损坏,不再可能恢复存储字符的内存)。你也可以想象相反的情况。假设活动成员是 i,我现在想使用字符串:
u.s="goodbye"; // thinks that s is an active valid string which is no longer the case
在这里,编译器假设我知道 s 是活动成员。但由于 s 不是正确初始化的字符串,执行复制运算符也会导致未定义的行为。
Demo of what you should not do
如何正确操作?
标准解释:
If M has a non-trivial destructor and N has a non-trivial constructor
(for instance, if they declare or inherit virtual functions), the
active member of u can be safely switched from m to n using the
destructor and placement new-expression as follows:
u.m.~M();
new (&u.n) N;
因此,在我们讨厌的示例中,以下内容将起作用:
u.s.~string(); // properly end the life of s
u.i=0; // this is now the active member
// no need to end life of an int, as it has a trivial destructor
new (&u.s) string("goodbye"); // placement new
cout << u.s <<endl;
C++ 的联合比 C 的联合更具限制性,因为它们引入了 "active member"(最后分配给的那个)作为唯一可以安全访问的概念。在我看来,工会的这种行为完全是负面的。有人可以解释一下这个限制有什么好处吗?
简答
在C中,并集只是一个如何解释存储在给定位置的数据的问题。数据是被动的。
在 C++ 中,联合可以具有不同 classes 的成员。而class对象不仅有数据,还有一个行为。由于您依赖于这种(可访问的)行为(甚至可能无法访问私有成员和受保护成员),因此必须确保对象从构造到销毁保持一致。活跃成员的概念就是为了这个目的而存在的:确保对象生命周期是一致的。
更长的解释
想象一下以下联合:
union U {
string s;
int i;
// due to string, I need to define constructor and destructor
U (string s) : s(s) { cout << "s is active"<<endl;}
U (int i) : i(i) { cout << "i is active"<<endl;}
U() : s() { cout << "s is active by default" <<endl; }
~U() { cout << "delete... but what ?"<<endl; }
};
现在假设我初始化它:
U u("hello");
当时的活跃成员是s
。我现在可以毫无风险地使用这个活跃的成员:
u.s += ", world";
cout << u.s <<endl;
在更改活动成员之前,我必须确保成员的生命周期结束(C++ 标准要求)。如果我忘了这个,例如使用另一个成员:
u.i=0; // ouch!!! this is not the active member : what happens to the string ?
然后我有未定义的行为(实际上,s 现在已损坏,不再可能恢复存储字符的内存)。你也可以想象相反的情况。假设活动成员是 i,我现在想使用字符串:
u.s="goodbye"; // thinks that s is an active valid string which is no longer the case
在这里,编译器假设我知道 s 是活动成员。但由于 s 不是正确初始化的字符串,执行复制运算符也会导致未定义的行为。
Demo of what you should not do
如何正确操作?
标准解释:
If M has a non-trivial destructor and N has a non-trivial constructor (for instance, if they declare or inherit virtual functions), the active member of u can be safely switched from m to n using the destructor and placement new-expression as follows:
u.m.~M(); new (&u.n) N;
因此,在我们讨厌的示例中,以下内容将起作用:
u.s.~string(); // properly end the life of s
u.i=0; // this is now the active member
// no need to end life of an int, as it has a trivial destructor
new (&u.s) string("goodbye"); // placement new
cout << u.s <<endl;