понедельник, 24 января 2011 г.

Наследие OLE DB

С давних времен в коде различных проектов остались строки следующего вида для работы с OLE DB:
ULONG m_BLOBDATA_LENGTH;
ULONG m_BLOBDATA_STATUS;
// ...
BLOB_ENTRY_LENGTH_STATUS(4, IID_ISequentialStream, STGM_READ, m_data, m_BLOBDATA_LENGTH, m_BLOBDATA_STATUS)
Откуда это пошло — не ясно: из MSDN или из книжек Круглински про MFC 4.0. Так или иначе, это все годами работало без сбоев... Пока не настала эра 64-битных приложений. Неприятность в том, что при сборке такого кода ошибок нет, да и работает он вроде. Но не всегда корректно.

Как всегда виноваты макросы. Если посмотреть на декларацию BLOB_ENTRY_LENGTH_STATUS, то можно увидеть, что там запоминается адрес переменной m_BLOBDATA_LENGTH и позже используется как ULONG в 32-битной сборке, и как ULONGLONG в 64-битной. Т.е. несмотря на тип, который мы используем, по указанному адресу писаться будет 4 или 8 байт соответственно. В лучшем случае будет затираться m_BLOBDATA_STATUS, которая часто идет следом за m_BLOBDATA_LENGTH, а в худшем... в худшем — это UB.

Выход из ситуации следующий: нужно вместо ULONG использовать DBLENGTH и DBSTATUS.

суббота, 1 января 2011 г.

explicit constructor

Давайте рассмотрим как отличаются явные и неявные конструкторы. Многие посмотрев на код ниже подумают, что различий между инициализацией a и b не очень много:
class SomeClass {
public:
  SomeClass( int );
};

int main() {
  SomeClass a( 5 );
  SomeClass b = 5; // если конструктор помечен explicit, то тут будет ошибка

  return 0;
}
Я имею ввиду, что в случае инициализации b не будет вызываться оператор копирования, как думают многие начинающие разработчики. Обе этих строчки вызывают конструктор с той лишь разницей, что в первом случае — это явный(explicit) вызов, в во втором — неявный(implicit).

Cледующий пример поможет продемонстрировать как эта разница может проявиться в виде ошибки на практике:
#include <iostream>

class sss
{
public:
  explicit sss( int ) {
    std::cout << "int" << std::endl;
  }

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

int main()
{
  sss ddd( 7 );
  sss xxx = 7;

  return 0;
}
Известно, что все целые числа по стандарту автоматически имеют тип int, если иного явно не указано. Т.е. число 7 в примере выше имеет тип int. Поэтому, кажется на первый взгляд, что в обоих случаях будет вызван конструктор принимающий int. Однако, это не так.

Запись вида «sss ddd( 7 );» является явным вызовом конструктора, а «sss xxx = 7;» — неявным. Если бы конструктор с int был в private секции, то была бы выдана ошибка. Но ключевое слово explicit скывает конструктор так незаметно, что даже предупреждения компилятор не выдаст. Если скомпилировать и запустить пример, то окажется, что во втором случает будет вызван конструктор, принимающий double. Это может оказаться довольно неожиданным поворотом событий, если не разбираться в формах вызовов конструкторов. Часто разработчики не придают особого значения как они конструируют объект.

Если написать explicit для обоих конструкторов, то ошибки можно будет избежать — компилятор подскажет где ошибка. Поэтому explicit следует писать для всех конструкторов с одним параметром, если специально не предполагается другое поведение. Мне кажется, что это стоило делать по умолчанию в стандарте, а для других случаев ввести что-то вроде nonexplicit.