Инициализация с помощью шаблонов
Введение
В практике программирования достаточно часто возникает задача, когда надо создать
структуру и обнулить ее с помощью функции memset. Многие структуры Microsoft
использует член структур cbSize в который надо записывать размер структуры.
Другая типовая задача, когда класс имеет несколько конструкторов, в каждом
из которых надо делать одинаковую инициализацию членов.
Все эти задачи решаются с помощью нескольких строчек кода. Только вот писать
одни и те же строчки не очень хорошо. В мире объектно-ориентированного программирования
просто необходимо повторно использовать код. Это не только хороший стиль, но
улучшение читаемости программ.
В этой статье предлагается одно из возможных решений каждой из этих задач с
помощью шаблонов.
Все примеры проверялись с помощью MS Visual Studio 2003 .NET.
Инициализация простых типов
Очень часто приходится писать классы, которые имеют несколько конструкторов.
Для каждого из них приходится писать список инициализации всех членов. А что
делать, когда большинство переменных класса должны иметь одни и те же значения,
не зависимо от конструктора, который используется? Решение в лоб – использовать
Copy/Past для списка инициализации. Это плохой способ. Во-первых, использование
такого стиля написания программ – плохой тон. Надо всегда стараться использовать
уже существующий код, вместо копирования. Этим мы и займемся. Во-вторых, при
добавлении нового члена можно просто забыть прописать инициализацию во все конструкторы.
template <typename T, T tVal>
struct InitVar
{
InitVar() : m_var(tVal) {}
InitVar(const InitVar& rsrc) {m_var = rsrc.m_t;}
operator T&() {return m_var;}
operator T&() const {return m_var;}
T* operator&() {return &m_var;}
const T* operator&() const {return &m_var;}
T& operator=(const T& t) {return (m_var = t);}
private:
T m_var;
};
Для того чтобы объявить переменную с типом int, надо написать:
InitVar<int, 10> iTest; //эквивалентно int iTest = 10;
Не имеет смысла объявлять так обычные переменные. Зато, если мы объявляем с
помощью этого шаблона член класса – мы получаем выгоду.
class KWindow : public CWnd
{
...
InitVar<int, 10> m_iTest; //эквивалентно int iTest = 10;
...
};
Вызов конструктора m_iTest и инициализация будет сделана перед выполнением
тела конструктора KWindow. Это нам и требовалось! Теперь мы можем делать несколько
конструкторов, и не заботится о том, чтобы выполнять одинаковую инициализацию
в каждом из них.
Инициализация структур
Рассмотрим задачу первоначального обнуления структуры и как частный случай
– запись в cbSize ее размера.
template <typename T>
struct SetZero : public T
{
SetZero()
{
ZeroMemory(this, sizeof(T));
}
};
template <typename T>
struct SetSize : public T
{
SetSize()
{
this->cbSize = sizeof(T);
}
};
template <typename T>
struct SetSizeClear : public T
{
SetSizeClear()
{
ZeroMemory(this, sizeof(T));
this->cbSize = sizeof(T);
}
};
Эти три шаблона используются для обнуления, установки размера структуры и выполнения
этих двух операций одновременно. Используются они следующим образом:
SetSizeClear<NOTIFYICONDATA> notify_icon;
notify_icon.hWnd = ...
notify_icon.hIcon = ....
...
Shell_NotifyIcon(NIM_ADD, ¬ify_icon);
Первая строчка заполняет структуру нулями и устанавливает член NOTIFYICONDATA::cbSize.
Остальные шаблоны используются аналогично.
Этот же механизм можно использовать для инициализации глобальных переменных
или членов класса. Замечательно то, что инициализацию по умолчанию не надо будет
явно прописывать в списке инициализации конструктора.
class KWindow : public CWnd
{
...
SetSizeClear<NOTIFYICONDATA> notify_icon;
...
};
Инициализация будет выполнена до выполнения тела конструктора.
Заключение
Используя приведенные техники можно эффективно делать инициализацию как простых,
так и сложных типов. Развивая эту концепцию, можно даже выполнять инициализацию
сложных типов по заданному образцу.
Обратите внимание, на случай, когда вы выполняете инициализацию
POD классов/структур. Формально, после применения шаблона – результирующий тип
не является POD. Это надо учитывать при написании надежного кода.
В некоторых случаях с результирующим типом можно обращаться как
с POD объектом. Имейте ввиду что возможность таких действий зависит от конкретной
реализации вашего компилятора. И вы должны очень хорошо понимать что и почему
вы делаете.
Инициализация может выполняться не только шаблонами. Один из распространенных
вариантов – использование инициализационной функции. Ее тоже можно сделать в
виде шаблона, чтобы она могла работать с разными типами. В итоге – опять использование
шаблонов.
Шаблоны из данной статьи можно усовершенствовать. Например, можно сделать универсальный
шаблон для структур, который будет автоматически определять наличие cbSize.
Этот же подход можно распространить и на другие признаки.
Коментарии к статьеНет комментариев
|