пятница, 23 октября 2009 г.

Deleting incomplete type

Поскольку люди стали задавать вопросы зачем вообще нужна проверка на то, что тип является полностью определенным, то разъясню этот момент подробнее.

Дело в том, что стандарт в части 5.3.5/5 разрешает удаление объектов неполностью определенных типов:
If the object being deleted has incomplete class type at the point of deletion and the complete class has a non-trivial destructor or a deallocation function, the behavior is undefined.
Продемонстрировать проблему можно на следующем простом примере:
struct X; // неполностью определенный тип (определяется позднее)

int main()
{
  X* x = 0;
  delete x; // стандарт разрешает это

  return 0;
}

struct X
{
private:
  ~X() {}; // private destructor
};
Этот код компилируется несмотря на приватный деструктор, и, очевидно, этот деструктор не вызывается. В этом и есть опасность. Приличные компиляторы выдают предупреждение в этом случае, но ошибка была бы полезнее. И функция SafeDelete обеспечивает выдачу такой ошибки.

Useful templates

В дополнение к предыдущему сообщению ещё пара полезных шаблонов:
//Безопасное удаление
template<typename T>
inline void SafeDelete( T*& p )
{
  // Проверка, что тип полностью определен
  typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];
  (void) sizeof(type_must_be_complete);
  // проверять p на равенство нулю не нужно, т.к. стандарт 
  // гарантирует корректную работу delete
  delete p;
  p = NULL;
}
Если нет запрета на использование boost, то лучше использовать boost::checked_delete.

// Безопасное удаление через Release
template<typename T, typename D>
inline void SafeRelease( T*& p, D d ) 
{ 
  if ( p != NULL ) {
    (p->*d)();
    p = NULL;
  }
}
template<typename T>
inline void SafeRelease( T*& p) 
{ 
  return SafeRelease( p, &T::Release );
}


UPD:22 окт добавлена проверка на то, что тип является полностью определенным.

понедельник, 19 октября 2009 г.

Portable way to shut up compiler warning

Совсем недавно Герб Саттер (Herb Sutter) привел пример портируемого способа запретить вывод предупреждений компилятора о неиспользуемых переменных. Широко используемый способ — определить макрос следующего вида:
#define UNUSED(x) x
Однако, этот способ не работает в некоторых компиляторах (в том смысле, что не убирает предупреждение). Саттер предлагает определить простой шаблон:
template<class T> void ignore( T& ) { }
Можно это определение поместить в неймспейс для избежания конфликтов, чего нельзя сделать с макросом. Следует отметить, что параметр функции ignore<T> следует оставить без имени, чтобы не получить предупреждение теперь уже в другом месте.

Использовать шаблонную функцию довольно просто:
static void Constraints(D* p)
{
  B* pb = p;
  ignore(pb); // portably suppresses the warning
}
От себя хотел бы дополнить, что лучше писать так:
template<class T> void ignore( const T& ) { }
Это необходимо по причинам, которые обсуждались на stackoverflow.com.
UPD:Саттер услышал замечание насчет const и обновил свое сообщение.

воскресенье, 18 октября 2009 г.

Do not use & for arrays

Представьте, что вам попался код следующего вида:
typedef unsigned char Str255[257];
Str255 Blah;

void f( Str255 s )
{
  memcpy(&Blah, &s, sizeof(Str255)); // error here
}

int main()
{
  Str255 x("Blah");
  f( x );
}
Вопрос в том, почему данный код будет работать неверно, т.е. в Blah будет скопировано не содержимое x, а непонятный мусор? Ошибка тут в том, что в memcpy не нужно использовать &, а писать memcpy(Blah, s, sizeof(Str255));. Потому что s и &s — это разные указатели, и они даже имеют разный тип: unsigned char * and unsigned char (*)[257] соответсвенно. Обычно указатель на первый элемент массива и указатель на массив — это одно и тоже, но в нашем случае это не так. Посмотри как выглядит функция f, если не использовать typedef:
void f( unsigned char* s )
{
  memcpy(&Blah, &s, sizeof(Str255)); // error here
}
При вызове этой функции, в соответсвии с пунктом 4.2 стандарта C++, массив будет неявно преобразован в указатель и мы получим, что s указывает на массив с текстом, но &s будет указывать на указатель, который хранится на стеке, т.е. при копировании мы получим содержимое стека в Blah. Чтобы избежать такого недоразумения лучше использовать std::string в C++, ну а в C просто не использовать typedef для строк и понимать что происходит.

пятница, 9 октября 2009 г.

Best font for programming

Наверное, многие уже слышали о шрифте Consolas. Его особенность в том, что он был разработан с нуля для использования всех преимуществ ClearType. Другие моноширинные шрифты сглаживаются операционной системой и, как правило, смотрятся размыленно со включенным ClearType. Шрифт совершенно бесплатный и может быть скачан тут.

Я услышал о нем не очень давно и совершенно случайно, но теперь не представляю как можно было смотреть на другие шрифты. Использую его при написании кода и в Windows и в Ubuntu.

Consolas, 10pt:

Lucida Console, 9pt:


Шкриншоты позаимствованы с www.codinghorror.com.

пятница, 2 октября 2009 г.

Determining 32 vs 64 bit in C++

Для реализации некоторых операций, которые зависят от битности платформы, часто используют директивы препроцессора. Однако, существует проблема с переносимостью такого кода, т. к. в стандарте нет макросов, которые помогают идентифицировать битность платформы.

Можно попробовать решить такую задачу на этапе компиляции. Шаблоны дают все возможности для этого и код остается переносимым.

template<int> void DoMyOperation();

template<> void DoMyOperation<4>() 
{
  // do 32-bits operations
}

template<> void DoMyOperation<8>() 
{
  // do 64-bits operations
}

int main()
{
  // appropriate function will be selected at compile time 
  DoMyOperation<sizeof(size_t)>(); 

  return 0;
}
В примере выше использован тип size_t, но стандарт говорит только о том, что этот тип должен уметь хранить максимальный размер блока памяти в текущей реализации системы аллоцирования. Строго говоря, это только косвенно говорит о том скольки битная платформа используется, но сложно представить себе реализацию С++, где это будет неприменимо.