Monday, July 27, 2009

Parsing input configuration files - Part 1

Hi,

When dealing with loading input configuration files, it is annoying to parse each line of the file and store the value in a parameter for each line. This often clutters the code due to the extensive if-else if-else if...-else statements for each config parameter. In the end this greatly affects the code maintainability. So let me introduce you a better approach to minimize the pain of maintenance.

Assumptions
I take it for granted that I'm addressing the audience who have their code in C++. I assume that you all might have stored the set of input parameters in a structure or a class, say ConfigStruct. If you haven't done so, I recommend encapsulating your input/output parameters in a nice structure atleast now. I use STL in this example to make things easier - you may also use your own version of the STL container if you are against using STL for some personal reasons.
Scenario: (existing parser logic)
If your input config parsing logic is something like,

if ( !strcmp(inputParam1,"paramStr1") ) {
load paramStr1's value into inputParam1
} else if ( !strcmp(inputParam2,"paramStr2") ) {
load paramStr2's value into inputParam2
} else {
print that the config parameter is unrecognized and probably quit
}

then you've come to the right place.

Pointer-based approach
This method deals with two forms of association:
a. Tying each config file tag to the datatype of corresponding variable - introduce a STL "map" called tagTypeMap
b. Tying each config file tag to the address of corresponding variable - introduce a STL "map" called tagAddrMap

Declaration of these maps
std::map<std::string,std::string> tagTypeMap;
std::map<std::string,void*> tagAddrMap;

New parser logic
registerConfig() {
#define REGISTER(tag,var) tagTypeMap[tag]=typeid(var).name();
REGISTER("paramStr1",inputParam1)
REGISTER("paramStr2",inputParam2)
//and so on.
#define REGISTER_ADDR(tag,var) tagAddrMap[tag]=(void*)&var;
REGISTER_ADDR("paramStr1",inputParam1)
REGISTER_ADDR("paramStr2",inputParam2)
//and so on.
}

parseTag() {
if ( tagTypeMap.find(tag) == tagTypeMap.end() )
return; //variable not found

std::string type = tagTypeMap.find(tag)->second;

if ( tagAddrMap.find(tag) == tagAddrMap.end() )
return; //addr not registered

void *vptr = tagAddrMap.find(tag)->second;

// If you include a new data type
// as input parameter, please define
// conversion for that data type in here
if ( type == typeid(std::string).name() ) {
std::string *ptr = (std::string*) vptr;
*ptr = value;
} else if ( type == typeid(int).name() ) {
int *ptr = (int*) vptr;
*ptr = atoi(value.c_str());
} else if ( type == typeid(float).name() ) {
float *ptr = (float*) vptr;
*ptr = atof(value.c_str());
} //and so on
}

I'll go over the second approach (template-based) in one of my future blogs (possibly the next one).

Until we meet again,
-Srini-

Monday, July 20, 2009

[C++] Playing with the preprocessor directives

Hi,

This was something I shared with my friends when I worked in Juno Online. Thought it'd be interesting to share it with rest of the world! Without much waiting, here we go -

We have the following 3 files:
--file1.h--
#ifndef __file1_h
#define __file1_h
#include <iostream>

struct S
{
#ifdef _SRINI
int a;
#endif
int b;

void Assign(int x, int y)
{
#ifdef _SRINI
a=x;
#endif
b=y;
}

void Print()
{
#ifdef _SRINI
std::cout<<"a="<<a<<std::endl;
#endif
std::cout<<"b="<<b<<std::endl;
}
};

void Assign(int,int);

#endif


--file1.cpp--
#include "file1.h"

S s;

void Assign(int x, int y)
{
s.Assign(x,y);
}

--file2.cpp--
#include "file1.h"

int main()
{
extern S s;
Assign(1,2);
s.Print();
return 0;
}


Compile using the commands:
g++ -D_SRINI -c file1.cpp
g++ -c file2.cpp
g++ -o file file1.o file2.o


What do you expect when you run the 'file' binary?
Will it result in a compilation error?

Or will it output:
a=1
b=2 ?
Or is it just "b=2" ?

But actually the output will be "b=1". Let me reason it out.

According the file1.o, the "struct S" is defined as:
struct S {
int a;
int b;

void Assign(int x, int y)
{
a=x;
b=y;
}

void Print()
{
std::cout<<"a="<<a<<std::endl;
std::cout<<"b="<<b<<std::endl;
}
};


but according to file2.o the "struct S" is defined as:
struct S {
int b;

void Assign(int x, int y)
{
b=y;
}

void Print()
{
std::cout<<"b="<<b<<std::endl;
}
};


Now when main() calls Assign(1,2) of file1.0, the struct S sets a=1 and b=2 according to file1.o's definition. In the viewpoint of struct S, it has initialized its FIRST member to 1 and SECOND member to 2.
When Print() is invoked by main(), file2.o's version of struct S is being used. But this structure has only one member, i.e. 'b'. Since struct S knows that its first value is 1, it doesn't care about the variable name, it just prints 'b=1'.

To reconfirm this, if you set the datatype of 'a' to 'char', the output will contain the ASCII value of the character stored in 'a'.

Until we meet again,
- Srini -

Friday, July 17, 2009

[C++] Override inherited scope

Hi,

This particular possibility in C++ took me by surprise when I tried it out. Consider a (child) class inheriting another (parent) class in 'protected' scope. Ideally it seems that the parent's 'protected' methods should be accessible only by the child. An object of the child could not be used to access the parent's protected methods. Think again!

Consider this:
#include<iostream>
class parent
{
protected:
void method() {
std::cout<<"parent: I am a protected method"<<std::endl;
}
};

class child:protected parent
{
public:
parent::method;
};

int main(){
child cObj;
cObj.method();
return 0;
}

This compiles/runs perfectly on my g++ v4.1.2 on a Linux box. I had tried this earlier on SunOS CC and it worked just fine. Dev C++ 4.9.9.2 doesn't complain either.

I wonder if this is a feature overlooked by Stroustrup or am I missing something obvious?! - Comments most welcome :-)

Until we meet again,
- Srini -

What's this all about?

Hey friends,

Most of the times I struggle through a lot of technical problems and either find a solution myself or with the help of someone else. It just dawned upon me that these technical issues are common to a larger set of people and so I decided to use this blog as a journal to record the technical problems I come across. I hope this will be a good place to capture this.

Note: By 'technical', I mean 'software' because I'm a Computer Science graduate.

Regards,
Srini