Create the Node class

You created the header file node.hpp in a previous step. Replace the default contents of node.hpp with the following.

Contents of node.hpp

#pragma once

#include <string>
#include <vector>
//#include "split.hpp"

namespace strom

    //class Tree;
    //class TreeManip;
    //class Likelihood;

    class Node
            //friend class Tree;
            //friend class TreeManip;
            //friend class Likelihood;


                    Node *              getParent()     {return _parent;}
                    Node *              getLeftChild()  {return _left_child;}
                    Node *              getRightSib()   {return _right_sib;}
                    int                 getNumber()     {return _number;}
                    std::string         getName()       {return _name;}
                    double              getEdgeLength() {return _edge_length;}
                    //Split               getSplit()      {return _split;}

            typedef std::vector<Node>    Vector;
            typedef std::vector<Node *>  PtrVector;

            void                clear();

            Node *              _left_child;
            Node *              _right_sib;
            Node *              _parent;
            int                 _number;
            std::string         _name;
            double              _edge_length;
            //double              _x;
            //double              _y;
            //unsigned            _n;
            //Split               _split;

    inline Node::Node()
        std::cout << "Creating Node object" << std::endl;

    inline Node::~Node()
        std::cout << "Destroying Node object" << std::endl;

    inline void Node::clear()
        _left_child = 0;
        _right_sib = 0;
        _parent = 0;
        _number = 0;
        _name = "";
        _edge_length = 0.0;
        //_x = 0.0;
        //_y = 0.0;
        //_n = 0;


Explanation of node.hpp


The first line is a pragma, which is an instruction to the compiler that can be either used or ignored, depending on the compiler. The once pragma is recognized by both the Microsoft Visual Studio and Apple Xcode IDEs, telling them that this header file should not be included more than once.


The #include <string> statement near the beginning of the file causes the code defining a standard string object to be inserted at that location in node.hpp. That allows us to create and use objects of type std::string inside node.hpp (and any file that specifies #include "node.hpp"). You have probably noticed the #include "split.hpp" statement that has been commented out by preceding it with a double slash (//). We will uncomment this line later after we create the split.hpp file.


The tree class is wrapped in a namespace just in case this class is used along with other code that defines a class of the same name. The namespace we will be using to wrap everything in this tutorial is strom, which means tree in Czech. Note that the std in std::string is also a namespace. In order to use a standard string, you must qualify the name string with the namespace (i.e. std::string). This allows you to define a string class (if you desired) inside node.hpp, in which case you would need to refer to it as strom::string outside the strom namespace.

Data member convention

Note that all data members have names beginning with a single underscore character. This is a convention that will be used throughout to make it easy to recognize data members as opposed to local variables or function parameters.

Node data members

The data members of the Node class include three pointers to other Node objects.

The _parent pointer points to the ancestor of the current Node. If _parent is 0, then the current Node is the root node.
The _left_child pointer points to the left-most child node of the current Node in the tree. If _left_child is 0, then the Node is a leaf node in the tree.
Finally, the _right_sib pointer points to the next sibling Node on the right. If _right_sib is 0, then the current Node is the rightmost child of its ancestor.

There are three other data members of the Node class.

This is a string that represents the taxon name of a leaf and is often (but not necessarily) an empty string for interior nodes.
This is the node number, which serves as an index into the Tree::_nodes vector.
This is the length of the edge between this Node and its ancestor (i.e. _parent).

Finally, there are four data members (_x, _y, _n, and _split) that are currently commented out. You will uncomment these lines later when we need them.

Node member functions


The Node class has several member functions. Most of these functions are accessors: they provide access to the private data members that are not otherwise accessible except by a member function of the Node class itself. The accessor functions are getParent(), getLeftChild(), getRightSib(), getNumber(), getName(), getEdgeLength(), and (commented out for now) getSplit().

Note that these functions are defined (i.e. their function body is provided) directly in the class declaration (i.e. the part between class Node and };). This is fine for really simple functions, but for functions with even slightly more complicated bodies, we will move the bodies further down in the file to avoid making the class declaration too difficult to comprehend at a glance.

Constructor and destructor

Two member functions are special: the constructor function and the destructor function. You can identify these by the fact that they have no return type (not even void!) and their names are identical to the class name. The constructor is called automatically when a new object of the class is created, so it is the logical place for doing setup tasks that should be done before an object is used for the first time. The destructor is called automatically when the object is being deleted, and is the logical place for cleanup tasks that should be done before the object goes away.

The constructor just reports that an object of type Node has been created, and then calls the clear() function to initialize data members. The destructor simply reports that a Node object is being destroyed. We will eventually comment out these std::cout statements to avoid cluttering the output, but for now it is nice to be able to see when objects are created and destroyed.

Public versus private

You might wonder why we don’t just make all data members public? It is always wise to expose as little as possible to the outside world. Here, the accessors provide read-only access to anyone who is interested, but do not allow just anyone to make changes to the data members of the Node class. This makes it harder for someone who does not fully understand your code to introduce errors when modifying it. It also will save you from making errors in your own code!

We will find soon that some classes use private members of Node to such an extent that we will make these classes friends of Node. Any class declared as a friend of Node has full access to private data members. Because the classes Tree (which comprises Node objects), TreeManip (whose purpose is to manipulate Tree objects), and Likelihood (whose purpose is to compute the likelihood of a tree) do not yet exist, these friend declarations are currently commented out.