Показаны сообщения с ярлыком Программинг. Показать все сообщения
Показаны сообщения с ярлыком Программинг. Показать все сообщения

вторник, 25 августа 2009 г.

Создание класса сериализующегося с использованием различных потоков



При программировании на С++ иногда приходится сталкиваться с необходимостью реализовать в классе сериализацию в c использованием различных потоков, а не только с иcпользованием iostream. Для начала приведу пример сериализации ставший классическим:
#include <iostream>
#include <string>

class User
{
public:
User() : m_id(-1), m_balance(0)
{
}
User(long id,
std::string& name,
std::string& surname,
double balance) :
m_id(id),
m_name(name),
m_surname(surname),
m_balance(balance)
{
}

void setName(std::string& name) { m_name = name; }
std::string name() const { return m_name; }
void setSurname(std::string& surname) { m_surname = surname; }
std::string surname() const { return m_surname; }
void setBalance(double balance) { m_balance = balance; }
long id() const { return m_id; }

friend std::ostream& operator << (std::ostream& out, const User& user)
{
return out << m_id
<< m_name
<< m_surname
<< m_balance;
}
friend std::istream& operator >> (std::istream& in, const User& user)
{
in >> m_id
>> m_name
>> m_surname
>> m_balance;
return in;
}
private:
long m_id;
std::string m_name;
std::string m_surname;
double m_balance;
};



В приведенном коде используется так называемая перегрузка дружественных операторов, в данном случае операторов << и >>. Записать такой класс в поток (также как и считать обратно из потока) не сложно:



#include "User.h"
#include <iostream>
#include <fstream>

int main()
{
User u_(121001, "User1", "UserSurname", 123);
std::ofstream fout;
if (fout.open("test.dat", std::ios::out|std::ios::binary))
fout << u_;
fout.close();


std::ifstream fin;
User _u;
if (fout.open("test.dat", std::ios::out|std::ios::binary))
fin << _u;
fin.close();

cout << _u << endl;

}



Во многих случаях этого вполне достаточно, однако у такого подхода есть несколько минусов:
- Если есть иерархия классов, то дружественные операторы придется перегружать для каждого класса иерархии
- Сложно использовать другие средства сериализации (например, может потребоваться сериализация в xml), или точнее для каждого класса иерархии уже придется дописывать еще по два перегруженных оператора (при этом 100% где-то что-то забудешь).

Существует элегантный способ сделать сериализацию еще более гибкой, так что предлагаю на суд читателей свое изобретение:


//Файл iserializable.hpp
#ifndef ISERIALIZABLE_HPP
#define ISERIALIZABLE_HPP

#ifndef interface
#define interface struct
#endif

template <typename _Stream>
interface ISerializable
{
virtual void pack(_Stream& out) const = 0;
virtual void unpack(_Stream& in) = 0;

};

#endif


//Файл User.h

#ifndef USER_H
#define USER_H


#include <iostream>

// Следующие два заголовка
// из библиотеки Qt
#include <QTextStream>
#include <QDataStream>


class User :
public ISerializable<std::iostream>,
public ISerializable<QTextStream>,
public ISerializable<QDataStream>
{
public:
User() : m_id(-1), m_balance(0){}
User(long id,
std::string& name,
std::string& surname,
double balance) :
m_id(id),
m_name(name),
m_surname(surname),
m_balance(balance)

{
}

virtual void setName(std::string& name) { m_name = name; }
virtual std::string name() const { return m_name; }
virtual void setSurname(std::string& surname) { m_surname = surname; }
virtual std::string surname() const { return m_surname; }
virtual void setBalance(double balance) { m_balance = balance; }

long id() const { return m_id; }

virtual void pack(std::iostream& out) const
{
out << m_id << m_name << m_surname << m_balance;
}

virtual void unpack(std::iostream& in)
{
in >> m_id >> m_name >> m_surname >> m_balance;
}

virtual void pack(QTextStream& out) const
{
out << m_id << m_name << m_surname << m_balance;
}
virtual void unpack(QTextStream& in)
{
in >> m_id >> m_name >> m_surname >> m_balance;
}

virtual void pack(QDataStream& out) const
{
out << m_id << m_name << m_surname << m_balance;
}
virtual void unpack(QDataStream& in)
{
in >> m_id >> m_name >> m_surname >> m_balance;
}


private:

long m_id;
std::string m_name;
std::string m_surname;
double m_balance;

};



