0

Say, for example, I have classes CPrimus, CSecundus, and CTertius that inherit from CParentis, and I want to implement a tree of objects where the leaves (nodes?) can be instances of any one of the derived classes, e.g.:

  • CPrimus
    • CTertius
  • CPrimus
    • CSecundus
      • CTertius
      • CTertius
  • CSecundus
    • CTertius
      • CPrimus
    • CTertius
      • CSecundus
      • CPrimus

What is the best* way to implement this tree in C++, and what modifications may be needed to the classes to support it?

(*) I will leave the definition of "best" up to the community, be it "fastest", "most efficient", "prettiest", or whatever you like. I personally have a preference for simple and efficient, but I'm willing to sacrifice simplicity for performance if the gain is worthwhile. Also, if you're going to suggest BGL please try to give an example, or at least link to an article or specific section of the documentation that explains how to implement the tree.

Edit:

Since the question is a bit too generalized, I'll give a potential use case. In Linux, a filesystem (commonly) will sit atop either a bare partition, an MD array, or an LVM logical volume. Assume I want my classes to represent these structures and store metadata about them (device name, size, uuid, etc etc) for later retrieval, and that the base class has a set of virtual functions for refreshing and retrieving the metadata.

Now, let's say that my system has a disk layout like so:

  • [FS-/boot]
    • [ARR-md0]
      • [PART-sda1]
      • [PART-sdb1]
  • [FS-/]
    • [LV-root]
      • [VG-system]
        • [ARR-md1]
          • [PART-sda2]
          • [PART-sdb2]

How would I represent this as a C++ data structure?

Justin ᚅᚔᚈᚄᚒᚔ
  • 15,081
  • 7
  • 52
  • 64
  • 6
    What does "represent" mean? XML? – Kerrek SB Jul 27 '11 at 19:10
  • Are you talking about database storage, or inheritance tree? – littleadv Jul 27 '11 at 19:14
  • Sorry about the unclear nomenclature. I think I mean "implement" -- I want to be able to access the tree either as an object or via a "manager" class from other C++ code. Right now, I have a rudimentary class with a map of `CParentis*` pointers, but I'd like to represent the parent/child relationships a bit better. – Justin ᚅᚔᚈᚄᚒᚔ Jul 27 '11 at 19:16
  • You may wish to read up on the Composite Pattern, which looks like a good fit for what you're trying to achieve. – qid Jul 27 '11 at 20:07

3 Answers3

3

Ah yes, multiple problems.

1. container There is no standard container for that, you could roll your own, but that's quite a lot of work to make it complete. The best thing I found is the Tree Container Library - free and very well documented. I haven't put it under heavy load, but it "looks good" to be an efficient implementation.

2. polymorphic containers - that is, containers with different types as elements - are a bit of a pain in C++. However, since you already have a shared base class, you have quite some options.

  • [edit] virtual methods in CParentis.
  • static_cast: you store the items as CParentis * (or better, smart pointer). If for an CParentis * you know what type it actually is, you can cast to it using a static_cast
  • dynamic_cast: same as above, but dynamic_cast will use runtime type information and you can "probe" for the different types. However, it can be comapredly slow depending on class heirarchy and compiler (game developers hate it because under most compilers it can't handle thousands of objects durign rendering a scene)
  • boost::any : That's a fabulous piece of code from boost that lets you store any type, and find out ask for it later. Like dynamic_cast, but doesn't need a shared base class. Requires an additional allocaiton, though - might be a problem for millions of objects.
  • boost::variant: Like boost:any, but for a predefined set of types, woth less overhead.

If you go with the static_cast route, you would need to add an type identifier to your class (or use typeof). However, a switch over types to enable specific handling is a code smell, since it scales badly with number of classes / number of objects involved. Better-scaling solutions are a shared abstract base class (when operations are known in advance) or the visitor pattern (when you need to throw on other operations later).

Last, the smallest: never call that a tree if there's a comp sci major in the room. Hel'll bash you over the head with his e-book reader, explain that it's a directed acyclig graph, and then complain you broke his toy (At least, that's what I imagine our comp sci guy doing if he had an e-book reader...)

peterchen
  • 40,917
  • 20
  • 104
  • 186
  • `static_cast`: don't `dynamic_cast`: don't `boost::any` : don't `boost::variant` : don't. (unless you know what are you doing). We have _virtual functions_ in C++, for this very purpose. All variations in behaviour of `CParentis`' children should go to virtual functions. – n. m. could be an AI Jul 27 '11 at 19:34
  • No, what the OP has described is clearly a tree, not a generic DAG. The leaf nodes are *instances* of one of `CPrimus`, `CSecondus`, or `CTertius`. Nowhere does he say that a given object can appear at multiple leaf nodes; it's implied that each leaf node is a unique object. – Adam Rosenfield Jul 27 '11 at 19:36
  • `CParentis` has a small set of virtual methods like `UpdateState()` and `Serialize()` that are implemented by each of the derived classes. So, when walking the "tree" I will only be calling the polymorphic methods and letting the objects handle the details themselves. In other words, I don't think I'll need to do any type sniffing at all. – Justin ᚅᚔᚈᚄᚒᚔ Jul 27 '11 at 19:57
  • @n.m.: sorry for forgetting the obvious method, I've added it. However, please notice that i haven't recommended any - that's because *I don't know* the use case. – peterchen Jul 27 '11 at 20:11
  • @peterchen, I updated the question with a use case, if it helps. – Justin ᚅᚔᚈᚄᚒᚔ Jul 27 '11 at 20:35
2
struct Node
{
  CParentis* content;
  std::vector<Node*> children;
};

Note that there's no mention of CPrimus etc. Indeed, this structure can hold anything derived from CParentis, including instances of not yet written classes.

You can also use std::whatever_smart_pointer_you_like<...> instead of bare pointers, and whatever container is convenient instead of a vector.

If you can modify CParentis, you may equip it with a std::vector<CParentis*> and do away with Node.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
0

you need to store pointers (or better smart pointers) to CParentis in your tree (obvious of course).

Now, about tree implementation. First, check this possible duplicate: What's a good and stable C++ tree implementation?.

Then, there's a STL-like tree implementation here: http://tree.phi-sci.com/

Community
  • 1
  • 1
Andriy Tylychko
  • 15,967
  • 6
  • 64
  • 112