包含非平凡成员的联合体类的构造函数和拷贝构造函数

19

我正在尝试实现一个定制的变体类型,它使用联合存储各种不同类型的数据。在字段type_id中,我计划存储联合中存储的数据的类型。该联合包含非平凡成员。这是我目前的实现:

struct MyVariant {
  enum { t_invalid, t_string, t_int, t_double, t_ptr, t_dictionary } type_id;
  union {
    int                             as_int;
    double                          as_double;
    std::string                     as_string;
    std::unique_ptr<int>            as_ptr;
    std::map<int, double>           as_dictionary;
  };
};

我尝试按照以下方式创建MyVariant的实例:

MyVariant v;

我收到了错误信息:调用MyVariant的隐式删除默认构造函数。所以,我尝试手动实现构造函数如下:

MyVariant() : type_id{t_int}, as_int{0} {}

这给了我一个类似的错误消息:尝试使用已删除的函数。 接下来,我尝试实现以下构造函数:

MyVariant(int value) : type_id{t_int}, as_int{value} {}

然后按照以下方式构建我的实例:

MyVariant v{123};

相同的错误信息: 尝试使用已删除的函数

我还开始实现一个复制构造函数,代码如下。但是,当然这并不能解决编译器错误。

MyVariant::MyVariant(const MyVariant& other)
{
    type_id = other.type_id;
    switch (type_id) {
        case t_invalid:
            break;
        case t_string:
            new (&as_string) std::string();
            as_string = other.as_string;
            break;
        case t_int:
            as_int = other.as_int;
            break;
        case t_double:
            as_double = other.as_double;
            break;
        case t_ptr:
            new (&as_ptr) std::unique_ptr<int>(nullptr);
            as_ptr = std::make_unique<int>(*other.as_ptr);
            break;
        case t_dictionary:
            new (&as_dictionary) std::map<int, double>();
            // TODO: copy values from other
            break;
    }
}

我正在使用Xcode和Apple LLVM 6.1编译器。

主要问题是:为什么我会得到当前的编译器错误?我该如何修改我的代码才能使其编译通过?

此外还有一个问题:我的构造函数和拷贝构造函数实现是否正确?


那个默认构造函数没问题。你需要实现一个析构函数。 - T.C.
你是不是想在分配一个实例时使用大括号而不是圆括号? - donjuedo
@donjuedo 是的,我想使用大括号,因为我想坚持使用统一的C++11初始化语法。它们有什么问题吗? - j00hi
@j00hi 不,我认为花括号本身没有问题。最近遇到了一个神秘(微妙)的类型转换问题,我想知道是否有更多的东西在幕后起作用,使得123变成一个对象。我的建议是尝试使用圆括号,看看直接将int传递给你明确选择的构造函数是否可以避开错误。 - donjuedo
1个回答

14

你的union有类型为stringunique_ptrmap的数据成员,它们都具有非平凡的默认/复制/移动构造函数、复制/移动赋值运算符和析构函数。因此,对于你的union,所有这些都被隐式删除了。

§9.5/2 [class.union]

...[注意:如果联合中有任何非静态数据成员具有非平凡的默认构造函数(12.1)、复制构造函数(12.8)、移动构造函数(12.8)、复制赋值运算符(12.8)、移动赋值运算符(12.8)或析构函数(12.4),则该联合的相应成员函数必须是用户提供的,否则将隐式删除(8.4.3)。—end note]

因此,您必须手动实现这些函数以便能够创建MyVariant实例。至少,类需要可构造和可析构。

MyVariant() : type_id{t_int}, as_int{0} {}
~MyVariant()
{
  switch(type_id)
  {
      case t_int:
      case t_double:
        // trivially destructible, no need to do anything
        break;
      case t_string:
        as_string.~basic_string();
        break;
      case t_ptr:
        as_ptr.~unique_ptr();
        break;
      case t_dictionary:
        as_dictionary.~map();
        break;
      case t_invalid:
        // do nothing
        break;
      default:
        throw std::runtime_error("unknown type");
  }
}

你的复制构造函数实现看起来是有效的,但我会采取不同的方式,而不是首先默认构造成员,然后从源对象进行复制。我会在placement new调用本身中使用复制构造函数。

MyVariant(const MyVariant& other)
{
  type_id = other.type_id;
  switch (type_id) {
      case t_invalid:
          break;
      case t_string:
          new (&as_string) auto(other.as_string);
          break;
      case t_int:
          as_int = other.as_int;
          break;
      case t_double:
          as_double = other.as_double;
          break;
      case t_ptr:
          new (&as_ptr) auto(std::make_unique<int>(*other.as_ptr));
          break;
      case t_dictionary:
          new (&as_dictionary) auto(other.as_dictionary);
          break;
  }

演示

请注意,如果unique_ptr成员处于活动状态,并且通过基类指针存储指向某个派生类实例的指针,则您的复制构造函数实现仅会复制基类部分。

最后,除非您将其作为学习练习进行,否则我强烈建议您使用Boost.Variant而不是自己编写程序。


3
小贴士:new (&as_dictionary) auto(other.as_dictionary) 等操作可以消除一些冗余代码,而且更方便进行重构。 - Luc Danton
Boost.Variant在当前的C++状态下已经过时。 - Tomilov Anatoliy
@Orient 为什么你认为 Boost.Varant 已经过时了?有什么好的、现代化的替代品吗? - j00hi
@j00hi,apply_visitor(Boost.Variant的一个重要组成部分)无法正确区分带有引用限定符的访问者的operator() - Tomilov Anatoliy
2
@abraham_hilbert 这是placement new语法,你正在传递要构建对象的地址。 - Praetorian
显示剩余4条评论

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