Antichat снова доступен.
Форум Antichat (Античат) возвращается и снова открыт для пользователей.
Здесь обсуждаются безопасность, программирование, технологии и многое другое.
Сообщество снова собирается вместе.
Новый адрес: forum.antichat.xyz
C++ и ООП фича для чуть более продвинутых новичков |

17.10.2009, 23:16
|
|
Постоянный
Регистрация: 16.08.2006
Сообщений: 640
Провел на форуме: 1354067
Репутация:
599
|
|
C++ и ООП фича для чуть более продвинутых новичков
Пожалуй на форуме не хватает топика про преимущества ООП. Нада бы исправить сей недостаток. А то многовато мнений, что с++ мощный язык, но при этом используют его ровно так же, как и си и паскаль. Я опишу интересный прием программирования с использованием ООП, который пригодился мне. Если еще что нить интересное вспомню и будет интерес - продолжу тему.
Итак, начнем. Наверняка вы сталкивались с ситуациями, когда необходимо освобождать ресурсы. Например файлы, память, сокеты, етц етц етц. Все просто, когда в начале функции открыли файл, в конце закрыли. Но как только линейность использования ресурсов нарушается или появляются зависимости - начинаются проблемы. Вторая проблема - не забыть закрыть файл в конце большой функции. Типичный пример первой проблемы - использование маппингов файлов. Обычно код выглядит примерно так:
Код:
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 для проекции файла в память.
Код:
#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. Например так:
Код:
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();
}
};
Ну и использовать так:
Код:
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 нада заменить вызовом конструктора, но по мне итак вполне юзабельно)
|
|
|
|
|
Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
|
|
|
|