7
\$\begingroup\$

I've been programming in C++ again (after switching to web languages) for about 2 weeks now. I wrote this simple Node class from which all other objects within the Tree will be derived from. Any critique is very welcome here, however please use clear explanations, don't just point out flaws.

#include <string>
#include <vector>

using namespace std;

// DEFINITION

class Node 
{
public:

    Node(string name_);
    virtual ~Node() = 0;

    string GetName() const;

    void SetParent(Node * parent);
    Node * GetParent() const;

    Node * AddChild(Node * child);
    vector<Node*> FindChildren(string name) const;
    Node * FindFirstChild(string name) const;
    Node * FindLastChild(string name) const;
    Node * FindNthChild(int nth, string name) const;
    vector<string> ChildrenNames() const;

    int Depth() const;
    bool IsLeaf() const;
    bool IsRoot() const;

protected:

    string name;

    Node * parent;
    vector<Node*> children;

private:
};

// IMPLEMENTATION

Node::Node(string name_)
{
        name = name_;
        parent = NULL;
}

Node::~Node() 
{
    for(vector<Node*>::iterator it = children.begin(); it < children.end(); it++) 
    {
        if (*it) 
        {
            delete * it;
        }
    }
    children.clear();
}

string Node::GetName() const
{
    return name;
}


void Node::SetParent(Node * parent_) 
{
    parent = parent_;
}

Node * Node::GetParent() const
{
    return parent;
}

Node * Node::AddChild(Node * child)
{
    child->SetParent(this);
    children.push_back(child);

    return child;
}

vector<Node*> Node::FindChildren(string name) const
{
    vector<Node*> children;
    for(vector<Node*>::const_iterator it = children.begin(); it < children.end(); it++) 
    {
        if (name.compare((*it)->name) == 0)
            children.push_back(*it);

    }
    return children;
}

Node * Node::FindFirstChild(string name) const
{
    for(vector<Node*>::const_iterator it = children.begin(); it < children.end(); it++) 
    {
        if (name.compare((*it)->name) == 0)
            return (*it);

    }
    return NULL;
}

Node * Node::FindNthChild(int nth, string name) const
{
    int n = 0;
    for(vector<Node*>::const_iterator it = children.begin(); it < children.end(); it--) 
    {
        if (name.compare((*it)->name) == 0)
        {
            if (++n == nth)
                return (*it);
        }

    }
    return NULL;
}

vector<string> Node::ChildrenNames() const
{
    vector<string> names;
    for(vector<Node*>::const_iterator it = children.begin(); it < children.end(); it++) 
    {
        names.push_back((*it)->name);
    }
    return names;
}

int Node::Depth() const
{
    int d;
    Node * ancestor = parent;
    for (d = 0; ancestor != NULL; d++)
    {
        ancestor = ancestor->parent;
    }
    return d;
}

bool Node::IsLeaf() const
{
    return (children.size() == 0) ? true : false;
}

bool Node::IsRoot() const
{
    return (parent == NULL) ? true : false;
}
\$\endgroup\$
1
  • 2
    \$\begingroup\$ My immediate reaction is that the interface is at too low a level of abstraction for most purposes. What do you really want this to accomplish? \$\endgroup\$ Commented May 17, 2012 at 19:06

1 Answer 1

12
\$\begingroup\$

OK. I complain about this every time. Please don't do this.

using namespace std;

It may be in every C++ book but in real programs (more than 100) lines it causes more problems than it is worth. You can easily get tangled up in all sorts of problems with name look-up. The whole point of std being so short (not standard) is so that it is simple to prefix items in the standard library easily. You can also selectively use using to bring specific objects into the current namespace.

using std::cout; // now you can use cout
                 // But try and scope it as much as possible
                 // preventing pollution is a good thing.

But personally I have got used to typing std:: in-front of everything.

When returning objects

    string GetName() const;

prefer to return by reference. If you want to keep the object immutable return by const reference. Depending on usage it may be worth having two version a const and a non const version.

    string&       GetName();
    string const& GetName() const;

Why do you need a SetParent()?

    void SetParent(Node * parent);

