30 апреля 2006

x in "C++0x"

В Dr.Djobb's Journal шутят по поводу даты выхода следующего стандарта C++:
The next revision of the C++ Standard, tentatively titled C++0x (the 'x' is a placeholder, intended to suggest that the revision will be finished before 2010, although some people like to remind us that "0x" introduces a hexadecimal constant, so C++0x really has to be finished by 2015), ...

28 апреля 2006

#define is evil

Как часто при написании Windows приложений приходится употреблять следующую строку:
#include <windows.h>

А теперь попробуем следующий код:
// Loader.h
class Loader
{
public:
void LoadString();
}

// Loader.cpp
#include "Loader.h"
#include

void Loader::LoadString()
{
// code here
}

Попробуем откомпилировать:
error C2039: 'LoadStringA' : is not a member of 'Loader'

Вот вам и windows.h со своими #define...
Подобные вещи мешают использовать даже стандартную библиотеку - макросы min и max не дают воспользоваться шаблонами std::min и std::max.

Лучшее лекарство от подобного безобразия:
// SafeWindows.h
#pragma once

#include <windows.h>

#ifdef min
#undef min
#endif

#ifdef max
#undef max
#endif

#ifdef MessageBox
#undef MessageBox
#endif
...

// Any.cpp
#include "SafeWindows.h"

int main()
{
return ::MessageBoxW(NULL, L"#define is evil", L"Note", MB_OK);
}

20 апреля 2006

Передача динамически созданных объектов в DLL

Замечательное свойство shared_ptr заключается в том, что он хранит в себе ссылку на deleter - функцию, которую shared_ptr вызывает для удаления объекта.

То есть если мы создадим shared_ptr в EXE и передадим его в DLL, которая потом будет удалять объект из динамической памяти, то она вызовет operator delete (или другой заданный deleter), который находится в EXE! Это спасет нас от головной боли с несколькими копиями CRT (одна в EXE, другая в DLL), которая была бы если бы мы передали простой указатель, а не shared_ptr.

Правда возникает вопрос: не возникнет ли у shared_ptr проблем с подсчетом ссылок при пересечении границы EXE-DLL?

Clever shared_ptr

Столкнулся с разработкой классов, подобных следующему:
class SomeClass
{
public:
SomeClass(T* Pointer, ...)
: _Pointer(Pointer) { }

private:
T* _Pointer;
}

Суть такова: передаем конструктору класса указатель на некоторый объект, он запоминает его и использует в своих целях.

Если мы динамически создали такой объект специально для передачи его в такой конструктор, то неплохо бы, чтобы деструктор этого класса автоматически удалил его из динамической памяти:
class SomeClass
{
public:
SomeClass(T* Pointer, ...)
: _Pointer(Pointer) { }

private:
auto_ptr<T> _Pointer;
}

SomeClass SomeInstance(new T(), ...);

Однако такой вариант предполагает, что мы обязательно будем создавать объекты типа T динамически, и экземпляр класса SomeClass будет владеть ими. Что не позволит:
1. передавать ссылки на объекты, которыми владеет кто-либо другой:
T Array[3];
SomeClass SomeInstance(&Array[2], ...);

2. использовать конструктор копирования и оператор присваивания, генерируемый компилятором по умолчанию (т.к. при копировании auto_ptr "донор" теряет ссылку).

Второй пункт можно обойти заменой auto_ptr на shared_ptr. Тем же самым методом можно обойти и первый пункт, если добавить немного кода:
struct NullDeleter
{
void operator()(const void *p) { /* ничего не делаем */ }
};

class SomeClass
{
public:
SomeClass(shared_ptr<T> Pointer, ...)
: _Pointer(Pointer) { }

private:
shared_ptr<T> _Pointer;
}

// передаем владение
SomeClass SomeInstance(new T(), ...);

