背景
最近遇到一个问题,当定义一个union的时候,发现初始化失败,具体如下:
class NonTrivial {
public:
NonTrivial(const std::string& s) : data(s) {
std::cout << "NonTrivial constructed with " << data << std::endl;
}
NonTrivial() {
std::cout << "NonTrivial constructed default" << data << std::endl;
}
~NonTrivial() {
std::cout << "NonTrivial destroyed with " << data << std::endl;
}
void Show() const {
std::cout << "Showing " << data << std::endl;
}
private:
std::string data;
};
union MyUnion {
int i;
double d;
NonTrivial nt;
// MyUnion(int x) : i(x) {}
// MyUnion(double y) : d(y) {}
// MyUnion(const std::string& s) : nt(s) {}
// ~MyUnion(){};
};
int main()
{
MyUnion test; // compiler error
}
结果:
从报错可以看出,MyUnion()的构造函数被删了…, 之前在这里讨论过编译器何时会生成默认的构造函数,按理说符合自动生成默认构造函数,为啥MyUnion()会被删掉了呢?这里涉及到一个规则:
在
union
中,如果包含非平凡成员(no-trivial),编译器无法为union自动生成合适的构造函数、析构函数或赋值操作符
那么问题来了,什么才是非平凡成员
?
非平凡成员
在 C++ 中,“非平凡成员”通常指的是具有复杂构造、析构或赋值行为的成员。以下是一些常见的非平凡成员特征:
-
自定义构造函数
如果类定义了任何自定义构造函数(包括有参数的构造函数),该类的构造函数是非平凡的。自定义构造函数会影响如何初始化对象。 -
自定义析构函数
如果类定义了自定义析构函数,则该类的析构函数是非平凡的。析构函数用于释放类对象占用的资源。 -
自定义赋值操作符
如果类定义了自定义赋值操作符(包括拷贝赋值和移动赋值),则该类的赋值操作符是非平凡的。赋值操作符用于将一个对象的值赋给另一个对象。 -
包含成员对象(也称为成员子对象)
如果类的成员是具有上述非平凡构造函数、析构函数或赋值操作符的类,则该成员也被认为是非平凡的。换句话说,如果一个类包含非平凡成员对象,那么这个类的构造函数、析构函数或赋值操作符也将是非平凡的。 -
有虚函数
如果类中定义了虚函数(即 virtual 函数),则这个类的析构函数是非平凡的,因为需要处理虚表(vtable)相关的资源释放。 -
有虚基类
如果一个类从虚基类继承,那么这个类的构造函数、析构函数和赋值操作符也会变成非平凡的,因为需要处理虚基类的初始化和清理。
根据这个规则基本可以看出,由于NonTrivial中含有string, 而string包含非平凡构造函数,所以nt是个非平凡成员
如何解决
- 手动定义Myunion的构造和析构函数,试用于小项目
- 当项目用的较多的时候,不适合给每个union写构造函数,毕竟使用union主要是利用其共用一块内存的特点,这时可以利用包裹器进行类型准换,初始化的时候避开Union, 而实际使用的时候再转Union, 下面式cub中实现的一个,我觉得还不错,大家可以参考使用。
template <typename T>
struct Uninitialized
{
/// Biggest memory-access word that T is a whole multiple of and is not larger than the alignment of T
typedef char DeviceWord;
enum
{
WORDS = sizeof(T) / sizeof(DeviceWord)
};
/// Backing storage
DeviceWord storage[WORDS];
/// Alias
T* Ptr()
{
return reinterpret_cast<T*>(this);
}
T& Alias()
{
return reinterpret_cast<T&>(*this);
}
};
struct TempStorage : Uninitialized<MyUnion> {};
int main()
{
TempStorage test; // 初始化提前到这里
MyUnion& a = test.Alias();
MyUnion* p = test.Ptr();
}
思考
其实在union中的成员函数nt, 虽然是非平凡成员,但是构造函数和析构函数都有,为啥union不能直接调用相关函数生成一个默认的构造函数和析构函数呢?不是很明白有多难。。。