This seems like you are exposing the implementation via the interface. This is binding your interface and tightly binding your implementation to this interface. I would remove this method. Remember that Node is a friend of itself so methods in Node can access and manipulate other objects of type Node (you trust yourself don't you).

There is absoilutely no reason to expose this.

    Node * GetParent() const;

You pass pointers around too much.

    Node * AddChild(Node * child);

OK. In a tree the ownership may seem obvious. But I would prefer to ownership explicitly defined by the code (this will prevent mistakes later). Thus you need to use smart pointers so that you explicitly define how ownership of pointers as they are passed around.

    // No need to return anything.
    // Explicitly pass ownership of the pointer.
    void AddChild(std::auto_ptr<Node> child);

These are OK returning pointers. As here you are saying that there may be no result and you need to validate any result (checking for NULL) before it can be used. But you can pass string by const reference.

    vector<Node*> FindChildren(string name) const;
    Node * FindFirstChild(string name) const;
    Node * FindLastChild(string name) const;
    Node * FindNthChild(int nth, string name) const;
    vector<string> ChildrenNames() const;

OK. Parent can be a pointer (as it potentially may be NULL).

    Node * parent;

Children on the other hand are owned.

  • One choice is a vector of smart pointers.
  • An alternative is boost::ptr_vector.

    vector<Node*> children;
    

Normally I would go with boost::ptr_vector (as it exposes its members as object references rather than pointers) but in this particular case I would go with a vector of smart pointers (because I want to manage the pointers in this case).

No need to test for NULL before deleting if (*it) { delete * it; } Just use:: delete (*it);

No point in calling clear. The destructor is about to destroy this object any way. By putting this here you are making the mainter think that there is something none obvious going on here.

    children.clear();


//  Just delete this function it is not required.
//  See below on how to replace it. 
/* void Node::SetParent(Node * parent_) 
{
    parent = parent_;
}*/

// No need for this. It is exposing internals without needing too.
/*Node * Node::GetParent() const
{
    return parent;
}*/

// Replace call to set parent
void Node::AddChild(std::auto_ptr<Node> child)
{
    // Check if the child is valid
    if (child.get() == NULL)
    {    return;
    }

    // child->SetParent(this);
    // Check that the child is not already in a tree.
    if (child->parent != NULL)
    {    throw std::runtime_error("Something bad happened this node is already in a tree");
    }

    // Add it to the tree.
    child->parent = this;
    children.push_back(child);

    // No need to return this
    // You have to think why does this return the child
    // I see no reason for this.
    // return child;
}

So you only check this node and its children!!!!!
If so this function seems to be named incorrectly. Searching a tree is normally recursive. The function should check the current node then RECUSIVELY check each child.

vector<Node*> Node::FindChildren(string name) const

Iterators are not compared with < you need to test against !=. While we are here prefer to use prefix ++ (here it does not make much difference but in other places it can so it is a nice habbit to get into (especially with iterators)).

    for(vector<Node*>::const_iterator it = children.begin(); it < children.end(); it++) 

Is there a particular reason you use compare() rather than the more natural ==?

        if (name.compare((*it)->name) == 0)

Try this:

std::vector<Node*> Node::FindChildren(std::string const& name) const
{
    std::vector<Node*> children;
    if (name == this->name)
    {
         children.push_back(this);
    }
    for(vector<Node*>::const_iterator it = children.begin(); it != children.end(); ++it) 
    {
        if (*it)
        {   it->FindChildren(name);
        }
    }
    return children;
}

Again. Only checking the children. See notes above. Same comments as last function.

Node * Node::FindFirstChild(string name) const

Does this really have any meaning for a tree? The nth child in this tree. What about the next tree down. Seems like a very strange interface.

Node * Node::FindNthChild(int nth, string name) const

Why are you looping over the vector.
std::vector has direct element access with operator[] if you want to make sure you don't exceed the size of the array then use at() and it will validate the index before use.

What about granchildren?

vector<string> Node::ChildrenNames() const

Sure it works. But initialize variables on declaration (d). And scope your variables (ancestor) so there lifespan does not exceed their need. Also your control loop is all messed up (you are incrementing d not the value you are testing in the condition!!!!

int Node::Depth() const
{
    int d;
    Node * ancestor = parent;
    for (d = 0; ancestor != NULL; d++)
    {
        ancestor = ancestor->parent;
    }
    return d;
}

Try this:

int Node::Depth() const
{
    // I hate single letter variables.
    // Make the name meaningful and represent what you are going to do with it.
    //
    // Short functions it does not matter. But for larger functions it makes
    // it so much easier to search see all the usage of the variable.

    int result  = 0;
    for (Node * ancestor = parent; ancestor;ancestor = ancestor->parent)
    {
        ++result;
    }
    return result;
}


bool Node::IsLeaf() const
{
    return (children.size() == 0) ? true : false;

    // Easier to read/write as

    return children.size() == 0;
}

bool Node::IsRoot() const
{
    return (parent == NULL) ? true : false;

    // Easier to read/write as

    return parent == NULL;
}
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Thank you VERY much! I will take all of your advice. I'm still a C++ newbie, as I said. I hadn't expected such a great answer. So once again: THANK YOU! \$\endgroup\$
    – Sparky
    Commented May 17, 2012 at 17:12
  • \$\begingroup\$ One adjustment: change it->FindChildren(name); to (*it)->FindChildren(name);. \$\endgroup\$
    – Kajzer
    Commented Apr 9, 2015 at 20:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.