为什么编译器不允许std :: string在union中?

时间:2010-08-19 12:50:37

标签: c++

我想在Union里面使用字符串。 如果我写如下

union U
{
   int i;
   float f;
   string s;
};

编译器给出错误,说U :: S有复制构造函数。

我读了一些其他帖子,了解解决此问题的其他方法。 但我想知道为什么编译器首先不允许这样做?

编辑:@KennyTM:在任何联盟中,如果成员被初始化,其他人将拥有垃圾值,如果没有初始化,则所有人都将拥有垃圾值。我认为,标记联合只是为从Union访问有效值提供了一些安慰。 您的问题:您或编译器如何在没有额外信息的情况下为联合编写复制构造函数?      sizeof(string)给出4个字节。基于此,编译器可以比较其他成员大小并分配最大分配(在我们的示例中为4字节)。内部字符串长度无关紧要,因为它将存储在单独的位置。让字符串为任意长度。 Union必须知道的是使用字符串参数调用字符串类复制构造函数。无论哪种方式编译器发现在正常情况下都必须调用复制构造函数,即使字符串在Union中,也要遵循类似的方法。所以我认为编译器可以这样做,分配4个字节。然后,如果为s分配了任何字符串,则字符串类将负责使用自己的分配器分配和复制该字符串。所以也没有内存损坏的可能性。

编译器中Union开发时字符串不存在吗? 所以我的答案还不清楚。 我是这个网站的新工作人员,如果有什么不对,请原谅。

7 个答案:

答案 0 :(得分:57)

因为在一个联合中有一个带有非平凡(复制/)构造函数的类没有意义。假设我们有

union U {
  string x;
  vector<int> y;
};

U u;  // <--

如果U是结构,u.xu.y将分别初始化为空字符串和空向量。但是工会成员共享同一个地址。因此,如果初始化u.xu.y将包含无效数据,反之亦然。如果它们都未初始化,则无法使用它们。无论如何,在联合中使用这些数据是不容易处理的,所以C ++ 98选择否认这一点:(§9.5/ 1):

  

具有非平凡构造函数(12.1)的类的对象,非平凡的复制构造函数(12.8),非平凡的析构函数(12.4)或非平凡的复制赋值运算符(13.5.3, 12.8)不能是一个联合的成员,也不能是一系列这样的对象。

在C ++ 0x中,此规则已经放宽(§9.5/ 2):

  

联合的最多一个非静态数据成员可能有一个大括号或等于初始化程序。 [注意:如果union的任何非静态数据成员具有非平凡的默认构造函数(12.1),复制构造函数(12.8),移动构造函数(12.8),复制赋值运算符(12.8),移动   赋值运算符(12.8)或析构函数(12.4),联合的相应成员函数必须由用户提供,否则将为联合隐式删除(8.4.3)。 - 结束记录]

但是仍然无法为联合创建(正确)con / destructors,例如你如何或编译器为上面的联合编写复制构造函数而没有额外的信息?要确保联合的哪个成员处于活动状态,您需要tagged union,并且需要手动处理构造和销毁,例如。

struct TU {
   int type;
   union {
     int i;
     float f;
     std::string s;
   } u;

   TU(const TU& tu) : type(tu.type) {
     switch (tu.type) {
       case TU_STRING: new(&u.s)(tu.u.s); break;
       case TU_INT:    u.i = tu.u.i;      break;
       case TU_FLOAT:  u.f = tu.u.f;      break;
     }
   }
   ~TU() {
     if (tu.type == TU_STRING)
       u.s.~string();
   }
   ...
};

但是,正如@DeadMG所提到的,这已经实现为boost::variant or boost::any

答案 1 :(得分:26)

想一想。编译器如何知道联合中的类型?

没有。联盟的基本操作基本上是一个按位演员。联合中包含的值的操作只有在每种类型基本上都可以填充垃圾时才是安全的。 std::string不能,因为这会导致内存损坏。使用boost::variantboost::any

答案 2 :(得分:14)

在C ++ 98/03中,union的成员不能有构造函数,析构函数,虚拟成员函数或基类。

基本上,您只能使用内置数据类型或PODs

请注意,它在C ++ 0x中发生了变化:Unrestricted unions

union {
    int z;
    double w;
    string s;  // Illegal in C++98, legal in C++0x.
};

答案 3 :(得分:7)

来自C ++规范§9.5.1:

  

具有非平凡构造函数,非平凡复制构造函数,非平凡析构函数或非平凡复制赋值运算符的类的对象不能是联合的成员。

这个规则的原因是编译器永远不会知道哪个析构函数/构造函数调用,因为它从来不知道哪个可能的对象在union中。

答案 4 :(得分:1)

如果你

,就会引入垃圾
  1. 分配字符串
  2. 然后分配一个int或float
  3. 然后又是一个字符串
  4. 字符串管理其他地方的内存。这个信息很可能是一些指针。在分配int时,该指针被赋予了。分配新字符串应该销毁旧字符串,这是不可能的。

    第二步应该销毁字符串,但不知道是否有字符串。

    他们显然在此期间找到了解决这个问题的方法。

答案 5 :(得分:0)

您现在可以做到。
当然,如果您首先初始化联合的任何其他成员,或者根本不初始化字符串,那么就会有问题。
由于字符串类会重载赋值运算符,因此您无法再通过赋值操作来初始化字符串:

this->union_string = std::string("whatever");

将失败,因为您仍在使用赋值运算符。

要在将其他内容放入联合中或未首先对其进行初始化之后正确初始化联合字符串,必须直接在该内存上调用构造函数:

new(&this->union_string) std::string("whatever");

这样,您根本就根本不使用赋值函数。

另一个值得关注的问题是,编译器应使您成为析构函数,如果由于某种原因而不是,则应使它成为析构函数。由于这是一个联合,因此在类的生命周期结束时,编译器无法知道该联合内存是否被字符串或其他方式使用,因此,在这种情况下,析构函数应调用字符串的析构函数。
因此,如果不执行此操作,则会导致内存泄漏,因为永远不会调用该字符串的构造函数,并且它永远不会释放所使用的内存。

答案 6 :(得分:0)

在新的C ++标准(我在C ++ 17中进行了测试)中,您可以使用复杂类型作为并集的成员。

    struct ustring
    {
        union
        {
            string s;
            wstring ws;
        };

        bool bAscii = true;
        ~ustring()
        {
            if (bAscii)
            {
                s.~string();
            }
            else
            {
                ws.~wstring();
            }
        }
    };

但是,您应该非常小心。想想您构造s但破坏了ws。