联合体成员有一个非平凡的复制构造函数。

24

我有一个联合体,长这样:

union {
  int intValue;
  double doubleValue;
  std::string stringValue;
  void *pointerValue;
} values;

编译时,我收到了这个错误消息(是的,我确实包含了#include <string>):

./Value.hh:19:19: error: union member 'stringValue' has a non-trivial copy constructor                 
      std::string stringValue;                                                                         
                  ^                                                                                    
/Developer/SDKs/MacOSX10.7.sdk//usr/include/c++/4.2.1/bits/basic_string.h:434:7: note: because         
      type 'std::basic_string<char>' has a user-declared copy constructor                              
      basic_string(const basic_string& __str);                                                         
      ^

我使用以下命令进行编译:

$ clang++ *.cc -isysroot /Developer/SDKs/MacOSX10.7.sdk/ -shared

如何在联合体中使用 std::string


你真的可能不想使用联合。 - Rob K
5个回答

31

无法实现。

一个联合体结合了两个功能:存储可能是选择的若干类型之一的对象的能力,以及有效地(和实现定义)在这些类型之间进行转换的能力。您可以放入一个整数并查看其表示为双精度浮点数的形式。等等。

因为联合体必须支持这两个功能(以及其他一些原因,如能够构造一个),所以联合体阻止您执行某些操作。也就是说,您不能将“活”的对象放入其中。任何需要非默认复制构造函数(以及许多其他限制)的“活”的对象都不能成为联合体的成员。

毕竟,联合体对象实际上没有存储它实际存储的数据类型的概念。它不仅存储一种数据类型;它同时存储所有数据类型。您需要能够提取正确的类型。那么它如何合理地将一个联合值复制到另一个联合值中呢?

联合体的成员必须是POD(纯旧数据)类型。虽然C++11确实放松了这些规则,但对象仍必须具有默认(或其他简单)复制构造函数。而std::string的复制构造函数是非平凡的。

您可能需要的是boost::variant。这是一个可以存储若干可能类型的对象,就像联合一样。但与联合不同的是,它是类型安全的。因此,它知道联合中实际上是什么;因此,它能够复制自己并以其他方式行为像一个常规的C ++对象。


1
更新:variant 自 C++ 17 起已成为标准库的一部分:https://en.cppreference.com/w/cpp/utility/variant - LNiederha

7
您不能将 std::string 放入联合中。这在C ++语言中是不允许的,因为这是不安全的。请考虑,大多数 std::string 实现都具有指向某些动态内存的指针,该内存保存字符串的值。同时请注意,无法知道哪个联合成员当前处于活动状态。
实现程序无法调用 std::string 的析构函数,因为它不知道 std::string 对象是否是当前活动成员,但如果不调用析构函数,则会造成内存泄漏。

5
我相信在C++11中,你可以在联合体中放置带有非平凡构造函数/析构函数的类型,但必须使用定位new和t.~T()语法显式调用它们。9.5.3-9.5.4 - user802003
以下是来自 en.cppreference.com/ 的示例: https://en.cppreference.com/w/cpp/language/union - sahn

4

很遗憾,您不能在联合体中使用非POD(普通旧数据)类型。 这种情况下的一个非常典型和简单的解决方法是将联合体包装在结构体中,并将非POD实例从联合体内部移动到结构体中。

例如:

struct Value {
    union {
        int intValue;
        double doubleValue;
        void *pointerValue;
    };
    std::string stringValue;
};

Value value;

// Demonstration of accessing members:

value.intValue = 0;
value.doubleValue = 0.0;
value.pointerValue = NULL;
value.stringValue = "foo";

然而,这样做会付出代价 - Value 结构体的内存占用量将比原始联合体更大。


4
根据C++标准§9.5.1的规定:
一个具有非平凡构造函数、非平凡复制构造函数、非平凡析构函数或非平凡复制赋值运算符的类对象不能成为联合体的成员。
因此,联合体的成员不能具有构造函数、析构函数、虚成员函数或基类。因此,您不能将std::string作为联合体的成员使用。
替代方案:
您可以使用boost::variant或boost::any。

看来我需要调查一下为什么我能够很好地将一个带有自定义复制赋值operator=(T const &that)的类放入一个union中...多次尝试都没有问题。这个限制在C++14中被取消了吗?还是union会“回退”到另一个隐式复制赋值,例如operator=(T that) - underscore_d
我相信上面的问题只是编译器对我的宽容度不够。现在我避免使用那种模式:我的任何union成员都没有不应该有的东西。它们仍然运行得很好。 :) - underscore_d
1
自C++11以来,该段落已经消失。 - rustyx

2

union不能有以下类型的成员§9.5/1:

具有非平凡构造函数(12.1)、非平凡复制构造函数(12.8)、非平凡析构函数(12.4)或非平凡复制赋值运算符(13.5.3,12.8)的类对象以及此类对象的数组都不能成为联合体的成员。

因此,您可以将std::string的指针定义为:

union {
  int intValue;
  double doubleValue;
  std::string *stringValue; //pointer
  void *pointerValue;
} values;

或者,使用被称为Boost.Variant的Boost Union库。


你介意看一下我对Alok回答的评论(为了避免重复),并让我知道任何想法吗? - underscore_d

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接