C++联合体的用法

7

到目前为止,我只使用联合体来存储成员A或成员B。
现在我发现自己需要在运行时更改所使用的成员。

union NextGen {
    std::shared_ptr<TreeRecord> Child = nullptr;
    std::vector<std::shared_ptr<TreeRecord>> Children;
};

我的当前使用情况:

void TreeRecord::AddChild(const std::shared_ptr<TreeRecord>& NewChild) {
    if(_childCount == 0) {
        _nextGeneration.Child = NewChild;
        _childCount++;
    } else if(_childCount == 1) {
        //This is not clear to me:
        //Do I have to set Child to nullptr first?
        //Do I need to clear the Children vecor?
        //Or will it work like this?
        _nextGeneration.Children.push_back(_nextGeneration.Child);
        _nextGeneration.Children.push_back(NewChild);
        _childCount++;
    } else {
        _nextGeneration.Children.push_back(NewChild);
        _childCount++;
    }
}

新实现(尝试):

typedef std::shared_ptr<TreeRecord> singlechild_type;
typedef std::vector<std::shared_ptr<TreeRecord>> children_type;



union {
    singlechild_type _child;
    children_type _children;
};



void TreeRecord::AddChild(const singlechild_type& NewChild) {
    if(_childCount == 0) {
        _child = NewChild;
        _childCount = 1;

    } else if(_childCount == 1) {
        singlechild_type currentChild = _child; //Copy pointer
        _child.~singlechild_type(); //Destruct old union member
        new (&_children) children_type(); //Construct new union member
        _children.push_back(currentChild); //Add old child to vector
        _children.push_back(NewChild); //Add new child to vector
        _childCount = 2;

    } else {
        _children.push_back(NewChild);
        _childCount++;
    }
}
2个回答

2
你需要一个符合 C++11 标准的编译器。阅读有关 union 的内容。
通常,你需要显式地调用旧联合成员的析构函数,然后是新联合成员的构造函数。实际上,最好使用标记联合,实际的union是匿名的,并且是某个类的成员。
class TreeRecord;

class TreeRecord {
   bool hassinglechild;
   typedef std::shared_ptr<TreeRecord> singlechild_type;
   typedef std::vector<std::shared_ptr<TreeRecord>> children_type;
   union {
     singlechild_type child;  // when hassinglechild is true
     children_type children;  // when hassinglechild is false
   }
   TreeRecord() : hassinglechild(true), child(nullptr) {};
   void set_child(TreeRecord&ch) {
     if (!hassinglechild) {
       children.~children_type();
       hassinglechild = true;
       new (&child) singlechild_type(nullptr);
     };
     child = ch;
   }
   /// other constructors and destructors skipped
   /// more code needed, per rule of five
}

请注意,我明确调用析构函数~children_type(),然后使用定位new显式地调用child的构造函数。

不要忘记遵循五个规则。所以你需要在上面添加更多的代码

另请参见boost::variant

顺便说一下,你的代码表明你区分拥有一个child和拥有一个元素向量children的情况。这是自愿的和有意义的吗?

PS. 在一些语言中,特别是Ocaml,标签联合(又称总和类型)比C++11更容易定义和实现...请参阅代数数据类型的维基页面。


我甚至没有考虑过那个!你所说的“一般”是什么意思? - Noel Widmer
这是正确的答案,但如果您能详细说明就更好了。 - mostruash
我不会使用只有一个元素的向量。如果只有0或1个元素,我会使用Child,而当元素数量大于1时,则会切换到向量。或者我犯了错误……?(我仍在阅读您的代码)我正在使用标记联合,只是没有布尔标识符,而是使用“size_t _childCount”,当使用Child时它为0或1。 - Noel Widmer
@LightnessRacesinOrblit:我只是提到了它,但 Boost 可能存在的问题是它需要额外的依赖关系。有时人们不喜欢这一点(我理解这种观点)。 - Basile Starynkevitch
@NoëlWidmer:它可能应该可以工作。使用调试器进行测试。 - Basile Starynkevitch
显示剩余2条评论

1
注意,同时使用联合体的多个元素会导致未定义的行为,例如:
    _nextGeneration.Children.push_back(_nextGeneration.Child);

正如@BasileStarynkevitch所提到的,一种方法是避免使用标记联合。

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