Цитата(haker_fox @ Jun 22 2011, 08:05)

Не моголи бы Вы подсказать, по какой литературе обучались? Или это опыт?
В основном по Страуструпу. Если есть реальный интерес к С++, то обязательно нужно прочитать его книжку "Дизайн и эволюция С++", она хоть и не техническая по языку, но зато из её прочтения куда лучше становятся понятны концепции языка, причинно-следственные связи, почему сделано так или иначе, а не иным способом. Очень полезное чтиво для понимания философии С++. Книжка, кстати, небольшая и читается очень легко.
Ну, и кореша есть, которые в плюсах "батонят" (как один из них выражается), особенно один, по сравнению с которым я просто мальчик в коротких штанишках. В общем, есть на кого равняться.

Ну, и опыт тоже, без опыта реальной работы, конечно, ничего не бывает, это как у всех.
На самом деле уровень проблем, который мы тут подняли, это в среде профессиональных программистов просто "асфальтированное шоссе", ко которому ехать легко и просто, это обычные штатные вещи, можно сказать тривиальные для них. Просто в embedded области в силу того, что С++ ещё только занимает позиции, и эти понятия, приёмы и средства языка идут в новинку. Но это и хорошо - то, что профессиональным РС программерам кажется рутиной и скукотой, для нас является прикольными фишками, которые вызывают искренний интерес и мотивируют работу. Второй момент: для нас это всё близко к железу, когда embedded программист пишет код, он всегда представляет, во что выльется реализация (если он хороший программист, конечно), поэтому чёткое понимание связи кода на С++ и его аппаратной реализации тоже пробуждает интерес. РС программисту в этом смысле хуже - от него реализация сидит слишком далеко. Кроме того, эффективное написание программ для настольных машин давно требует использования какого-нить развитого фреймворка, и программист оказывается заключённым в определённые рамки, выйти за которые он по сути не может - иначе сразу потеряет эффективность работы. Embedded программист в значительной степени свободен от этого, что предоставляет куда большие возможности для творчества. А именно творческое начало в любой работе делает эту работу интересной и желанной. Вот как-то так.
Цитата(Danis @ Jun 21 2011, 20:27)

P.S. может в контексте этой темы и ваших постов Страуструпа выложить?
В смысле? Книжку его выложить или что?
Цитата(scifi @ Jun 21 2011, 21:37)

Другими словами, средства полиморфизма из Си++ могут быть оправданы там, где на Си нужно было бы вводить "поле типа" или массив указателей на функции.
Неудивительно, что у меня нет к этому тяги: в моих задачах это редко встречается :-)
Ну, это ведь от целей зависит. И конечно, можно результат получать разными способами. Когда я ещё находился в процессе изучения С, был период, когда конструкции языка типа операторов if/for/while/switch уже были уверенно освоены, а концепция указателей на функции ещё не была - там один синтаксис выражения, задающего массив указателей на функции, просто пугал своей замороченностью и сложностью. И когда нужно было вызывать функции по условию, то городился switch, в котором перебором вызывался нужный код. Когда прогресс достиг и понимания концепции указателей на функции и возникла привычка к разбору кода "изнутри-наружу" в выражениях, то массивы указателей на функции стали применяться широчайшим образом для решения тех же задач, которые до этого решались исключительно с помощью операторов ветвления. Т.ч. возможно стоит пересмотреть существующие стереотипы, может это оказаться и не напрасным.
Цитата(Danis @ Jun 21 2011, 22:55)

