Wednesday, January 8, 2014

Chapter:02 Initializer List

Fundamental
========

List initialization is initialization of an object or reference from braced-init-list. Such an initializer is called initializer list, and the comma-separated initializer-clauses of the list are called the elements of the initializer list. This concept can be used to initialize any object and due to this it is also known as the universal initializer. In fact now it has been included to all standard container of C++. I think the key about this is, it encapsulate the initialization concept in a class. Now I would write a small program which would require to explain this concept. I would also use the debugger result to explain some of the concepts and why they behave in that way.


//Code Begins
#include<initializer_list>

void learn_initializer_list(void)
{
    std::initializer_list<int> ilist_x;
    std::initializer_list<int> ilist_y {1,2,3,4,5};
    auto sz_x = ilist_x.size();
    auto sz_y = ilist_y.size();

    auto itrs = ilist_y.begin();
    std::initializer_list<int>::const_iterator itre = ilist_y.end() - 1;
}

int main(int argc, const char* argv[])
{
    learn_initializer_list();
    return 0;
}

//Code Ends



The above simple program basically creates the two object of initializer list. I have used "auto", the another new feature introduced in C++11 in the current program. This class provides the begin() and end() method to access the members. However if we see the standard specification, this class do not provide any non const pointer/iterator mechanism. This is because initializer list class do not allow any modification once members of this class has been assigned with some values. This make complete sense and we would get to know once I would try to explain this with the debugger result.


//Debugger Output Begins
// This command would simply displays all the local variable defined
// inside this function.
(gdb) info locals
ilist_y = {
_M_array = 0x400670,
_M_len = 5
}
sz_x = 0
itrs = 0x400670
itre = 0x400399
ilist_x = {
_M_array = 0x0,
_M_len = 0
}
sz_y = 5
// These iterator are defined as const pointer. This is to optimize
// the read operation. It also ensures the safety and if program
// tries to write into these memory, there would be compilation
// error.
(gdb) ptype itrs
type = const int *

// The size of the objects of this class is two word size.
(gdb) p sizeof(ilist_x)
$2 = 16
(gdb) p sizeof(ilist_y)
$3 = 16

// This is the key concept in this scenario. We know that variable
// "itrs" stores the base pointer of this list. Now just read the
// 5 word from this base address as there is 5 members intitialized
// in "ilist_y". We get the expected output.
(gdb) x/5dw 0x400670
0x400670 <._0>: 1 2 3 4
0x400680 <._0+16>: 5


// Here i am trying to identify in which memory segment the base
// address "0x400670" lies. As we know that on GNU/Linux machine, we
// can check the virtual address map layout of any program. I have
// also used the grep command to shrink the output which is
// required to explain this.
(gdb) info proc
process 4732
cmdline = './test'
cwd = './c++11_notes'
exe = './c++11_notes/test'

// Now we can see that "0x400670" lies in the first segment in the
// below list. If we look more carefully, we can see that this is
// read-execute only segment(r-xp). Its look like std::initializer_
// list stores the members on the some part of .data segment.
// Normally any global/static variable initialized with some values
// stored in this segment which is write protected. This explains why
// it is serious error if we try write into these memory by any means
// .This is the reason why standard have not provided any non const
// interface so that client program can modify these. I think its
// consistent with the fact this class encapsulate the initialization
// of object. If you want to modify these values, first you should
// store it into some container and then you can modify these values
// within that container class.
(gdb) shell cat /proc/4732/maps|grep "test"
00400000-00401000 r-xp 00000000 08:03 10237508 ./test
00600000-00601000 r--p 00000000 08:03 10237508 ./test
00601000-00602000 rw-p 00001000 08:03 10237508 ./test
//Debugger Output Ends







Advantage
=======

In above section, we discussed that why initializer list provides const iterator in its all interfaces. This is very important from safety perspective and it also optimizes the reading members from it. Now let me try to use the const cast mechanism to remove const from these pointer and try to write into this memory. By now, we know this could be suicidal and something nasty would be the result.It would also demonstrate that we should try to avoid these casting mechanism uses in our program.



//Code Begins
#include<initializer_list>
#include<iostream>