class AdvancedUser : public User

{

public:

AdvancedUser() : User()
{
}

AdvancedUser( long id,
std::string& name,
std::string& surname,
double balance,
std::string& advanced) :
User(id, name, surname, balance),
m_advancedData(advanced)

{
}

void setAdvancedData(std::string& dat) { m_advancedData = dat; }
std::string advancedData() const { return m_advancedData; }

virtual void pack(iostream& out) const
{
User::unpack(in);
out << advancedData;
}



virtual void unpack(iostream& in)
{
User::pack(out);
in >> m_advancedData;
}



virtual void pack(QTextStream& out) const
{
User::pack(out);
out << advancedData;
}



virtual void unpack(QTextStream& in)
{
User::unpack(in);
in >> m_advancedData;
}



virtual void pack(QDataStream& out) const
{
User::pack(out);
out << advancedData;
}


virtual void unpack(QDataStream& in)
{
User::unpack(in);
in >> m_advancedData;
}


private:

std::string m_advancedData;
};


#endif




Идея предлагаемого подхода состоит в следующем: создается абстрактный шаблонный интерфейс, который должен быть инстанциирован по потоку. Далее наследуем наш класс от этого интерфейса и реализуем функции чтения и записи. Казалось бы все просто? Ан, нет... При наследовании от интерфейса мы обязаны явно указать параметры шаблона. Магия в том, что интерфейс написан один раз, и наследуемся мы вроде бы тоже от одного класса, а в реальности получается множественное наследование! В некотором смысле в приведенном примере используется статический полиморфизм, однако мы обязаны реализовать теперь не 2 функции а 6 (по 2 на каждый инстанциированный шаблон). Некоторые могут возразить что приведенный пример не скомпилируется из-за того что имена функций чтения/записи одинаковы, но это не совсем так: просто получается, что мы делаем эти функции перегруженными.
Другие читатели могут заметить, что возникнут проблемы если два раза пронаследоваться от интерфейса проинстанциированным по одному и тому же типу потока. Сразу хочу заметить: такое не скомпилируется (ошибка времени компиляции: уже здорово, гораздо лучше чем ошибка времени выполнения), и кроме того: часто ли вы по ошибке наследуетесь от одного и того же класса? )))
А вот самые внимательные читатели обнаружат, что начал я с прегрузки операторов вводо-вывода в поток, что же с ними? Тут очередной (хотя и вполне очевидный) трюк: перегрузить операторы ввода-вывода потребуется только один раз! Действительно, поскольку класс User в нашем примере полиморфный, то операторы ввода-вывода можно сделать глобальными и описать следующим образом:


std::iostream& operator << (const User& usr, std::iostream& out)
{ usr.pack(out); }

std::iostream& operator >> (const User& usr, std::iostream& in)
{ usr.unpack(in); }



QTextStream& operator << (const User& usr, QTextStream& out)
{ usr.pack(out); }

QTextStream& operator >> (const User& usr, QTextStream& in)
{ usr.unpack(in); }



QDataStream& operator << (const User& usr, QDataStream& out)
{ usr.pack(out); }

QDataStream& operator >> (const User& usr, QDataStream& in)
{ usr.unpack(in); }


вторник, 24 февраля 2009 г.

Всем доброго времени суток!

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

Проект
Текстовые файлы
Файл1.txt
Файл2.txt
Файл3.doc
Рисунки
Рис1.jpg
Рис2.bmp
Рис3.png
......
......

Логично что во время выполнения программы должен существовать один единственный проект. В большинстве случаев в такой ситуации используют шаблон Singletone. Да,это не плохое решение, но я пошел немного дальше. Известно, что при написании любой программы на Qt экземпляров класса QApplication также не может быть более одного. Но вернемся к проекту, описать эту абстракцию можно следующим образом:

class QProject // класс проекта
{
public:
Q_DISABLE_COPY(QProject) // блокируем копирование объекта

// добавить файл в проект
void appendFile(const QString& filename)
{
if (!m_filesList.contains(filename))
m_filesList.append(filename)
}
// удалить файл из проекта
void removeFile(const QString& filename)
{
int index = m_filesList.indexOf(filename));
if (index != -1)
m_filesList.remove(index);
}
void load(constQString& filename)
{
........
........
}

