Is it a bad idea in C++ to do something like:
struct Node
{
string name;
vector<Node> children;
};
I’m asking because it looks to me like resizing children for any reason could potentially cause an exponential copying cascade. But on the other hand, vector<Node*> has its own problems, and things like vector<shared_ptr<Node>>, while safe, have poor memory locality (due to too many indirections), which starts hurting when you’re trying to e.g. build a gigantic tree in memory.
So, in general, is it a bad idea to have a container of your own type in C++ (03)? Are there other reasons to avoid (or prefer) this idiom, instead of using containers of pointers?
It’s not bad as a general rule. It really comes down to the context.
In this case, the indirections of a shared pointer should not be significant compared to nontrivial resize operations, and the cascading (assuming that will be a problem).
If you can reserve the proper sizes prior to populating the vector or make copying trivial, then there’s not much to worry about here.
If it’s really gigantic, you could base your nodes of an external store, which handled (and potentially shared) the nodes. Then your resizing ops would be trivial, as your
childrenwould all be pointers.Depending on the size of the Nodes, this:
may be better than shared nodes. It really depends on the size, depth, how often you resize. A container isn’t bad, but you will have to know how your program will execute to choose the best strategy (profiling can also help here, even in cases when you figure you know the fastest).
If you know you have a lot of objects and a lot of resizing to do, then an fast external store will be a good choice; jalf’s answer outlines a good strategy for this.
You could also use a
listof nodevectors on a backing store if you will have a lot of mutations, and pointers to list elements for the children.Support for these more complex implementations also takes more time than your current design, but they are worth trying if you do need to perform a lot of mutations. Simplicity to implement and maintain would then be a bonus to your original design.
If your graph really doesn’t grow very large, another approach would be to use custom allocators which either referred to a backing store of nodes, or shrank less often than the default allocators.