// владение не передаем
T Array[3];
SomeClass SomeInstance(shared_ptr<T>(&Array[2], NullDeleter(), ...);

Плюс такого решения еще в том, что можно помимо двух крайностей "владеет"/"не владеет", наш класс получает третью возможность - "совладеет":
vector<shared_ptr<T> > v;
v.insert(new T());
...
SomeClass SomeInstance(v[0], ...);
...
v.clear();

Минус этого решение в том, что shared_ptr имеет много оверхеда (как по использованию памяти, так по процессорному времени) по сравнению с написанием специализированного класса, поддерживающего только два состояния: "владеет"/"не владеет".

17 апреля 2006

Текстовые файлы в 21 веке

Стандартная библиотека С++ имеет хороший инструментарий для работы с текстовыми файлами. Однако, этот инструментарий немного устарел.

Вот пример кода, написанного с использованием стандартной библиотеки:
void process_file(const char *filename)
{
std::ifstream f(filename);
std::string s;
while (!f.eof())
{
std::getline(f, s);
process_line(s);
}
f.close();
}

На первый взгляд все замечательно, но только на первый. В реальности и в имени файла, и в самом тексте могут встретиться символы, не входящие в стандартный ASCII алфавит.

А проблема в приведенном фрагменте кода в двух местах:
1. const char *filename
2. std::string s;

С первым разобраться достаточно просто:
void process_file(const wchar_t *filename)
{
std::ifstream f(_wfopen(filename, L"rb"));
...
}

Казалось бы – и второе достаточно просто решается, заменой ifstream на wifstream. Однако, не все так просто как кажется. А загвоздка вот в чем – текстовые файлы в unicode могут быть в различных кодировках: UTF-8, UTF-16, ... Да и в принципе могут встретиться «старые добрые» файлы в ANSI кодировке.

Как и следовало полагать, «велосипед» по чтению unicode-файлов уже давно изобрели. Вообще, я ожидал его увидеть в boost-е, но его там не оказалось. Я нашел его... в contributes к boost-у. Причем эта библиотека датирована 2003 годом, но так и не попала в boost!

Библиотека отлично работает, сама разпознает кодировку по BOM. Самое интересное – в ней сразу нашлась небольшая ошибка:
facet_type* facet;
switch(is.get())
{
case ...:
case ...:
}
if(!facet)
{
...
}
return facet;

Присвоение переменной facet значения идет только в случаях, если выполняется один из case-ов, в любых других случаях она остается неинициализированной, о чем мне и сообщил VC 7.1. Вылечилось просто:
facet_type* facet = NULL;

Использование библиотеки очень простое:
std::wifstream input_file(file_name, std::ios_base::binary);
// autodetect the UTF encoding of this file
boost::utf::imbue_detect_from_bom(input_file);

// some reading
std::wstring s;
input_file >> s;

Только возникают небольшая (легко устранимая) проблема с интерпретацией конца строки в файле, открытом в режиме binary (CR LF уже не становится автоматически LF).

Вызывает сомнения то, что библиотека с 2003 года не обновлялась и так и не включилась в boost, да еще эта ошибка... Поиск в конференциях показал, что используют эту библиотеку очень редко. Значит используют что-то другое? Скорее всего тяжеловесную ICU.

10 апреля 2006

AlphabeticalLess

А теперь, если кому интересно, вариант предиката less для регистронезависимого сравнения строк - для использования в std::map, std::set и т.п.:

struct AlphabeticalLess
{
template <typename CharType>
bool operator ()
(
basic_string<CharType> lhs,
basic_string<CharType> rhs
) const
{
boost::to_upper( lhs );
boost::to_upper( rhs );

return std::use_facet< std::collate< CharType > >( std::locale() )
.compare( lhs.data(), lhs.data() + lhs.size(),
rhs.data(), rhs.data() + rhs.size() ) == -1;
}
};

Приходится все переводить в upper case, а то иначе получается, что "HELLO" < "hello".

Для сортировки сойдет и версия из предыдущего поста, без to_upper.

Сортировка строк по алфавиту

Задача:
отсортировать контейнер строк по алфавиту.

Попытка 1:
std::sort( container.begin(), container.end() );


Результат получаем сортированный. Но не по алфавиту. std::sort по умолчанию использует для сортировки предикат less<T>, который в свою очередь использует operator<() для типа T, и если у нас строки типа basic_string<Ch>, то operator<() сравнивает посимвольно две строки, считая их значениями типа Ch, а не символами алфавита.

Единственное, кто может знать про понятие "алфавит", это локаль.

Попытка 2:
struct AlphabeticalCompare
{
template <typename CharType>
bool operator ()(const basic_string<CharType> & lhs, const basic_string<CharType> & rhs) const
{
return std::use_facet< std::collate< CharType > >( std::locale() )
.compare( lhs.data(), lhs.data() + lhs.size(),
rhs.data(), rhs.data() + rhs.size() ) == -1;
}
};

std::sort(container.begin(), container.end(), AlphabeticalCompare());


Вот теперь сортирует как надо.

Используемый мною тест:

#include <iostream>
#include <string>
#include <locale>
#include <vector>
#include <algorithm>

struct AlphabeticalCompare
{
template <typename CharType>
bool operator ()(const basic_string<CharType> & lhs, const basic_string<CharType> & rhs) const
{
return std::use_facet< std::collate< CharType > >( std::locale() )
.compare( lhs.data(), lhs.data() + lhs.size(),
rhs.data(), rhs.data() + rhs.size() ) == -1;
}
};

int main()
{
std::locale::global(std::locale("Ukrainian_Ukraine.1251"));

std::vector< std::wstring > container;

std::wstring s1; s1 += 0x456; // cyrillic "л"
std::wstring s2; s2 += 0x43b; // cyrillic "і"
// 0x456 is greater than 0x43b, but "і" comes before "л" in Ukrainian

container.push_back(s1);
container.push_back(s2);

std::sort(container.begin(), container.end(), AlphabeticalCompare());
std::cout << (container[0] == s1 ? "passed" : "failed") << std::endl;

return 0;
}

Update: хорошая статья на эту тему - How To Do Case-Insensitive String Comparison

08 апреля 2006

try-блоки конструкторов

Читаю Саттера. Оказывается у конструкторов можно писать try-блоки для перехвата исключений, генерируемых в конструкторах member-ов и родительских классов:

class C : private A
{
B b_;
};

C::C()
try
: A ( /*...*/ ) // optional initialization list
, b_( /*...*/ )
{
}
catch( ... )
{
// We get here if either A::A() or B::B() throws.

// If A::A() succeeds and then B::B() throws, the
// language guarantees that A::~A() will be called
// to destroy the already-created A base subobject
// before control reaches this catch block.
}

Такая методика позволяет конвертировать сгенерированные исключения в какие-нибудь другие исключения. Интересно, эту возможность вообще кто-нибудь использует?