void save(constQString& filename)
{
........
........
}
private:
QProject(){} // блокируем конструктор
QStringList m_filesList;
};

Уже любопытно не правда ли? Экземпляр класса QProject невозможно создать никаким образом! Далее пронаследуемся от QApplication:

class QMyApplication : public QApplication
{
public:
QMyApplication(int argc, char *argv[]) :
QApplication(argc, argv)
{}
void createProject();
void closeProject();
QProject *project() const;
private:
QProject m_project; // указатель на проект
};

Пока вроде ничего необычного. А теперь собственно трюк:

class QMyApplication;

class QProject // класс проекта
{
public:
Q_DISABLE_COPY(QProject) // блокируем копирование объекта

// добавить файл в проект
void appendFile(const QString& filename)
{
if (!m_filesList.contains(filename))
m_filesList.append(filename)
}
// удалить файл из проекта
void removeFile(const QString& filename)
{
int index = m_filesList.indexOf(filename));
if (index != -1)
m_filesList.remove(index);
}

private:
QProject(){} // блокируем конструктор
QStringList m_filesList;
friend class QMyApplication;
};

class QMyApplication : public QApplication
{
public:
QMyApplication(int argc, char *argv[]) :
QApplication(argc, argv),
m_project(NULL),
refCount(0)
{
}
void createProject()
{
if (refCount < 1)
{
m_project = new QProject();
}
}
void closeProject()
{
if (refCount > 0)
{
delete m_project;
m_project = NULL;
}
}
QProject *project() const { return m_project; }
private:
int refCount; // счетчик ссылок
QProject m_project; // указатель на проект
};


Что получается: QProject не имеет возможности создать себя сам, однако экземпляр можно создать только через QMyApplication, который по умолчанию Singltone! Таким образом можно гарантировать, что во время работы программы будет создан лишь один единственный экземпляр QProject! Более того, переписав макрос qApp можно получать доступ к проекту из любой точки программы!

среда, 28 января 2009 г.

Разработка кроссплатформенных приложений с использованием библиотеки Qt

