пятница, 4 сентября 2009 г.

C#, Java или С++?... (продолжение)

Минуло несколько лет с тех пор как была написана первая статья по этой тематике. Как ни странно мое мнение ничуть не изменилось, а даже напротив укрепись. Как и раньше я считаю, что С++ является на сегодняшний день, хотя и сложным , но наиболее удобным, эффективным и продвинутым средством. Некоторые могут меня упрекнуть, что Java и C# более переносимы, на них проще писать, у них меньше проблем с наследованием и управлением памятью. Со всеми этими доводами я не согласен! 
Начнем попорядку.
С++ vs Java
Да, на Java писать программы проще, однако язык С++ по словам Б. Страуструпа с самого начала не предназначался для штампованных программистов пишущих штампованные программы. По поводу управления памятью могу привести цитату Б. Страуструпа из книги "Дизайн и эволюция С++ ":  
При проектировании С++ я сознательно решил не полагаться на автоматическую сборку мусора, опасаясь весьма значительных издержек по времени и памяти, которые удалось наблюдать в различных сборщиках мусора. <...> Общий вывод таков: сборка мусора желательна и возможна, но мы не можем позволить, чтобы от нее была зависима семантика С++ и большинства стандартных базовых библиотек.
 Данные утверждения хорошо иллюстрируются тем фактом, что в свое время шли разговоры (и даже были некоторые экспериментальные разработки) по созданию Java-процессоров напрямую поддерживающих инструкции Java. Вот насколько были плохи дела...
В Java реализованы шаблоны, но они сильно уступают по возможностям и гибкости шаблонам С++. Отсюда выдод: зачем нужны инструменты в новом языке, повторяющие уже созданные, но менее совершенные... не понятно...
Некоторые из читателей могу сказать, что Java абсолютно переносима. Нет скажу я Вам... Попробуйте перекомпилировать без виртуальной Java-машины код под процессоры SPARK, MIPS, что не получается? Правильно нужна Java-машина.. Что нет Java-машины под этот процессор? Давайте саму Java-машину перекомпилируем. А тут оп-па! программный код на С++! И кто тут переносимей?)))

С++ vs C#
С C# немного лучше в плане языка - тут тебе и управление памятью на низком уровне (Unmanaged code)  и перегрузку операторов для шаблонных можно реализовать (изрядно помявшись правда и спустившись до MASM'a, но кого это пугает!). Но вот незадача, даже под Linux ваша программа не пойдет - .NET Framework нужен! (стоит добавить что существует проект Mono для портирования программ на C# под Linux но он очень далек от совершенства). Заглянем внуть .NET Framework! И... О боже и здесь C++!

В заключении хочу сказать - многие, очень многие пророчут скорую смерть С++.
Бред! 
С++ будет еще очень долго существовать, поскольку реальной замены еще даже на горизонте не видно!

вторник, 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 г.

Блокирование наследования в С++

Представим на минуту что мы пишем инструментальную библиотеку, в которой есть менеджер сессий входа в систему, причем реализовать его требуется как синглтон и кроме того заблокировать возможность наследования от этого класса, поскольку пр наследовании и переопределении методов менеджера программа может работать неустойчиво или попросту падать.
Итак перед нами интересный вопрос на эрудицию: как заблокировать наследование от класса в С++? Напомню, что в C# и Java такая возможность существует. Как же ее реализовать в С++?
Многие скорее всего кинуться делать макросы или просто попытаются пихнуть в базовый класс какую-нибудь статическую переменную для проверки пронаследован класс или нет. Однако это не поможет. При та5ком подходе мы упускаем одну очень важную деталь: все эти проверки будут происходить во времяы выполнения и кроме того всегда возвращать неверные значения, поскольку по правилам C++ конструктор базового класса ВСЕГДА вызывается первым.
Так как же быть?
Ответ настолько же прост насколько и сложен: необходимо перенести деструктор класса в private часть класса. Читатель может возразить: при чем же здесь деструктор?

Предлагаю взглянуть на следующий фрагмент кода:


class SessionManager
{
public:
bool login(std::string& name, std::string& password);
void logout();
static bool create();
static bool destroy();
static SessionManager* instance();
private:
static SessionManager* m_self;
// блокируем констрктор
SessionManager();
// блокируем деструктор!!!
~SessionManager();
// блокируем оператор присваивания
SessionManager& operator = (const SessionManager&);
};

class SessionManagerDerived : public SessionManager
{
............
............
static bool create() {
// ошибка времени компиляции:
// невозможно создать объект класса с закрытым деструктором!
if (!m_self) return new SessionManagerDerived();
return m_self;
}
};



Вот и вся фишка: компилятор не позволит создать класс с закрытым деструктором, поскольку не знает как его удалять! Замечу что для класса SessionManager компилятор имеет эти данные хоть они и в private части.

вторник, 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 серьезная альтернатива ( а баги в следующих версиях исправят;) ).

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