Tuesday, May 27, 2008

c++: virtual functions in contructors

There is a limitation on virtual function calls in constructors. If you call a virtual function in the constructor of the base class that was overridden in derived class you will be surprised. The function of base class will be called:

class A
{
    public:
        A() {function();}
        virtual void function() {cout << "A" << endl;}
};

class B : public A
{
    public:
        B() {function();}
        virtual void function() {cout << "B" << endl;}
};

int
main(int argc,
    char **argv)
{

    B b;

    return 0;
}
This code produces
A
B
output. The explanation is pretty simple, when constructor of the base class is called the derived object has not been constructed. This way c++ protects us. You don't have access to the derived class object from the the base class constructor. Only when initialization of base class was had been finished virtual table is being refreshed.

Friday, May 23, 2008

c++: reuse memory for objects

It happens when you are creating and deleting objects heavily. Like this:

    for (int i=0;i<1000000;++i)
    {   
        A *a = new A;

        //do something with 'a'

        delete a;
    }
Yup, it happens and sometimes it's a best solution. If you can't use stack memory because A is pretty big and you used almost all of the stack memory before or used a recursion. But this is extremely slow. You obtain memory fragmentation and invoke memory manager to get free chunk of memory and than free it. c++ specification allows you to reuse memory:
#include <new>

int
main(int argc, char **argv)
{
    char *memory = new char[sizeof(A)];
    void *place = memory;
    
    for (int i=0;i<1000000;++i)
    {
        A *a = new(place) A;

        //do something with 'a'

        a->~A();
    }   
}
In the code sample above A in the loop is always put into the 'memory'. The executable will request for the chunk of memory once before the loop. Depending on the code this may be more than 10 times faster. While using new() developer have to call destructor explicitly and include 'new' header manually. Anyway, if you can put A in the loop into the stack, try to do it. It the fastest and the safest way:
    for (int i=0;i<1000000;++i)
    {   
        A a;

        //do something with 'a'
    }   

Thursday, May 22, 2008

c++: default initializers

In c++.03 you are unable to call explicitly one constructor within another:

class A
{
    public:
        A(int x, int y) : x(x), y(y) {}
        A(int x) { A(x, 0); }

        int x, y;
}
This will produce compilation errors. As a workaround developers usually use an initialization function that can be called within the constructor:
class A
{
    public:
        A(int x, int y) : {init(x, y);}
        A(int x) {init(x,0);}

        int x, y;

    protected:
        void init(int a, int b) {x = a; y = b;}
}
The idea I got today is to make an abstraction of class data in base struct, derive class from the struct and initialize struct data with it's constructor. This will make things clear and will allow to separate data from its manipulation:
struct A
{
        A(int x, int y) : x(x), y(y) {}

        int x, y;
};

class B : public A
{
    public:
        B() : A(0, 0) {}
        B(int x) : A(x, 0) {}
};

c++: constructor arguments

Names of constructor arguments which will initialize class members can have the same name as class members:

class A
{
    public:
        A(int x) : x(x) {} 

        int x;
};
I used to make a prefix for constructor arguments as thought compiler will produce an error message. Yeah, now source code can be cleaner. Be aware that next code won't work:
class A
{
    public:
        A(int x) {x = x;} 

        int x;
};
Here x in constructor's body is the argument of the contructor. You will have an unexpected value of A::x each time as it not initialized. As a workaround:
class A
{
    public:
        A(int x) {this->x = x;}

        int x;
};
Here x in constructor's body is argument of constructor and this->x is A::x.

Wednesday, May 7, 2008

bash: completion

Almost everybody uses bash. It's awesome with it simplicity and power. Recently I've been introduced to bash completion in conjunction with ssh. The author proposed to use bash completion to expand list of known hosts(~/.ssh/known_hosts) for ssh. He suggested to fetch the list and feed it to 'complete' built-in bash command:

complete -W "`cat ~/.ssh/known_hosts \
| cut -d ' ' -f1 | cut -d ',' -f1 | cut -d ']' -f1 \
| sed 's/\[//' | sort`" ssh
This command will provide a fixed list. If you eventually have gone to the new host the new hostname will be lost for the completion. I've made some investigation and have found out that you can provide a function for 'complete' that will be called each time the completion requested. Here is a function:
function _ssh_comp()
{
CUR="${COMP_WORDS[COMP_CWORD]}";
COMPREPLY=( $(compgen -W "$(gawk 'BEGIN {i=0}\
{split($1,nodes,",");\
gsub("([[]|[]]:?[0-9]*)","",nodes[1]);\
hosts[i++]=nodes[1]}\
END {for (j in hosts) {print hosts[j]}}' ~/.ssh/known_hosts)" -- ${CUR}) );
return 0;
}
From the bash reference:
We can read the description of COMPREPLY
An array variable from which Bash reads the possible completions generated by a shell function invoked by the programmable completion facility
We can also see how we found the current word using the array COMP_WORDS to find both the current and the previous word by looking them up
An array variable consisting of the individual words in the current command line. This variable is available only in shell functions invoked by the programmable completion facilities.
COMP_CWORD
An index into ${COMP_WORDS} of the word containing the current cursor position. This variable is available only in shell functions invoked by the programmable completion facilities
Then you should tell 'complete' to use this function:
complete -o default -F _ssh_comp ssh
I typed 'ssh 1' then pressed and here we go:
$ssh 192.168.229.1
192.168.229.128  192.168.229.130  192.168.229.132  192.168.229.134  192.168.229.137  192.168.229.140  192.168.229.145  
192.168.229.129  192.168.229.131  192.168.229.133  192.168.229.136  192.168.229.138  192.168.229.144  192.168.229.146