Практика моя не стоит на месте, поэтому осваивая все новые и новые высоты мастрества программирования, я решил, что для многих читателей будет полезен цикл сообщений по использованию библиотеки Qt. Тот кто уже пользуется возможно найдет нечто новое для себя, тот кто еще не пользуется надеюсь после прочтения захочет хотя бы посетить сайт компании (http://www.qtsoftware.com/)
Подробно описывать библиотеку не имеет никакого смысла, по этому поводу написано впечатляющее количество книг, и самое главное есть полная документация к коду, не говоря уже о том, что библиотека распространяется бесплатно в исходных кодах по лицензии GNU GPL.
Итак, отрывая этот цикл статей, кратко опишу все достоинства и недостатки библиотеки.
Достоинства: практически полная платформонезависимость кода. Для не поддерживаемых данной библиотекой платформ сейчас софта пишется очень мало (о многих поддерживаемых компиляторах и платформах я вообще узнал из списка поддерживаемых компиляторов и платформ на все том же сайте Qt Software).
Сама организация архитектуры классов сделана крайне грамотно, с использованием новейших техник программирования. Мощная мета-объектная система (по функционалу практически ничем не уступает C#, а если учесть что все это воистину кроссплатформенно, то и качественно превосходит C#). Как в C# есть сборщик мусора .
Замечательный модуль QtGui позволяющий тонко и гибко работать с любыми элементами управления начиная от пресловутых кнопочек и кончая таблицами баз данных workspace'ами и прочими. К слову в графическом интерфейсе всезде используется крайне удачная модель MVC (модель-вид-контроллер) значительно упрощающая коммуникацию приложения с пользователем.
Огромный функционал для написания графических движков, в том числе с использованием OpenGL графики (все аффинные преобразования любых фигур, в том числе картинок, проверка столкновений, ну практически готовый игровой 2D движок!).
Сушествует даже Qt вариант STL, QList, QVector, QHash, QMap и прочие, поддержка итераторов в стиле STL и Java (кому что больше нравится).
Поддержка многих драйверов баз данных, архиваторов (zip, tar...), форматов картинок (jpg, png, gif, svg, ... и многие многие другие).
В общем этот список можно продолжать еще очень долго. Сразу видно, что Qt по функционалу разом бьет и C# и gdi+.
Недостатки: да, достоинств конечно много, но есть и не достатки (куда уж без них!). Существуют проблемы с развертыванием приложения, особенно с плагинами (базы данных, некоторые форматы картинок и архиваторов), не приятно что под Windows приходится вместе с приложением таскать Qt'шные библиотеки, а если еще вы и обновить вздумаете библиотеку, то приложение вообще может не запуститься.
Время от времени обнаруживаются проблемы со строками.
Библиотека классов с геометрическими фигурами написана конечно здорово - много всего полезного, но вот получить все точки фигуры из абстрактного класса стоящего на вершине иерархии - не просто, а это уже промах в архитектуре, не такой большой конечно, но в некоторых случаях приходится писать заново под себя, а это, согласитесь, неприятно.
Есть некоторые неожиданности и в графическом интерфейсе.

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

На этом я пожалуй на сегодня закончу. Заглядывая в будущее могу сказать, что в ближайшем времени появятся статьи в которых я поделюсь готовыми рецептами, ибо на мой (хоть и субъективный) взгляд любому программисту это полезно. И все таки последний совет: если совсем уже ничего не получается - прочтите документацию, она всегда поможет!

вторник, 6 ноября 2007 г.

Что такое макрос?

В двух словах макрос заменяет вызов функции ее определением через команды препроцессора:
#define Max(a,b) (((a) > (b)) ? (a) : (b))


четверг, 11 октября 2007 г.

Мелочи на C# облегчающие жизнь: Real World Examples

Некоторые полезные мелочи которым я научился программируя на C#:

1. Вызов функции из С библиотеки:
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
extern static short QueryPerformanceCounter(ref long x);


2. Перегрузка индексатора (перегрузка оператора []):
public float this[int row, int col]
{
get
{ return GetElement(row, col); }
set
{ SetElement(row, col, value); }
}


3. Символ @ в начале строки позволяет записывать строку, не дублируя слеши, как это было в С++, то есть строка:
string FileName = "c:\\temp\\test.txt";
Полностью идентична строке:
string FileName = @"c:\temp\test.txt";
Не правда ли читабельнее! ;-)

4. Символ @ перед идентификатором (именем переменной) означает, что компилятор должен разрешить использовать данное имя идентификатора, даже если оно зарезервированоое слово:
int this = 42; // не компилируется, ошибка:
// Identifier expected, 'this' is a keyword

int @this = 42; // Компилируется нормально


5. Смело юзайте #region и #endregion для выделения секций кода. Очень помогает!

воскресенье, 16 сентября 2007 г.

Создание нетривиальных приложений на C#: Real World Examples


То, что преподают в институте зачастую весьма просто и тривиально и плохо соотносится с кодом и решениями в реальных приложениях. Поэтому, я счел, что нелишним будет знакомство читателя с реальными трудностями с которыми я столкнулся, так сказать real world examples.
Начну с самого, казалось бы, простого - с массивов. Недавно работая над научной работой наткнулся на интересную вещь: необходимо было создать в C# трехмерный массив,
причем две из трех его размерностей должны быть динамическими. И избежать этого никак было нельзя.
Как оказалось решение весьма просто - если раньше в C++ для работы с динамическими массивами использовались шаблоны библиотеки STL и работа с трехмерным указателем на массив не предвещала ровным счетом ничего хорошего, то теперь на C# объявление и использование такого массива оказывается весьма прстым и тривиальным:
Немного подумав я пришел в итоге к следующему объявлению:

 .....
/// <summary>
/// declare inner List
/// </summary>
private List<double> _arr;

/// <summary>
/// declare main List
/// </summary>
public static List<List<double>>[] Arr;
.....

Инициализация получилась также нетривиальной:

/// <summary>
/// initialize List and array
/// </summary>
private void init()
{
N = (int)numericUpDown1.Value;

Arr= new List<List<double>>[N];

for (int i = 0; i < N; i++)
{
Arr[i] = new List<List<double>>();
}
}
.....

.....
// Где-то в коде:
    _arr = new List<double>();
_arr.Add(some_value);
.....
Еще дальше:
           X[counter].Add(_x);
Y[counter].Add(_y);
t[counter].Add(_t);


Использовать массивы можно как всегда, не забывая только про правильную индексацию:


for (byte i = 0; i < Arr.Length; i++)
for (byte j = 0; j < Arr[i].Count; j++)
for (byte k = 0; k < Arr[i][j].Count - 1; k++)
.......
Arr[i][j][k];