четверг, 31 мая 2007 г.

С++09 - новый стандарт, новые возможности. Часть 2.

Продолжая тему, начатую в предыдущем посте, рассмотрим следующее нововведение в стандарте C++09. Итак, встречайте:

Шаблоны с переменным количеством аргументов (Variadic templates (aka "type varargs" for templates) - [N2242], [N2087])

Вы наверное знаете, что существуют функции типа printf, использующие переменное количество аргументов. Естественно, рано или поздно настанет время, когда вам понадобится возможность сделать подобное для шаблона, у которого количество параметров тоже будет переменным. Изначально в новой версии стандарта предполагалось использовать так называемые кортежи (tuples), которые могут быть определены для какого-то фиксированного числа шаблонных аргументов:

// Черновой вариант в C++09 до введения variadic templates
// (некоторые параметры по умолчанию жестко заданы)
//
template<
class T1 = unspecified ,
class T2 = unspecified ,
... ,
class TN = unspecified
> class tuple;

template<class T1, class T2, ..., class TN>
tuple<V1, V2, ..., VN> make_tuple( const T1&, const T2& , ..., const TN& );

Данное объявление выглядит всё более и более зловещим при увеличении N. И что делать, если нам понадобится больше типов, чем содержится в объявлении? К счастью, с появлением variadic templates в C++09 все становится гораздо проще и понятнее:

//
template<class... Types> class tuple;

template<class... Types>
tuple<VTypes...> make_tuple( Types&... );

Данный вариант гораздро проще для определения и работает с любым количеством аргументов. Рассмотрим пример:

// Абсолютно корректный код в C++09
//
template<typename... Mixins>
class X : public Mixins...
{
public:
X( const Mixins&... mixins ) : Mixins(mixins)... { }
};

class A { };
class B { };
class C { };

X<A, B, C> x;

Для типа X<A, B, C> компилятор сгенерирует примерно следующий код:

class X<A, B, C> : public A, public B, public C
{
public:
X( A const& __a, B const& __b, C const& __c ) : A(__a), B(__b), C(__c) { }
};

Продолжение следует...

четверг, 24 мая 2007 г.

C++09 - новый стандарт, новые возможности

В своём блоге Герб Саттер приводит примеры нескольких новых особенностей C++, которые вошли в черновой вариант стандарта C++09. Я решил сделать небольшой обзор наиболее интересных из них.

Начнём с шаблонных алиасов (Template aliases (aka typedef templates, generalized typedefs) [N2258]) . Данная возможность позволяет зафиксировать часть параметров шаблона.

// Не является правильным в C++, но хотелось бы...
//
template<typename T>
typedef std::map<std::string, T> Registry;

// Тогда можно было бы написать следующее:
//
Registry<employee> employeeRoster;
Registry<void(*)(int)> callbacks;
Registry<classfactory*> factories;

В C++09 вводится более обобщенная поддержка алиасов:

// Разрешено в C++09
//
template<typename T>
using Registry = std::map<std::string, T>;

// Теперь это работает, как и задумывалось:
//
Registry<Employee> employeeRoster;
Registry<void (*)(int)> callbacks;
Registry<ClassFactory*> factories;

Пока для создания подобного поведения можно использовать шаблонную стуктуру-обёртку (на примере смарт-указателя, инкапсулирующего класс Loki::SmartPtr, который использует несколько стратегий в качестве параметров шаблона):

template<typename T>
struct shared_ptr
{
typedef Loki::SmartPtr
<
T, // T может быть любым, остальные параметры фиксированы
RefCounted, NoChecking, false, PointsToOneObject, SingleThreaded, SimplePointer<T>
>
type;
};

shared_ptr<int>::type p; // пример использования, “::Type” - выглядит зловеще

Одной из основных причин (но и не только) для введения в новом стандарте шаблонных алиасов является удобство в использовании. Теперь при написании подобного кода не придётся явно указывать набор причудливых параметров, которые невозможно спрятать с помощью обычного алиаса:

