Пожалуй на форуме не хватает топика про преимущества ООП. Нада бы исправить сей недостаток. А то многовато мнений, что с++ мощный язык, но при этом используют его ровно так же, как и си и паскаль. Я опишу интересный прием программирования с использованием ООП, который пригодился мне. Если еще что нить интересное вспомню и будет интерес - продолжу тему. Итак, начнем. Наверняка вы сталкивались с ситуациями, когда необходимо освобождать ресурсы. Например файлы, память, сокеты, етц етц етц. Все просто, когда в начале функции открыли файл, в конце закрыли. Но как только линейность использования ресурсов нарушается или появляются зависимости - начинаются проблемы. Вторая проблема - не забыть закрыть файл в конце большой функции. Типичный пример первой проблемы - использование маппингов файлов. Обычно код выглядит примерно так: Code: bool ReadData (char* File, char* buffer) { bool is_readed = false; MappingAddr = 0; hFile = CreateFile (File, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (hFile == INVALID_HANDLE_VALUE) goto ERROR_CREATE_FILE; hMapping = CreateFileMapping (hFile, 0, PAGE_READWRITE, 0, 0, 0); if (hMapping == 0) goto ERROR_CREATE_MAPPING; MappingAddr = (char*)MapViewOfFile(hMapping, FILE_MAP_READ|FILE_MAP_WRITE, 0, 0, 0); if (MappingAddr == 0) goto ERROR_VIEW_MAPPING; strcpy(buffer, (char*)MappingAddr); is_readed = true; ERROR_VIEW_MAPPING: CloseHandle (hMapping); ERROR_CREATE_MAPPING: CloseHandle (hFile); ERROR_CREATE_FILE: return is_readed; } Вариант рабочий, но не очень красивый. Кутерьма из-за необходимости освободить хэндлы, которые удалось открыть, но произошла ошибка в дальнейшем доступе к хэндлам. Давайте поиграем в игру - найдите решение. Оно должно свестись к убиранию CloseHandle'ов из кода, т.е. чтобы ресурсы освобождались сами. Хинт - тема этого поста - деструкторы. Попробуйте придумать свое решение, потом глядите сюда. Если впадлу - просто читаем дальше. Ну а теперь перейдем к решению. Итак, первым делом создаем 3 класса - KFile для файла, KMapping для маппинга и KViewOfFile для проекции файла в память. Code: #include <iostream> #include <Windows.h> using namespace std; class KFile{ private: HANDLE hFile; public: KFile(){hFile = INVALID_HANDLE_VALUE;} ~KFile(){ if(hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile); hFile = INVALID_HANDLE_VALUE; } HANDLE handle() const{ return hFile; } bool isOpened(){ return !(hFile == INVALID_HANDLE_VALUE); } bool open(char* path){ hFile = CreateFile (path, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); return isOpened(); } }; class KMapping{ private: HANDLE hMapping; public: KMapping(){hMapping = NULL;} ~KMapping(){ if(hMapping != NULL) CloseHandle(hMapping); hMapping = NULL; } HANDLE handle() const{ return hMapping; } bool isOpened(){ return !(hMapping == NULL); } bool open(const KFile& file){ hMapping = CreateFileMapping (file.handle(), 0, PAGE_READWRITE, 0, 0, 0); return isOpened(); } }; class KViewOfFile{ private: BYTE* MappingAddr; public: KViewOfFile(){MappingAddr = NULL;} ~KViewOfFile(){ if(MappingAddr != NULL) UnmapViewOfFile(MappingAddr); MappingAddr = NULL; } BYTE* value() const{ return MappingAddr; } bool isOpened(){ return !(MappingAddr == NULL); } bool open(const KMapping& mapping){ MappingAddr = (BYTE*)MapViewOfFile(mapping.handle(), FILE_MAP_READ|FILE_MAP_WRITE, 0, 0, 0);; return isOpened(); } }; void test() { KFile file; KMapping mapping; KViewOfFile view; if(!(file.open("test.txt") & mapping.open(file) & view.open(mapping))){ cout << "error opening file\n"; return; } char text[50]; strcpy(text, (char*)view.value()); cout << "file readed\n"; cout << text << "\n"; } void main() { test(); system("pause"); } Вся работа осуществляется в функции test(). Открываем маппинг одной строкой в ифе. Если хоть один из классов не отрабатывает - выходим из функции, причем не заморачиваясь об закрытии хэндлов. Так же не заморачиваемся этим вопросом и в случае удачного чтения. А все почему? Суть приема. А потому, что мы объявили объекты классов локально. Это значит, что при выходе из функции они будут уничтожены. Уничтожение объекта приводит к вызову его деструктора. Всю логику контроля хэндлов мы как раз там и реализовали. В итоге все работает и без нашего вмешательства. В данном примере есть только одно опасное место(не считая конструктора копирования, который лучше бы сделать приватным. Или решив проблему как то иначе. В чем суть проблемы - попробуйте понять сами, зная лишь что конструктор копирования просто копирует значение полей в новый объект) - в данном случае критичен порядок определения переменных наших классов, т.к. уничтожаются они в обратном порядке относительно создания. А для нас это вполне важно. Нужно сначала делать унмап, потом закрывать маппинг и только потом закрывать хэндл файла. Это на самом деле очень плохо, т.к. заставляет нас помнить об этой фиче. Но для удобства мы можем поступить просто - сделать еще один класс, который сокроет(инкапсулирует) все шаги открытия маппинга до одной операции - open. Например так: Code: class KFileMapping{ private: KFile file; KMapping mapping; KViewOfFile view; public: KFileMapping(){ } ~KFileMapping(){ close(); } void close(){ view.close(); mapping.close(); file.close(); } bool open(char* path){ if(!(file.open(path) & mapping.open(file) & view.open(mapping))){ close(); return false; } return true; } BYTE* value() const{ return view.value(); } }; Ну и использовать так: Code: void test2() { KFileMapping mapping; if(!mapping.open("test.txt")){ cout << "error opening file\n"; return; } char text[50]; strcpy(text, (char*)mapping.value()); cout << "file readed\n"; cout << text << "\n"; } Итого имеем: 1) Защита от ошибок 2) Упрощение кодирования 3) Повторное использование кода. Постепенно со временем вы соберете подборку своих классов, реализующих наиболее нужный вам функционал, с нужными вам интерфейсами(например не часто приходится указывать, как открывать файл - только для чтения, только для записи, итп. Вы делаете так, чтобы этим было максимально удобно пользоваться. Когда понадобится указывать доступ - просто добавите новый метод, минимально трогая старую реализацию). Ну вот собсно и все. PS: то, что мы с вами сделали, это по сути паттерн RAII (велкам ту вики), только немного модифицированный(в идеале метод open нада заменить вызовом конструктора, но по мне итак вполне юзабельно)
Хороший обзоз, пример паттерна + многократное использование кода ) Вот только жаль что все чаще и чаще встречаешь с++ код подобный первому примеру, в натив си, еще такой подход приемлен, но не в плюсах )
тут важно понять, что нужно всегда учитывать требования к задаче, вероятность доработки программы и прочее. Один разок записать/прочитать один файл - можно и процедурным программированием обойтись, также как не стоит перегружать оператор сложения для собственных классов, чтобы сложить два числа. А вообще в продвинутых(читать высокоуровневых) платформах реализованы Garbage Collector'ы, которые автоматически подчищают все объекты и прочее.
это типичная ошибка. GC адекватно подходит для очистки памяти. Но вот для других ресурсов(файлы, сокеты, все, что влияет на систему целиком, а не только на среду выполнения) его применимость под большим вопросом. Советую прочитать о специфике деструкторов в Java и C#. Выяснится не очень приятная их особенность. В дотнете это решено IDisposable, в Java хз как. так вот это я к тому, что GC без использования мозга и адекватного программирования не решает проблем, а лишь добавляет.
я думаю на закрытие потоков и прочих стандартных вещей они его заточили, в остальном - да, никаких тебе "правильных" закрытий
2Ra$cal можно попробовать не писать отдельный класс для каждой сущности а заюзать типа этого Code: template<class fn_t,class res_t> struct jk{ fn_t pfn; res_t some; jk(fn_t pfn,res_t some):pfn(pfn),some(some){} void detach(){some=0;} ~jk(){some?pfn(some):0;} }; #include <boost/typeof/typeof.hpp> #define do_exit(pfn,some)\ jk<typeof(&pfn),typeof(some)> _##some(pfn,some) получится что то типа Code: bool ReadData (char* File, char* buffer) { hFile = CreateFile (File, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (hFile == INVALID_HANDLE_VALUE) return false; do_exit(CloseHandle,hFile); hMapping = CreateFileMapping (hFile, 0, PAGE_READWRITE, 0, 0, 0); if (hMapping == 0) return false; do_exit(CloseHandle,hMapping); MappingAddr = (char*)MapViewOfFile(hMapping, FILE_MAP_READ|FILE_MAP_WRITE, 0, 0, 0); if (MappingAddr == 0) return false; strcpy(buffer, (char*)MappingAddr); UnmapViewOfFile(MappingAddr); return true; }
Вот за это я и не учил С++, куча лишней писанины. Мне интересно во что превращается в итоге ассемблерный код в кучу вызовов call ret ов? + насколько я скудно знаю дополнительная память на всевозможные указатели иерархии классов. Code: bool isOpened(){ return !(hFile == INVALID_HANDLE_VALUE); } не могу это понять... вернуть НЕ инвалид хендл чо? Ну а так прикольно, сам сейчас пытаюсь мудрить с классами переносимый между проектами код - задолбало копипастить, но реально выходит кучу лишней писанины и движений, особенно в WinAPI коде кое где нужно указывать ссылки на CALLBACK функции, а ведь напрямую метод класса не укажешь и начинаются пляски с бубнами
То же самое, что и return hFile != INVALID_HANDLE_VALUE; Мелкие функции принято инлайнить, а еще последние компиляторы очень хорошо оптимизируют код. Укажешь, но только статический.