Monday, May 30, 2011

Neat Coding Trick # 1

Last Friday, I learned three neat coding tricks which I'm going to try to share here. The first one involves inherited classes in C++ and the use of virtual functions to create run-time decisions about which versions of a function to run. This is a very powerful trick because it simplifies function calls and data handling in situations where you have a number of related classes.

I've put together a little demonstration here to illustrate how useful this technique is:



#include<iostream> 
using namespace std;

class base {
protected:
 int x;
public:
 void setX(int val){x = val;};
 int getX(){return x;};
 virtual void incrX() = 0;
};

class derived1 : public base {
public:
 void incrX(){x+=1;};
};

class derived2 : public base {
public:
 void incrX(){x+=2;};
};

int main(){

 base *myVars[2];

 myVars[0] = new derived1;
 myVars[1] = new derived2;

 myVars[0]->setX(0);
 myVars[1]->setX(0);

 myVars[0]->incrX();
 myVars[1]->incrX();

 cout << myVars[0]->getX() << endl;
 cout << myVars[1]->getX() << endl;

 delete myVars[0];
 delete myVars[1];

 return 0;
}


We can think of this code as implementing two versions of a common class. The "common" portions of the class are in the base class. The base stipulates a private variable named x, as well as functions for setting and retrieving x. The base also stipulates an undefined virtual function named incrX. The way I've coded, this, any derived class that inherits base must define its own implementation of incrX. In the "derived1" class, incrX increments the value of "x" by 1, whereas in "derived2", incrX increments the value of x by 2.

In the main function, I create an array of pointers of type *base. I can then create new instances of the derived class and have those pointers stored in the array of type *base. This is a pretty amazing trick. Because derived1 and derived2 both inherit base, I am allowed to define a pointer of type *base and point it to either of the derived classes.

The second remarkable part of this code is that when I run the myVars[0]->incrX() command, the code is clever enough to realize that myVars[0] actually points to an object of class "derived1"; it then runs the appropriate version of incrX.

This trick is very handy because it is going to allow us to solve a few nasty problems in our neuron simulator. We have models for about five basic neuron types. In many ways, those neuron types are similar: all must keep track of who their pre-synaptic neurons are and all must have a function for numerically updating the state variables. However in other ways, those neurons are quite different: the differential equations and state variables are all different from neuron to neuron.

The elegant solution to this model is to create a "base" neuron which contains all the elements that are common to all neurons. The base neuron will also stipulate a virtual "update" function which will need to be redefined by each specific neuron type. Then we can create five "derived" classes which inherit the base and add the individual update functions and state variables. The great part is that in the "main" function, I only have to maintain a single array of neurons. I do this by creating an array of pointers of type *base. Then I can populate that array with any combination of the five neurons. When I tell a neuron to "update", the program makes sure that the update function appropriate to the specific neuron is called.

Another neat upshot of this technique is that the base neuron class can contain a vector of pointers to base which can be populated with pointers to presynaptic neurons, regardless of their specific type.

Thanks to Chris for figuring most of this out!

No comments:

Post a Comment