// Разрешено в C++09
//
template<typename T>
using shared_ptr =
Loki::SmartPtr<
T,
RefCounted, NoChecking, false, PointsToOneObject, SingleThreaded, SimplePointer<T>
>;

shared_ptr<int> p; // вариант в C++09

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

// Верно в C++98 and C++09
//
typedef int Size;
typedef void (*handler_t)(int);

// Верно в C++09
//
using Size = int;
using handler_t = void (*)(int);

Для таких типов, как указатели на функции, это особенность новой формы "using" весьма важна, поскольку она является более простой в использовании и наглядной, чем typedef.

Продолжение следует...

вторник, 22 мая 2007 г.

Вышла новая версия Boost - 1.34.0

С небольшим опозданием, но тем не менее с удовольствием сообщаю, что новая версия Boost 1.34.0 доступна для скачивания. Для тех, кто собирается скомпилировать все компоненты библиотеки - готовьте свободное место, при сборке с использованием bjam и Microsoft Visual Studio 2005 результат занял порядка 2.5 гигабайт, столько же заняли объектники и прочий мусор.

Удобочитаемый код

Отрывок из книги Джошуа Кериевски "Рефакторинг с использованием шаблонов". Не знаю почему, но очень понравилось.

"Время от времени я натыкаюсь на столь поразительные образцы кода, что не могу не рассказывать о них в течение месяцев, а то и лет. Так произошло и тогда, когда я изучал образец кода, написанного Вардом Каннингемом (Ward Cunningham)...
...Изучаемый мною код представлял часть вымышленной системы платежных ведомостей, предназначенной для использования на семинаре по рефакторингу. Как одному из преподавателей семинара, мне было необходимо предварительно разобраться в коде. Я начал с просмотра тестирующего кода. Первый же разобранный мною тестовый метод вычислял выплаченную сумму для данной даты. Что сразу бросилось мне в глаза, так это дата. Код гласил:

november(20, 2005)

Этот код вызывал следующий метод:

public void Date november(int day, int year)

Я был удивлен и восхищен. Даже в тестовом коде Вард беспокоился о написании легко воспринимаемого метода. Если бы он не испытывал желания написать простой и легкий для понимания код, он бы написал так:

java.util.Calendar c = java.util.Calendar.getInstance();
c.set(2005, java.util.Calendar.NOVEMBER, 20);
c.getTime();

И хотя этот код создает ту же дату, что и предыдущий, в нем отсутствует многое из того, что присуще методу Варда november(), который:

  • читается как фраза на обычном языке;
  • отделяет важный код от побочного.

А вот совершенно другая история. Она касается метода с названием w44(). Функцию w44() я обнаружил в куче мусора на языке Turbo Pascal, которую пердставлял собой калькулятор ссуд большого банка на Уолл-стрит. Свои первые три недели карьеры профессионального программиста я потратил на исследование этой трясины. В конце концов я выяснил, что 44 - это ASCII-код запятой, а "w" означает "with". Таким образом, для программиста название w44() было способом указать, что данная подпрограмма возвращает число в текстовом формате с запятыми. Это надо же было додуматься! Программист или решил обеспечить секретность работы, или просто не выработал способ именования функций.

Мартин Фаулер (Martin Fowler) выразил это лучше:

"Каждый дурак может написать код, понятный компьютеру. Хорошие программисты пишут код, понятный людям"."

понедельник, 21 мая 2007 г.

"Умный" указатель, или всё своё ношу с собой

Небольшая иллюстрация использования "умных" указателей (я использую boost::shared_ptr, вы можете выбрать любой другой, только не забывайте про то, как реализовано копирование в std::auto_ptr, и не пытайтесь использовать его в контейнерах STL).

template<typename T>
struct CPointerHolder
{
typedef boost::shared_ptr<T> CPtr;
virtual ~CPointerHolder() {}
};

Например, нам нужен список указателей на int:

typedef CPointerHolder<int> CInt;
typedef std::list<CInt::CPtr> CIntList;

Или сделать указатель частью произвольного типа и объявить массив таких указателей:

struct CType : public CPointerHolder<CType>
{
int i;
};

