суббота, 12 июля 2008 г.

Processing STL containers

Уже не первый раз в ходе code review натыкаюсь на ошибку связанную с организацией обработки того или иного контейнера. Суть проблемы в организации цикла обработки, как показано в примере:
typedef std::list<sometype_t> list_t;
list_t sample_list;
void f( void ) {
// Remove items from list
for ( list_t::iterator it = sample_list.begin(); it != sample_list.end(); ++it ) {
if ( it->condition == true ) {
it = sample_list.erase( it ); // <- errors here !!!
}
}
}


Для начинающих разработчиков, по всей видимости, этот код кажется безобидным. Однако, если внимательно посмотреть, то можно заметить, что после удаления элемента происходит заход на следующий цикл. А что там? Там первым делом происходит ++it, т. е. мы перескакиваем через элемент следующий за удаленным. Плохо? Конечно, но и это ещё не всё. Если мы удалили последний элемент в списке, то it окажется за пределами sample_list.end() и цикл, по всей видимости, завершится уже только из-за ошибки access violation.

Чтобы избежать таких недоразумений лучше написать что-то вроде:
void f( void ) {
// Remove items from list
list_t::iterator it = sample_list.begin();
while ( it != sample_list.end() ) {
if ( it->condition == true ) {
it = sample_list.erase( it );
} else {
++it;
}
}
}


Ещё один интересный момент, это использование такой конструкции для удаления:
sample_list.erase( it++ );

Смотрится симпатичнее варианта со знаком равенства. Однако тут тоже кроется опасность. Если контейнером будет std::list, то ошибки не произойдет. Но что, если мы решили поменять тип контейнера на std::vector, например? А будет вот что: it++ запомнит текущее значение it, которое затем и будет передано в erase – тут все неплохо. Однако перед вызовом erase итератор it будет указывать на следующий элемент контейнера из-за переаллокации элементов контейнера. Для std::vector после работы erase в этом месте будет элемент, следующий за тем, что был нужен. А в худшем случае – опять access violation.

среда, 9 июля 2008 г.

implicit c-tor

Пока пробовал реакцию компиляторов на предыдущий пост обнаружил такую ошибку в gcc (версия 4.2.3 Ubuntu) — следующий код не компилируется, хотя должен:
#include <iostream>

struct type1 {
type1() {};
};

struct type2 {
type2( const type1& ) {};
};

struct sss {
sss( type2 ) {
std::cout << "type2" << std::endl;
};
};

int main() {
type1 x;
sss xxx = x; // <- тут выдается ошибка в gcc
return 0;
}

Поясню что тут должно произойти: класс sss должен конструироваться из класса type2, который, в свою очередь, неявно получается из type1. В компиляторе от Microsoft все так и работает.

Кстати, если писать явно sss xxx( x );,то работает и в gcc.

P.S. Ответ разработчиков gcc был следующим: Only a single user-defined conversion is allowed in an implicit conversion sequence, but the copy-initialization would require two.
Что в переводе означает только то, что та форма инициализации, которая вызывает ошибку, требует два неявных преобразования, а это запрещено.

Остается неясным почему это запрещено(пока не нашел в стандарте) и, если это так и есть, то почему компилятор от Microsoft не выдает ошибку?..

среда, 2 июля 2008 г.

Coding style

Наткнулся при поиске в интернете на стиль написания кода принятый для открытых(а может и закрытых) проектов Google. Требования довольно разумные и могут быть взяты за основу в любой компании, если там пока анархия в этом плане. Смотреть тут.

Дебри boost::multi_index

На днях пришлось выводить тип части композитного ключа в boost::multi_index. Результат меня поразил своим размером... Смотрите сами, для кода ниже:


struct eventinfo_t {
long eventId; /** index **/
long groupId; /** index **/

bool isMarked; /** index **/
bool isHovered; /** index **/
};

typedef multi_index_container<
eventinfo_t,
indexed_by<
/* 0 */ ordered_unique< member<eventinfo_t, long, &eventinfo_t::eventId>>,
/* 1 */ ordered_non_unique< member<eventinfo_t, long, &eventinfo_t::groupId>>,
/* 2 */ ordered_unique<
composite_key<
eventinfo_t,
member<eventinfo_t, bool, &eventinfo_t::isMarked>,
member<eventinfo_t, long, &eventinfo_t::eventId>
>
>
>
> eventmap_t;


Получается, чтобы вывести тип второй части композитного ключа нужно написать довольно нетривиальное выражение:

eventmap_t::nth_index<2>::type::key_type::composite_key_type::key_extractor_tuple::tail_type::head_type::result_type


Если бы частей в композитном ключе было больше, то нужно было бы писать:


...key_extractor_tuple::tail_type::[tail_type:: x <нужная часть>]head_type::result_type


Может есть другой способ?..