void learn_initializer_list(void)
{
    std::initializer_list<int> ilist_y {1,2,3,4,5};
    auto itrs = ilist_y.begin();
    std::cout<<"First Element:"<<*itrs<<"\n";
    int* dangerous = const_cast<int*>(itrs);
    *dangerous = 100;
}

int main(int argc, const char* argv[])
{
    learn_initializer_list();
    return 0;
}


//Code Ends



If we run the above program, it would crash with SIGSEGV. This was expected as we did something which was not advised to do. Internally it stores members information into the memory segment which is write protected. Initializer list will not allow such a thing, if user tries to do such things accidentally, there would be compile time error. This is one of the great advantage of this mechanism.



This mechanism also provides the type safety and avoids any conversions that may cause information loss. This is major benefit from this mechanism which the programmer gets for free.


//Code Begins
#include<initializer_list>
#include<vector>

void learn_initializer_list(void)
{
    int var_x{int{3}};
    int var_y {int{1.2}};
    int var_z = 5.5;
    std::vector<int> vec_y{1, 2, 3.4, 4};
}

int main(int argc, const char* argv[])
{
    learn_initializer_list();
    return 0;
}

//Code Ends




When I compile the above program on GNU/Linux machine gcc 4.8.1. I get the following message on my console.


//Compiler Output Begins
$ g++ -g -gdwarf-2 -std=c++11 -Wall test.cpp -o test
test.cpp: In function ‘void learn_initializer_list()’:
test.cpp:7:20: warning: narrowing conversion of ‘1.2e+0’ from
‘double’ to ‘int’ inside { } [-Wnarrowing]
int var_y {int{1.2}};
^ test.cpp:9:37: warning: narrowing conversion of ‘3.3999e+0’
from ‘double’ to ‘int’ inside { } [-Wnarrowing]
std::vector vec_y{1, 2, 3.4, 4};
//Compiler Output Ends



Uses
===
No language feature can be used in isolation to deal with the non trivial problem. Initializer list concept can/should be used when we are writing a new container class or container type of class.


I was reading about the "std::array" container class which is also introduced in C++11 standard. I would cover this topic in detail in the subsequent chapter. But for current discussion,"std::array" does not have a public constructor. However there is other methods provided in the standard to initialize the members after we created the object."std::array" actually uses stack memory not the heap memory to store all members. This is the reason why standard has not been provided any public constructor.


"std::array" has been designed to match the performance of default array in C/C++ language which uses the stack memory. However, just to match the interface with the other containers classes, I have improved the "std::array" so that it can have the public constructor and behave like other standard container. While doing so I had to use the initializer list mechanism for my new class. The following program shows how we can use this concept in implementing container type classes.



//Code Begins

#include<initializer_list>
#include<array>
#include<iostream>

template<typename T, std::size_t max>
class myarray: public std::array<T, max>
{
public:
    typedef std::array<T,max> arraytype;

    myarray() {
        arraytype::fill(T{});
    }

    myarray(std::size_t sz, T val) {
        arraytype::fill(T{val});
    }

    myarray(std::initializer_list<T> lst) {
        auto * p = lst.begin();
        if(lst.size() >= arraytype::size() )
        {
            for(std::size_t cnt = 0; cnt < arraytype::size(); ++cnt)
            {
                arraytype::operator[] (cnt) = p[cnt];
            }
        }
        else
        {
            std::size_t cnt{0};
            for(; cnt < lst.size(); ++cnt)
            {
                arraytype::operator[] (cnt) = p[cnt];
            }

            for(std::size_t remain = cnt; remain < arraytype::size(); ++remain)
            {
                arraytype::operator[] (remain) = T{};
            }              
        }
    }

};


void learn_initializer_list(void)
{
    constexpr std::size_t sz {5};
    myarray<int, sz> var_x;
    myarray<int,sz>  var_y(sz, 1);
    myarray<int,sz>  var_z{7,8,9};

    for(std::size_t i = 0; i < var_x.size(); ++i)
        std::cout<<var_x[i]<<"\t"<<var_y[i]<<"\t"<<var_z[i]<<"\n";
}

int main(int argc, const char* argv[])
{
    learn_initializer_list();
    return 0;
}


No comments:

Post a Comment