typedef std::vector<CType::CPtr> CTypeArray;

И так далее для любых типов.

Вычисление количества элементов статического массива на этапе компиляции

Я уверен, что 99% из тех, кто программирует на С++, сталкивался с проблемой, когда имеется статический массив значений, количество элементов которого нужно передать вызываемой функции, присвоить другой переменной или использовать его ещё каким-либо способом. Самое первое, что приходит на ум - это разделить длину массива в байтах на размер типа элемента. Например:

static const int ARRAY[] = { 1, 2, 3, 4, 5, 6 };
static const size_t ELEMENTS_COUNT = sizeof(ARRAY) / sizeof(int);
DoSomethingUseful(ELEMENTS_COUNT);


Конечно, это работает, и работает правильно до тех пор, пока вы не решите изменить тип элементов массива. Хорошим тоном считается определение констант и переменных непосредственно перед их использованием, но представьте себе следующую ситуацию:

static const int ARRAY[] = { 1, 2, 3, 4, 5, 6 };
// 20 тысяч строк полезного кода
static const size_t ELEMENTS_COUNT = sizeof(ARRAY) / sizeof(int);
DoSomethingUseful(ELEMENTS_COUNT);

В этот момент ваш менеджер говорит, что код надо срочно адаптировать под платформу х64, используя все её преимущества, причём это должно быть сделано "вчера", и вы бросаетесь изменять тип int на __int64. В условиях острой нехватки времени я вам гарантирую, что хотя бы в одном участке кода вы забудете исправить sizeof(int) на sizeof(__int64). К чему это приведёт - рассказывать не стоит, особенно если ошибка окажется в редко используемой части кода. Как правило, крах вашей программы на глазах у заказчика заставит вас серьёзно и надолго задуматься.
Но не стоит отчаиваться, поскольку проблема решается просто - при вычислении размера массива вместо размера типа нужно использовать размер одного из элементов этого массива (обычно первого, поскольку в С++ разрешены массивы только ненулевой длины). Таким образом получаем следующее:

static const int ARRAY[] = { 1, 2, 3, 4, 5, 6 };
static const size_t ELEMENTS_COUNT = sizeof(ARRAY) / sizeof(ARRAY[0]);


Не знаю, как вам, но мне не нравится определение ELEMENTS_COUNT, поскольку оно засоряет пространство имён в своей области видимости, да и компилятор всё равно оптимизирует вычисление количества элементов, и в случае с вызовом DoSomethingUseful(ELEMENTS_COUNT) в скомпилированном с включенной оптимизацией бинарном коде это будет выглядеть примерно так:

push 6
call DoSomethingUseful

К тому же, писать постоянно такую конструкцию, как sizeof(ARRAY) / sizeof(ARRAY[0]), довольно утомительно, а если не использовать переменную ELEMENTS_COUNT, то вызов функции DoSomethingUseful будет довольно зловещим:

DoSomethingUseful(sizeof(ARRAY) / sizeof(ARRAY[0]));

Есть ли более элегантный способ вычисления количества элементов? Да, он существует, поскольку существует возможность передачи массива по ссылке в качестве параметра функции:

void F(int (&ARRAY)[6]);

Развивая мысль дальше, сделаем эту функцию шаблонной и в качестве параметров шаблона зададим тип элементов массива и количество его элементов. Ссылка на массив остаётся параметром функции, а возвращаемым значением является нужное нам число элементов:

template<typename T, size_t N>
inline size_t LengthOf(T (&x)[N])
{
return N;
}

Поскольку функция определена как встраиваемая (хотя компилятор в принципе может и проигнорировать это указание, но в этом случае всё получалось как и задумано), значение N будет вычислено на этапе компиляции. В результате получаем более краткий и читаемый код для вызова DoSomethingUseful:

DoSomethingUseful(LengthOf(ARRAY));

В скомпилированном коде по-прежнему без изменений:

push 6
call DoSomethingUseful

Таким образом, мы получили требуемый результат. Если кто-то знает более изящное решение - не стесняйтесь, напишите мне.