Albert Makhmutov - Идиомы и стили С++
- Название:Идиомы и стили С++
- Автор:
- Жанр:
- Издательство:неизвестно
- Год:неизвестен
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Albert Makhmutov - Идиомы и стили С++ краткое содержание
Идиомы и стили С++ - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Вернемся опять к статической функции. Статическую функцию класса можно вызвать двумя способами - указав либо имя класса, либо через экземпляр класса.
CClass* cc1 = CClass::factory(void);
CClass* cc2 = cc1-›factory(void); // Вызов производящей функции
// Не знаю, откуда мы его берем, но это стековый экземпляр
CClass cc3;
CClass* cc4 = cc3.factory(void); // Еще один вызов производящей функции
Тут-то и делается самый прикол. Мы делаем виртуальный конструктор: виртуальную производящую функцию:
CClass {
public:
// Теперь виртуальная, а не статическая.
virtual CClass* factory (void);
// Конструктор делаем для простоты открытым,
// поскольку все-таки нам нужен
// базовый способ получения экземпляров
CClass () {}
};
CClass* CClass::factory(void) { return new CClass(); }
// Где-то в коде
CClass* cc = new CClass();
// Виртуальное конструирование!!!
CClass* cc1 = cc-›factory(void);
Думаю, что на этом следует закончить этот шаг. К конструированию объектов мы будем возвращаться еще не раз… но не сегодня.
Примером производящих функций являются макросы DECLARE_SERIAL, IMPLEMENT_SERIAL, DECLARE_DYNCREATE, IMPLEMENT_DYNCREATEв MFC. Они конечно сложнее и делают много чего еще, но в конечном итоге это замазанные макросом производящие функции.
Шаг 12 - Двухэтапная инициализация.
Когда мы создаем нестековый экземпляр, то пишем такой код:
CClass* cc = new CClass();
Попробуем поразбираться. new- это глобальный оператор с определением:
void* operator new (size_t bytes);
Он получает от компилятора количество байт, необходимое для хранения объекта, а потом передает управление конструктору, чтобы тот правильно произвел нужные инициализации. То есть, в одном выражении исполняется два совершенно разных логических действия:
1. Выделение памяти;
2. Конструирование.
Оба действия могут кончиться неудачей. Либо память не выделится, тогда негде будет инициализировать объект, либо память выделится, но инициализация будет неудачной. С 1998 года стандарт C++предусматривает, что если инициализация прошла неудачно, то выделенная память должна автоматически освободиться - то есть вызваться оператор delete, но без передачи управления деструктору. До того это оставалось на совести разработчика компилятора, и довольно часто выделенная память могла застрять, и больше не вернуться в систему. Кроме того, конструктор ничего не возвращает. Только что проверить на NULL. Ну еще конечно исключения, да… но все так сложно, елы… Короче, не след бы нам смешивать разные вещи, даже если это совсем не суп и не мухи, а совсем выделение памяти и инициализация.
В какой-то степени по этой причине, но так же и по некоторым другим соображениям, в C++применяется прием двухэтапной инициализации. На мой взгляд это не есть идиома, а довольно простой прием, но есть весьма важная причина, почему я должен о нем рассказать: его не используют.
Особенно часто этим грешат начинающие Delphi-щики, и VB-шники: слишком велик соблазн щелкнуть по методу формы OnCreate, OnShow( Form_Create, Form_Show), и прописывать инициализации там, или, что еще ужаснее, залезть из одной формы в другую и там изменять значения переменных. Не делайте этого! Граждане дельфинщики! Форма - такой же класс, как и все остальные. Не лишайте ее законного конструктора, дайте ей заслуженную инициализацию! Не чмарите свой инструмент, и он воздаст Вам сторицей!
Ну ладно, чувствую я тут возбужденный такой забуду код нарисовать. Сначала пояснения. Пусть Ваш класс управляет Темными Силами, и они лежат в закрытой части объявления. Не ввязывайтесь в борьбу с ними в конструкторе, там они слишком злобно гнетут. Конструктор пусть инициализирует только примитивные типы, он к тому же их оптимизирует ловчее, но фактически его дело - только выделить память и получить указатель на объект. Инициализация Темных Сил - дело специальной функции, которая грамотно выполняет все нужные действия.
class CClass {
private:
// Чудовищно сложные структуры, ресурсы,
// мьютексы-шмутексы, все сплошь критическое,
// пачками выбрасывающие исключения.
public:
CClass (); // Конструктор, которому на Ваши проблемы плевать.
// Вот тут мы и замучаем свои ресурсы.
int InitInstance (‹список аргументов›) throw (‹список исключений›);
};
// Где-то в коде:
CClass* cc = new CClass;
if (cc != NULL) {
try {
int ret_code = cc-›InitInstance();
// Тут еще и код возврата можно обработать, если не лень,
// но только если инициализация прошла успешно.
// если выскочило исключение, сюда мы не попадем.
} catch (…) {
// да еще и исключения обработать.
}
};
Все…
Шаг 13 - Перегрузка operator+.
Оператор operator-›мы уже перегружали. Результаты получились просто феерические. Давайте замучаем еще кого-нибудь и посмотрим, что получится? Давайте. Первейшим кандидатом на переопределение является оператор operator+,потому что в жизни (помимо С++) он выражает замечательное действие - добавление объекта к другому объекту. Конечно, Вы можете придать ему и другой смысл, но это будет как раз тот случай, где "если хочешь быть здоров - ешь один, и в темноте"…
Проиллюстрирую сказанное следующим примером: Вы пишете интерфейс на Паскале, и для добавления подменю или пункта меню используете функции AddSubMenu()и AddMenuItem(). Тогда для создания меню Вы прописываете замечательно изящное выражение с толстым-толстым слоем скобок в конце:
Menu.AddSubMenu(Submenu1,
AddMenuItem(MenuItem1,
AddMenuItem(MenuItem2,
AddMenuItem(MenuItem3,
AddSubMenu (SubMenu2, nil)
))));
А вот что получается на С++:
CMenu Menu = Menu() + SubMenu1() + MenuItem1() + MenuItem2() + MenuItem3() + SubMenu2();
Прошу извинить за отсутствие подробностей - на память не помню; но и так очевидно, что выражение в C++выглядит просто красивее. Это одно из главных достоинств С++- язык позволяет выражать наши намерения предельно ясно.
Есть еще один смысл, который можно придать оператору operator+, если "от перестановки мест слагаемых сумма не изменяется". Примером, кроме сложения и умножения чисел, может быть соударение объектов: столкнулся ли Титаник с айсбергом, айсберг ли столкнулся с Титаником, результат один - вызов деструктора для Титаника. (Я не утверждаю, что это НУЖНО делать так, это только можно!)
Немного ниже мы используем это для уменьшения количества диспетчерских функций при двойной диспетчеризации из Шага 4, а сейчас быстренько напишем код с переопределением operator+для любимого стека всех времен и народов. Понятно, на месте стека может оказаться каждый, те же меню, опять же.
Читать дальшеИнтервал:
Закладка: