问题
So far I have just used unions to store either member A or member B.
I do now find myself in the case where I want to change the used member during runtime.
union NextGen {
std::shared_ptr<TreeRecord> Child = nullptr;
std::vector<std::shared_ptr<TreeRecord>> Children;
};
My current usage:
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++;
}
}
New implementation (try):
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++;
}
}
回答1:
You need a C++11 compliant compiler. Read about union-s.
In general, you need to explicitly call the destructor of the old union member, and then the constructor of the new union member. Actually, you'll better have tagged unions, with the actual union
being anonymous and member of some class:
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
}
Notice that I am explicitly calling the destructor ~children_type()
then I am using the placement new
to explicitly call the constructor for child
.
Don't forget to follow the rule of five. So you need more code above
See also boost::variant
BTW your code is suggesting that you distinguish the case when you have a child
and the case when you have a one-element vector of children
. Is that voluntary and meaningful?
PS. In some languages, notably Ocaml, tagged unions (a.k.a. sum types) are considerably easier to define and implement than in C++11.... See wikipage of algebraic data types.
回答2:
Note, using more than one element of a union
concurrently invokes undefined behavior, e.g.
_nextGeneration.Children.push_back(_nextGeneration.Child);
As @BasileStarynkevitch mentioned, one way is to avoid this is a tagged union.
来源:https://stackoverflow.com/questions/34578600/c-union-usage