Маленько не так. Полиформизм дает возможность создавать множественные определения для операций и функций, что более абстрагирует программы. Конкретное определение, которое будет использоваться, зависит от задачи. В результате в крупном проекте коэффициент повторного использования кода возрастает. Си, к сожалению такой возможности не предоставляет.
Да, именно! Полиморфизм означает разное поведение при одинаковой форме. Поэтому собсно реализация тут вторична, главное - как задача, её цель согласуется с этим принципом. А конкретная реализация - дело техники. Вплоть до того, что задача может более успешно решаться не с помощью динамического полиморфизма (иерархии классов с виртуальными функциями), с помощью статического полиморфизма, реализуемого на основе шаблонов.
* * *
Кстати, вспомнил ещё пример, где рулит ООП. Реально используется в текущем проекте. Уже как-то собирался выложить реализацию (правда, с другим акцентом), но всё руки не доходили. Раз уж тут пошло обсуждение, то пусть будет до кучи.
Речь идёт о передаче сообщений. Есть программа, в которой в разных её местах возникают события и нужно их как-то передавать на обработку. Причём, нужно сделать так, чтобы при необходимости добавить новый тип сообщения не нужно было перелопачивать существующий код. Иными словами, нужно, чтобы та часть программы, к которой попадает сообщение, как-то сама распознавала тип объекта-сообщения и обрабатывала его, если сообщение предназначено для неё, и игнорировала в противном случае. При этом и создание типов (классов) сообщений, и их посылка, и их приём были простыми и прозрачными для пользователя. Это чем-то похоже на технологию RTTI (Run Time Type Identification), поддерживаемую С++ и реализуемую на основе dynamic_cast<>, но в RTTI является довольно "тяжёлым" механизмом и не очень катит в embedded (к слову, не все компиляторы для МК и поддерживают это).
По организации. Данный пример взят из рабочего проекта, где есть небольшой самописный GUI, но сам принцип передачи сообщений, в общем, не зависит от того, где это применяется, и может быть обобщен на другие задачи. В данном примере есть иерархия классов, где заглавный абстрактный базовый класс выглядит следующим образом:
Код
//--------------------------------------------------------------------------
class TObject
{
public:
virtual void on_message(TBaseMsgWrapper *msg_wrp) = 0;
};
//--------------------------------------------------------------------------
Т.е. в нём определена одна (при желании это можно расширить) чисто виртуальная функция - приёмник сообщений. В программе от этого класса отнаследованы все классы GUI (начиная с класса TWidget) и некоторые другие классы. На любом уровне иерархии функция-приёмник сообщений on_message() может быть переопределена с целью задания поведения при приёме сообщения - ведь разные объекты могут ждать разные типы сообщений. В частности, далее там есть класс TWidget, в котором уже функция on_message() имеет вполне конкретное определение (см ниже).
Аргументом on_message() является указатель на базовый класс сообщений - все классы сообщений генерируются на основе этого класса. Далее будет понятно, зачем это сделано и как реализовано - кстати, тут тоже появляется полиморфное поведение для функции, которая возвращает идентификатор сообщения.
В общем, нужно, чтобы был простой и эффективный механизм генерации служебной информации, обозначающей тип сообщения, и эффективное средство проверки типа сообщения. В результате нам с корешем (он, кстати, тоже на элхе появляется под ником
mikeT) удалось родить следующее (включение заголовочных файлов скипнуто):
Код
//------------------------------------------------------------------------------
inline uint16_t generate_id() { static uint16_t id; return ++id; }
class TBaseMsgWrapper
{
public:
virtual uint16_t get_id() = 0;
};
template<typename T> class msg_wrapper : public TBaseMsgWrapper
{
public:
msg_wrapper(T *msg) : body(msg), TBaseMsgWrapper() { }
static uint16_t get_class_id() { static const uint16_t id = generate_id(); return id; }
virtual uint16_t get_id() { return get_class_id(); }
T *get_msg() const { return body; }
private:
T *body;
};
template<typename T> T *check_msg(TBaseMsgWrapper *msg_wrp)
{
if( msg_wrp->get_id() == msg_wrapper<T>::get_class_id() )
{
return (static_cast<msg_wrapper<T> *>(msg_wrp))->get_msg();
}
else
{
return 0;
}
}
template<typename T> bool send_message(T *msg, gui::TObject *dst)
{
msg_wrapper<T> msg_wrp(msg);
dst->on_message(&msg_wrp);
return true;
}
//------------------------------------------------------------------------------
С целью достижения как можно более простой и дешёвой реализации в качестве типа идентификатора сообщения был выбран целочисленный тип (в данном случае 16-битное беззнаковое целое). Для каждого типа сообщений генерируется уникальный идентификатор - целочисленное значение. Генератором идентификаторов служит функция generate_id(), которая на каждый вызов возвращает новое значение.
Далее, сообщение технически представляет собой тело сообщения - это тип, определяемый пользователем, который может быть произвольным - классом (или встроенным типом, но понятно, что идеологически правильно использовать класс или перечислимый тип - в общем, чтобы тип был уникальным), и класс-обёртку для тела сообщения - на каждый тип сообщения генерируется своя обёртка. Генерируется она на основе шаблона с аргументом в виде типа тела сообщения - шаблон msg_wrapper<>. Как видно из кода выше, этот шаблон генерирует классы, которые все являются производными от базового класса TBaseMsgWrapper, который является абстрактным базовым классом и определяет метод get_id(), который переопределяется в производных. Цель этого - чтобы в каждом производном классе эта функция возвращала уникальный идентификатор только своего класса, т.е. только соответствующего своему типу сообщений.
Ну, и до кучи ещё определена пара шаблонных функций (шаблонные потому, что типы сообщений разные), которые отвечают за отправку сообщения и проверку его типа. Функция send_message() принимает указатель на тело сообщения (сам объект тела сообщения должен быть создан до этого - где и как, это компетенция пользователя - можно в стеке, можно в статической памяти, а можно хоть и в свободной - как удобно и приемлемо) и указатель на объект, которому сообщение отсылается, т.е. объект назначения. Внутри функции создаётся объект-обёртка сообщения, в которой есть функция, возвращающая уникальный идентификатор для данного типа сообщения, затем делается вызов метода on_message() объекта назначения с передачей ему сообщения.
На приёме остаётся только проверить, что сообщение "свое", и если это так, то обработать его, в противном случае проигнорировать.
Использование. В приборе, для которого написана программа есть несколько кнопок управления (конкретно их 4 штуки). При нажатии на любую из них генерируется соответствующее событие, по котором формируется сообщение, посылаемое текущему активному объекту (указатель TObject *focus содержит адрес активного объекта).
Код
//------------------------------------------------------------------------------
// где-то в заголовочном файле
enum TKeyMessage
{
kmNONE,
kmSEL,
kmPOWER,
kmPLUS,
kmMINUS
};
// в исходном файле, где уже определены события на физических кнопках прибора
...
gui::TKeyMessage key_code = gui::kmNONE;
if( dev_key_events.sel.IsClick() ) // если кнопка "Select" нажата
{
key_code = gui::kmSEL;
send_message<gui::TKeyMessage>(&key_code, focus);
}
if( power_key_event.IsClick() )
{
key_code = gui::kmPOWER; // если кнопка "Питание" нажата
send_message<gui::TKeyMessage>(&key_code, focus);
}
...
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//
// На приёме сообщения виджетами:
//
void TWidget::on_message(TBaseMsgWrapper *msg_wrp)
{
gui::TKeyMessage *p = check_msg<gui::TKeyMessage>(msg_wrp);
if(p)
{
switch(*p)
{
case gui::kmSEL: handle(); break;
case gui::kmPOWER: back(); break;
case gui::kmPLUS: next(); break;
case gui::kmMINUS: prev(); break;
default: print("Error");
}
}
}
//------------------------------------------------------------------------------
Тут первым делом проверяется, соответствует ли тип сообщения ожидаемому. В частности, тут мы ждём сообщение типа TKeyMessage. Если реально сообщение будет другого типа, то функция check_msg() вернёт 0, и сообщение будет проигнорировано. Проверка сводится к банальному сравнению двух целых, что выполняется очень эффективно. Если тип сообщения соответствует ожидаемому, то будет возвращён указатель на тело сообщения, которое дальше может быть использовано по назначению. В частности, тут производится вызов функций в соответствии с информацией о нажатой кнопке прибора. Функции handle(), back(), next() и prev() являются виртуальными, изначально определены в TWidget и переопределяются в виджетах-потомках - это как раз тот случай, который был описан в предыдущем посте.
Здесь все виджеты, которые не переопределили у себя функцию on_message(), будут работать в логике TWidget::on_message(). Если необходимо у какого-то объекта иметь другую логику, то достаточно этого класса определить его собственную on_message(), и обработка сообщений для именно этого объекта будет реализована по его собственной схеме.
В общем, тут можно расширять и вверх, и вширь - т.е. создать столько типов сообщений, сколько нужно, и это всё без изменения остального (уже нередко стабильного) кода без опасности что-то поломать в нём. При этом не надо заботиться о правильности обработки - пользователь только создаёт тело сообщения, делает send_message() и check_message() в точке приёма. Всё остальное делает компилятор и сгенерированный код на рантайме.
* * *
Этот же приём был портирован на фреймворк Qt. Там особенность в том, что уже есть механизм передачи событий (QEvent) и при портировании было желание использовать его. Задумка у авторов Qt в части событий такова, что там события делятся на два типа - системные (всякие события от клавиатуры, мыши и прочего) и пользовательские. И предусмотрен идентификатор событий - целое.

Разделено там так: события от 0 до 1000 - это системные события, а от 1000 до 65535 - пользовательские. Реализация:
Код
#include <QApplication>
#include <QEvent>
//------------------------------------------------------------------------------
inline int generate_id() { static int id = QEvent::User; return ++id; }
//------------------------------------------------------------------------------
template<typename T> class msg_wrapper : public QEvent
{
public:
msg_wrapper(T msg)
: QEvent( static_cast<QEvent::Type>( get_class_id() ) )
, body(msg)
{
}
static int get_class_id() { static const int id = generate_id(); return id; }
T *get_msg() { return &body; }
private:
T body;
};
//------------------------------------------------------------------------------
template<typename T> T *check_msg(QEvent *msg_wrp)
{
if( msg_wrp->type() == msg_wrapper<T>::get_class_id() )
{
return (static_cast<msg_wrapper<T> *>(msg_wrp))->get_msg();
}
else
{
return 0;
}
}
//------------------------------------------------------------------------------
template<typename T> bool send_message(QObject *dst, T msg)
{
msg_wrapper<T> msg_wrp(msg);
return QApplication::sendEvent(dst, &msg_wrp);
}
//------------------------------------------------------------------------------
Использование. Тело сообщения - событие переименования элемента комбобокса:
Код
class TComboBoxRenameEvent
{
public:
TComboBoxRenameEvent(QString NewValue): Value(NewValue){ }
QString value() const { return Value; }
private:
QString Value;
};
Сам класс комбобокса - в нём нужно переопределить функцию-обработчик события customEvent() (виртуальная).
Код
class TComboBox : public QComboBox
{
Q_OBJECT
public:
explicit TComboBox(QWidget *parent = 0) : QComboBox(parent) { }
TComboBox & operator=(const TComboBox & );
protected:
virtual void customEvent(QEvent *Message);
};
Генерация и отсылка сообщения (по кнопке Ok в диалоге переименования генерируется сигнал, к которому подконнекчен слот, представленный ниже):
Код
void TRenameDialog::on_pbOk_clicked()
{
TComboBoxRenameEvent msg_body(ui->leName->text());
send_message<TComboBoxRenameEvent>(Dst, msg_body);
}
И, наконец, приём и обработка сообщения:
Код
void TComboBox::customEvent(QEvent *Message)
{
TComboBoxRenameEvent *msg = check_msg<TComboBoxRenameEvent>(Message);
if( msg )
{
...; // обработка сообщения
}
}
//------------------------------------------------------------------------------
В общем-то, сама по себе идеология передачи событий в Qt очень похожа на описанную выше для embedded системы, она проста и логична, будучи основанной на передаче события в виде указателя на базовый класс. Но тут есть недостаток - приходится руками генерировать идентификатор типа, руками проверять на соответствие и делать явное преобразование типов (а явное преобразование типов - это, как правило, не гуд, и как утверждает тов. Страуструп: "...обычно указывает на ошибки проектирования", поэтому всегда желательно каждый такой случай рассматривать внимательно и при малейшей возможности прятать с глаз долой; в любом случае нехорошо, когда пользовательский код пестрит явными преобразования типов). Описанный приём просто все эти действия автоматизирует и предоставляет программисту более простой и безопасный интерфейс для использования.