Russian Qt Forum

Qt => Общие вопросы => Тема начата: 8Observer8 от Февраль 16, 2014, 08:14



Название: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 16, 2014, 08:14
Привет!

Расскажите о своём подходе к созданию ООП программ. С чего вы начинаете разработку? Какими инструментами пользуетесь для построения диаграмм? В каких случаях используете виртуальные функции и полиморфизм?

Если все используют UML, то подскажите: где скачать? как использовать? И дайте, пожалуйста, ссылку на простой и доступный туториал :)

Допустим мне нужно создать игру. Как определиться с требованиями? Как построить архитектуру? Пусть она будет текстовая-консольная (у меня нет сил осваивать OpenGL)

За основу я возьму проект из каталога examples/json/savegame Там следующие классы:

game.h
Код
C++ (Qt)
#ifndef GAME_H
#define GAME_H
 
#include <QJsonObject>
#include <QList>
 
#include "character.h"
#include "level.h"
 
//! [0]
class Game
{
public:
   Game();
 
   enum SaveFormat {
       Json, Binary
   };
 
   const Character &player() const;
   const QList<Level> &levels() const;
 
   void newGame();
   bool loadGame(SaveFormat saveFormat);
   bool saveGame(SaveFormat saveFormat) const;
 
   void read(const QJsonObject &json);
   void write(QJsonObject &json) const;
private:
   Character mPlayer;
   QList<Level> mLevels;
};
//! [0]
 
#endif // GAME_H
 

character.h
Код
C++ (Qt)
#ifndef CHARACTER_H
#define CHARACTER_H
 
#include <QJsonObject>
#include <QString>
 
//! [0]
class Character
{
public:
   enum ClassType {
       Warrior, Mage, Archer
   };
 
   Character();
   Character(const QString &name, int level, ClassType classType);
 
   QString name() const;
   void setName(const QString &name);
 
   int level() const;
   void setLevel(int level);
 
   ClassType classType() const;
   void setClassType(ClassType classType);
 
   void read(const QJsonObject &json);
   void write(QJsonObject &json) const;
private:
   QString mName;
   int mLevel;
   ClassType mClassType;
};
//! [0]
 
#endif // CHARACTER_H
 

level.h
Код
C++ (Qt)
#ifndef LEVEL_H
#define LEVEL_H
 
#include <QJsonObject>
#include <QList>
 
#include "character.h"
 
//! [0]
class Level
{
public:
   Level();
 
   const QList<Character> &npcs() const;
   void setNpcs(const QList<Character> &npcs);
 
   void read(const QJsonObject &json);
   void write(QJsonObject &json) const;
private:
   QList<Character> mNpcs;
};
//! [0]
 
#endif // LEVEL_H
 

main.cpp
Код
C++ (Qt)
#include <QCoreApplication>
 
#include "game.h"
//! [0]
int main(int argc, char *argv[])
{
   QCoreApplication app(argc, argv);
 
   Game game;
   game.newGame();
   // Game is played; changes are made...
//! [0]
//! [1]
   if (!game.saveGame(Game::Json))
       return 1;
 
   if (!game.saveGame(Game::Binary))
       return 1;
 
   Game fromJsonGame;
   if (!fromJsonGame.loadGame(Game::Json))
       return 1;
 
   Game fromBinaryGame;
   if (!fromBinaryGame.loadGame(Game::Binary))
       return 1;
 
   return 0;
}
//! [1]
 


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 16, 2014, 08:28
Если вы по настоящему хотите с этим разобраться, то эту книгу нужно почитать обязательно:
Гради Буч - Объектно-ориентированный анализ и проектирование с примерами приложений.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 16, 2014, 10:01
Спасибо большое! Начал изучать.

Почитал на ozon.ru отзывы: http://www.ozon.ru/context/detail/id/3905587/#tab_comments

Все ругают русский перевод. Говорят, что нужно обязательно иметь под рукой оригинал. К счастью, я английский хорошо понимаю. Буду читать на русском и заглядывать в оригинал.

Перевод: http://rutracker.org/forum/viewtopic.php?t=3343958
Оригинал: http://kickass.to/object-oriented-analysis-and-design-with-applications-3rd-editio-t2753820.html

Прилагаются ли к книге какие-нибудь дополнительные материалы (например, исходники или\и файлы UML)? Обычно исходники в отдельную папку помещают и их можно откуда-нибудь скачать.


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 16, 2014, 10:05
Прилагаются ли к книге какие-нибудь дополнительные материалы (например, исходники или\и файлы UML)?
Нет. Это фундаментальный труд, компилируемых примеров в нем нет. Зато на примерах разбирается как же нужно думать, что бы проектировать ОО-программы.


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 16, 2014, 11:56
Расскажите о своём подходе к созданию ООП программ. С чего вы начинаете разработку? Какими инструментами пользуетесь для построения диаграмм? В каких случаях используете виртуальные функции и полиморфизм?
- Не думаю что надо разделять программы на "ООП и нет".

- Диаграммы - не знаю точно что Вы имеете ввиду. Часто использую гистограммы (обычно просто печать в консоли), иногда графики. А чаще всего сливаю линии/точки (иногда раскрашенные) в текстовик и визуализирую это в 3D.

- виртуалы и.т.п. Все это цветет пышным цветом в UI, в расчетной части иерархия классов может быть очень небольшой и "конкретные" классы могут доминировать. Зато часто нужен гораздо более тщательный выбор структур данных и контейнеров.

С чего начинать разработку. С четкого понимания "кому это нужно". Можно сказать и "кому это можно продать" - не вижу в этом ничего плохого. Пройдет какое-то время и любой энтузиазм угаснет, это нормально, запас энергии человека ограничен. Поэтому надо прикинуть - а что я успею сделать "на первой волне", пока мне (еще) интересно этим заниматься. Сумею ли достичь каких-то пусть промежуточных но реальных результатов? Потому что неоплачиваемый проект долго не протянет. А учитывая еще что Вы делаете массу др дел .. :)

В общем до пресловутой "архитектуры" еще очень далеко. Сначала ТЕМУ/задачу выберите тщательно, пока у меня впечатление что для Вас это дело десятое, типа "что угодно лишь бы опыта набраться". Это не вредно, но и пользы не много.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 17, 2014, 15:40
Диаграммы - не знаю точно что Вы имеете ввиду. Часто использую гистограммы (обычно просто печать в консоли), иногда графики. А чаще всего сливаю линии/точки (иногда раскрашенные) в текстовик и визуализирую это в 3D.

Я имею ввиду диаграммы наследования. А Вы?

виртуалы и.т.п. Все это цветет пышным цветом в UI

А на примере проекта "savegame" (о котором я написал вначале) можно же поиграть с наследованием, полиморфизмом, виртуальными функциями? Как мне развивать этот проект (консольная игра), чтобы я видел, что мне действительно улучшает жизнь полиморфизм (и т.д.)? Я хочу на этом живом примере применить ООП в полной мере! Мне нужны идеи именно по этому проекту. Я думаю, что разработка простой игры - это как раз то, что нужно для тренировки ООП. Но у меня в этом проекте полный тупик. Я буду рад любым идеям по его развитию (с точки зрения применения всех аспектов ООП).

Вот тут создаётся этот персонаж (за которого мы будем играть). Как мне заставить его ходить? Создаётся так же несколько неигровых персонажей (NPS) на разных локациях (этот пример расположен в папке Qt examples/json/savegame):

Код
C++ (Qt)
void Game::newGame() {
   mPlayer = Character();
   mPlayer.setName(QStringLiteral("Hero"));
   mPlayer.setClassType(Character::Archer);
   mPlayer.setLevel(15);
 
   mLevels.clear();
 
   Level village;
   QList<Character> villageNpcs;
   villageNpcs.append(Character(QStringLiteral("Barry the Blacksmith"), 10, Character::Warrior));
   villageNpcs.append(Character(QStringLiteral("Terry the Trader"), 10, Character::Warrior));
   village.setNpcs(villageNpcs);
   mLevels.append(village);
 
   Level dungeon;
   QList<Character> dungeonNpcs;
   dungeonNpcs.append(Character(QStringLiteral("Eric the Evil"), 20, Character::Mage));
   dungeonNpcs.append(Character(QStringLiteral("Eric's Sidekick #1"), 5, Character::Warrior));
   dungeonNpcs.append(Character(QStringLiteral("Eric's Sidekick #2"), 5, Character::Warrior));
   dungeon.setNpcs(dungeonNpcs);
   mLevels.append(dungeon);
}
 


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 17, 2014, 16:59
Я имею ввиду диаграммы наследования. А Вы?
Если они есть в ID - иногда смотрю (редко). А нет - прекрасно обхожусь без них.

А на примере проекта "savegame" (о котором я написал вначале) можно же поиграть с наследованием, полиморфизмом, виртуальными функциями? Как мне развивать этот проект (консольная игра), чтобы я видел, что мне действительно улучшает жизнь полиморфизм (и т.д.)? Я хочу на этом живом примере применить ООП в полной мере! Мне нужны идеи именно по этому проекту. Я думаю, что разработка простой игры - это как раз то, что нужно для тренировки ООП.
Я так не думаю. Фрагмент кода что Вы показали - это уже манипуляции "верхнего уровня", когда все готово/выписано и этим надо рулить. ООП здесь ни при чем. Конечно можно поиграться с этим примером - но на этом дело и кончится.

Вообще примеры - палка о двух концах. Создается впечатление что все довольно "просто" и даже "элементарно" - но смогли бы Вы сделать все это "с нуля"? Да, Вы узнаете много нового, но также и "попадаете под влияние" могучих троллей, и в этом есть свои минусы. Вот относительно недавно тут паренек писал свой морской бой с нуля - мне кажется такой подход лучше. Задумка должна быть своя, а идей "простой" игры предостаточно

Возвращаясь к ООП - вряд ли игровой проект может служить для его тренировки. Есть пример ООП наследования который приводится с незапамятных времен: фигуры. Напр есть базовый класс Shape, от него наследуется Circle, Triangle и.т.п. Аналогичный пример есть в Qt (кажется демонстрация undo, точно не помню). Хоть и не игра - но для освоения ООП подойдет гораздо лучше


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 17, 2014, 17:20
Возвращаясь к ООП - вряд ли игровой проект может служить для его тренировки.
Конечно может и очень хорошей. А написание своего рогалика, так вообще замечательная идея.
В нем очень много объектов разных классов, которые должны взаимодействовать друг с другом.

А пример с фигурами... Это для детского сада.


Название: Re: Как писать ООП программы?
Отправлено: Bepec от Февраль 17, 2014, 17:34
На мой взгляд только игра и способствует развитию ООП мышления. Ибо в играх обычно приводится реальный мир с его разнообразием объектов. Всё под рукой :)


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 17, 2014, 18:19
А пример с фигурами... Это для детского сада.
Смотря как к нему подходить. С чем-то более сложным - боюсь все уйдет в изучение того что уже сделано (готовых классов), и на изобретение чего-то своего ничего не останется

А вот кстати сегодняшний пример наследования http://www.prog.org.ru/index.php?topic=26553.msg190549#msg190549 (http://www.prog.org.ru/index.php?topic=26553.msg190549#msg190549) Что Вы о нем думаете? 

Old, просьба не спешить, пусть сначала товарищи выскажутся  :)


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 17, 2014, 18:25
С чем-то более сложным - боюсь все уйдет в изучение того что уже сделано (готовых классов), и на изобретение чего-то своего ничего не останется
А в своем рогалике нет готовых классов, там все нужно делать с нуля. Вообще все. :)
Нечего изучать, можно только делать. А текстовый интерфейс не дает уйти в создание красивых эффектов вместо хорошего геймплея.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 18, 2014, 15:26
Всем большое спасибо! Начну с простого.

Поступим следующим образом: я буду писать классы, а вы их критикуйте. Задача у меня - довести каждый класс до уровня профи. Классы будут демонстрировать один или несколько принципов ООП. Подкидывайте, пожалуйста, идеи - какие принципы и на каких классах ещё можно рассмотреть.

Примеры будут на Qt и на C++, чтобы меня не ругали, что это форум Qt, а не C++.

Рациональные числа

Примечание. Пример взял отсюда: http://www.e-reading.co.uk/chapter.php/1002058/56/Mayers_-_Effektivnoe_ispolzovanie_CPP.html

В примере затронуты следующие темы:

- список инициализации в конструкторе
- дружественные функции
- перезагрузка операторов: умножения, сложения и вычитания.

Какие ещё операторы посоветуете перезагрузить? Как дальше доработать этот класс?

Примечание. При сложении и вычитании дробей: к общему знаменателю привожу - перемножением, результат не сокращаю.

Вывод:
product = 6/24
sum = 1472/768
sub = 32/768

main.cpp
Код
C++ (Qt)
#include <QCoreApplication>
#include <QTextStream>
#include "rational.h"
 
QTextStream cin(stdin);
QTextStream cout(stdout);
 
int main(int argc, char *argv[])
{
   QCoreApplication app(argc, argv);
 
   // Перемножаем дроби и выводим результат на экран
   Rational a(2, 3), b(1, 2), c(3, 4), product;
   product = a * b * c;
   cout << "product = " << product.numerator() << "/" << product.denominator() << endl;
   cout.flush();
 
   // Складываем дроби и выводим результат на экран
   Rational x(47, 48), y(15, 16), sum;
   sum = x + y;
   cout << "sum = " << sum.numerator() << "/" << sum.denominator() << endl;
   cout.flush();
 
   // Вычитаем дроби и выводим результат на экран
   Rational m(47, 48), n(15, 16), sub;
   sub = m - n;
   cout << "sub = " << sub.numerator() << "/" << sub.denominator() << endl;
   cout.flush();
 
   return app.exec();
}
 

rational.h
Код
C++ (Qt)
#ifndef RATIONAL_H
#define RATIONAL_H
 
class Rational
{
public:
   Rational(int numerator = 0, int denominator = 1);
 
   int numerator() const;
   void setNumerator(const int numerator);
 
   int denominator() const;
   void setDenominator(const int denominator);
 
   friend const Rational operator*(const Rational& lhs, const Rational& rhs);
   friend const Rational operator+(const Rational& lhs, const Rational& rhs);
   friend const Rational operator-(const Rational& lhs, const Rational& rhs);
 
private:
   int mNumerator;
   int mDenominator;
};
 
#endif // RATIONAL_H
 

rational.cpp
Код
C++ (Qt)
#include "rational.h"
 
Rational::Rational(int numerator, int denominator) :
   mNumerator(numerator),
   mDenominator(denominator)
{
}
 
int Rational::numerator() const
{
   return mNumerator;
}
 
void Rational::setNumerator(const int numerator)
{
   mNumerator = numerator;
}
 
int Rational::denominator() const
{
   return mDenominator;
}
 
void Rational::setDenominator(const int denominator)
{
   mDenominator = denominator;
}
 
const Rational operator*(const Rational &lhs, const Rational &rhs)
{
   return Rational(lhs.mNumerator * rhs.mNumerator,
                   lhs.mDenominator * rhs.mDenominator);
}
 
const Rational operator+(const Rational &lhs, const Rational &rhs)
{
   Rational result;
 
   // Общий знаменатель
   int commonDenominator = lhs.denominator() * rhs.denominator();
 
   // Числитель
   int a = lhs.numerator() * rhs.denominator();
   int b = rhs.numerator() * lhs.denominator();
   result.setNumerator(a + b);
 
   // Знаменатель
   result.setDenominator(commonDenominator);
 
   return result;
}
 
const Rational operator-(const Rational &lhs, const Rational &rhs)
{
   Rational result;
 
   // Общий знаменатель
   int commonDenominator = lhs.denominator() * rhs.denominator();
 
   // Числитель
   int a = lhs.numerator() * rhs.denominator();
   int b = rhs.numerator() * lhs.denominator();
   result.setNumerator(a - b);
 
   // Знаменатель
   result.setDenominator(commonDenominator);
 
   return result;
}
 


Название: Re: Как писать ООП программы?
Отправлено: Bepec от Февраль 18, 2014, 15:54
Походу все темы можно в говорилку отправлять :D
PS я не имею в виду, что они флуд. Но к вопросам они явно не относятся :D


Название: Re: Как писать ООП программы?
Отправлено: m_ax от Февраль 18, 2014, 15:58
Цитировать
Примечание. При сложении и вычитании дробей: к общему знаменателю привожу - перемножением, результат не сокращаю.

Это плохо(
Во-вторых, хотелось бы иметь операторы сравнения..
И такие, чтобы, например, 4/2 и 10/5 получались  равными друг другу)

И ещё я бы посмотрел на Compile-time rational arithmetic http://en.cppreference.com/w/cpp/numeric/ratio (http://en.cppreference.com/w/cpp/numeric/ratio)


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 18, 2014, 16:08
Примеры будут на Qt и на C++, чтобы меня не ругали, что это форум Qt, а не C++.
Ну так Вас забросают помидорами, скажут что "занимаетесь фигней" - и, в общем, это будет правильно  :). Вы бросились в др крайность - слишком просто, нет достойного обсуждения предмета. "Отдельно стоящий класс" можно разрисовать как угодно красиво, но столь же бесполезно - потому что нет никаких связок/взаимодействий классов.

Подкинуть идей - ну так, наскидку, трудновато. Впрочем один я уже подкинул (с тенью окна, см выше). Хорошо, вот еще

- в классическом примере с фигурами утверждается что "каждая фигура умеет себя рисовать", это полиморфный метод. Какие недостатки у такого решения? Так ли уж оно бесспорно? Можно ли привести пример когда оно оказывается неудачным?


Название: Re: Как писать ООП программы?
Отправлено: deMax от Февраль 19, 2014, 07:30
Цитировать
Если все используют UML, то подскажите: где скачать? как использовать? И дайте, пожалуйста, ссылку на простой и доступный туториал Улыбающийся
А кто то этой штукой пользуется?

http://habrahabr.ru/post/153353/
Цитировать
Используете ли вы UML?
3%(122)    Пользуюсь постоянно (очень нужный инструмент)
18%(664)    Иногда бывает полезен
0%(17)    К сожалению, довольно часто приходится пользоваться
6%(234)    Приходится пользоваться, но слава богу - не часто
55%(2039)    Не пользуюсь
18%(661)    Не пользуюсь, но подумываю


Название: Re: Как писать ООП программы?
Отправлено: Akon от Февраль 19, 2014, 09:33
8Observer8: Не тратьте время на бесполезные примеры, их развитие и т.п. Беритесь сразу за реальную (полезную) задачу. Применить ООП  можно и в ней. Когда вы реализуете то, что изначально у вас в голове, вы находитесь на более глубоком уровне понимания и ООП реализацию увидеть будет легче.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 19, 2014, 10:01
8Observer8: Не тратьте время на бесполезные примеры, их развитие и т.п. Беритесь сразу за реальную (полезную) задачу. Применить ООП  можно и в ней. Когда вы реализуете то, что изначально у вас в голове, вы находитесь на более глубоком уровне понимания и ООП реализацию увидеть будет легче.

Спасибо за совет! Я не так уж и много трачу времени на примеры. Просто мне так легче понимать отдельные принципы ООП. "Полезную" задачу очень трудно придумать, так как уже почти всё реализовано другими программистами.

А кто то этой штукой пользуется?

Я так понимаю, что удобнее всего использовать программу doxygen, так как она генерирует и диаграммы наследования. Примером может служить документация для Qwt: http://qwt.sourceforge.net/class_qwt_plot.html (http://qwt.sourceforge.net/class_qwt_plot.html)

Подкинуть идей - ну так, наскидку, трудновато. Впрочем один я уже подкинул (с тенью окна, см выше). Хорошо, вот еще
- в классическом примере с фигурами утверждается что "каждая фигура умеет себя рисовать", это полиморфный метод. Какие недостатки у такого решения? Так ли уж оно бесспорно? Можно ли привести пример когда оно оказывается неудачным?

Igors, Спасибо! Я помню про тот пример. Вернусь к нему позже. Спасибо за новый! Хороший пример. Как раз то, что нужно. Но пока не готов ответить.

Цитировать
Примечание. При сложении и вычитании дробей: к общему знаменателю привожу - перемножением, результат не сокращаю.

Это плохо(
Во-вторых, хотелось бы иметь операторы сравнения..
И такие, чтобы, например, 4/2 и 10/5 получались  равными друг другу)

И ещё я бы посмотрел на Compile-time rational arithmetic http://en.cppreference.com/w/cpp/numeric/ratio (http://en.cppreference.com/w/cpp/numeric/ratio)

Да, Вы правы! Но мне не хочется замарачиваться с сокращениями дробей и поиском наименьшего общего знаменателя. Время дорого. Тут главное ООП освоить.

После прочтения вот этого правила, у меня возникли сложности с пониманием: Правило 23: Предпочитайте функциям-членам функции, не являющиеся ни членами, ни друзьями класса http://www.e-reading.co.uk/chapter.php/1002058/60/Mayers_-_Effektivnoe_ispolzovanie_CPP.html

Получается, что оператор вывода на экран (<<) НЕ должен быть частью класса Relation, так как это НЕ свойство чисел (вывод на экран не относится к классу Relation, так как с точки зрения математики у рациональных чисел есть только свойства сложения, сравнения и т.д.). Следуя правилу, оператор вывода нужно располагать отдельно от класса Relation и даже не friend, а отдельной функцией. Но в том же пространстве имён. А можно ли так делать? C++ позволяет ли создавать свободные операторные функции? Под свободными функциями имеются ввиду функции, которые не являются ни членами-функциями класса, ни функциями-друзьями.


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 19, 2014, 10:54
Получается, что оператор вывода на экран (<<) НЕ должен быть частью класса Relation, так как это НЕ свойство чисел (вывод на экран не относится к классу Relation, так как с точки зрения математики у рациональных чисел есть только свойства сложения, сравнения и т.д.). Следуя правилу, оператор вывода нужно располагать отдельно от класса Relation и даже не friend, а отдельной функцией. Но в том же пространстве имён. А можно ли так делать? C++ позволяет ли создавать свободные операторные функции? Под свободными функциями имеются ввиду функции, которые не являются ни членами-функциями класса, ни функциями-друзьями.
Можно ли (и как) - откройте какой-нибудь простенький Qt класс (напр QRect) и там посмотрите синтаксис. Форум с таким вопросом беспокоить не стоит.

А так Вы столкнулись с типичным "каноническим" правилом - которое часто доводится до абсурда. Ах, якобы это уже НЕ свойство чисел - да неужели? Кто как не сам класс знает как себя печатать? В действительности же дело в другом. Пример
Код
C++ (Qt)
class Rational {
public:
friend std::ofstream & operator << ( std::ofstream & strm, const Rational & r );
...
}
Беда в том что теперь класс Rational уже не сможет жить без std::ofstream, создается "плохая" зависимость классов.

Однако никто не мешает написать так
Код
C++ (Qt)
class Rational {
public:
template <class stream>
friend stream & operator << ( stream & strm, const Rational & r );
...
}
Но сила "канона" велика, все-таки с ним лучше/практичнее не спорить  :)


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 20, 2014, 07:07
Igors, Спасибо большое!

Можно ли (и как) - откройте какой-нибудь простенький Qt класс (напр QRect) и там посмотрите синтаксис. Форум с таким вопросом беспокоить не стоит.

Вопрос же был такой: C++ позволяет ли создавать свободные операторные функции? Гугл молчит.

Беда в том что теперь класс Rational уже не сможет жить без std::ofstream, создается "плохая" зависимость классов.

Чем плоха эта зависимость? Мы тоже при программировании консольных приложений не можем жить без <iostream>

И ещё вопрос. Как оператор в моём случае написать?

Ошибка: main.cpp:15: error: no match for 'operator<<' (operand types are 'QTextStream' and 'Rational')
     cout << "product = " << product << endl;
                                    ^
Код:
#include <QCoreApplication>
#include <QTextStream>
#include "rational.h"

QTextStream cin(stdin);
QTextStream cout(stdout);

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    // Перемножаем дроби и выводим результат на экран
    Rational a(2, 3), b(1, 2), c(3, 4), product;
    product = a * b * c;
    cout << "product = " << product << endl;
    cout.flush();

    return app.exec();
}


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 20, 2014, 07:15
Вопрос же был такой:
Что же вы так, а поговорить... :)

C++ позволяет ли создавать свободные операторные функции? Гугл молчит.
А почему нет, конечно позволяет.
Дружба нужна, только если нужен доступ к закрытым членам.


Название: Re: Как писать ООП программы?
Отправлено: Johnik от Февраль 20, 2014, 07:20
Вопрос же был такой: C++ позволяет ли создавать свободные операторные функции? Гугл молчит.

можно:
Код:
class Bar
{
public:
Bar(int i = 0) : _i(i) { }
~Bar() { }
int _i;
};

Bar operator+(const Bar& b1, const Bar& b2)
{
Bar result;
result = b1;
result._i += b2._i;
return result;
}

int main(int argc, char *argv[])
{
Bar b1(1);
Bar b2(2);
Bar r = b1 + b2;
return 0;
}


P.S. пока писал, ответили


Название: Re: Как писать ООП программы?
Отправлено: Johnik от Февраль 20, 2014, 08:24
Чем плоха эта зависимость? Мы тоже при программировании консольных приложений не можем жить без <iostream>

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

И ещё вопрос. Как оператор в моём случае написать?

Ошибка: main.cpp:15: error: no match for 'operator<<' (operand types are 'QTextStream' and 'Rational')
     cout << "product = " << product << endl;
                              ^

Для примера, можно посмотреть как это сделано в исходниках Qt (правда там для QDataStream, для cout надо std::ostream и std::istream):
\src\corelib\tools\qstring.h (и соответствующий .cpp);
Код
C++ (Qt)
#if !defined(QT_NO_DATASTREAM) || (defined(QT_BOOTSTRAPPED) && !defined(QT_BUILD_QMAKE))
Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QString &);
Q_CORE_EXPORT QDataStream &operator>>(QDataStream &, QString &);
#endif
 


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 20, 2014, 10:14
to Old, Johnik
Спасибо за ответы, что операторную функцию можно делать и не дружественной.

Для примера, можно посмотреть как это сделано в исходниках Qt (правда там для QDataStream, для cout надо std::ostream и std::istream):
\src\corelib\tools\qstring.h (и соответствующий .cpp);

Только у меня QTextStream:

Код:
friend QTextStream& operator<<(QTextStream& stream, const Rational& r);

Спасибо! Покапаю исходники Qt.

Скажите, пожалуйста, правильно ли я понял, что единственное для чего нужен полиморфизм - это чтобы писать функции, которые принимают указатель на базовый класс и работают с объектами производных классов (так сказать - для уменьшения дублирования кода)


Название: Re: Как писать ООП программы?
Отправлено: Johnik от Февраль 20, 2014, 10:31
Только у меня QTextStream:

Там было для примера, вот минимум кода:
Код
C++ (Qt)
class Bar
{
public:
Bar(int ii = 0) : i(ii) { }
int i;
};
 
QTextStream& operator<<(QTextStream& stream, const Bar& bar)
{
stream << QString::number(bar.i);
return stream;
}
int main(int argc, char *argv[])
{
Bar b1(1);
 
QString s;
QTextStream out(&s);
out << b1;
return 0;
}

Не надо все воспринимать все буквально, учитесь думать и искать самостоятельно.


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 20, 2014, 10:57
Скажите, пожалуйста, правильно ли я понял, что единственное для чего нужен полиморфизм - это чтобы писать функции, которые принимают указатель на базовый класс и работают с объектами производных классов (так сказать - для уменьшения дублирования кода)
Единственное или нет - не задумывался. И очень многое придумано "для уменьшения дублирования кода", т.е. это достойная цель. А вообще полиморфизм - одна из самых легких, приятных и полезных вещей в ООП, когда результат/эффект налицо.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 20, 2014, 13:11
Спасибо, парни!

Вот такая сейчас ошибка, помогите понять:
Цитировать
error: 'QTextStream& Rational::operator<<(QTextStream&, const Rational&)' must take exactly one argument
 QTextStream &Rational::operator<<(QTextStream &stream, const Rational &r)
                                                                                                               ^
                           

Говорит, что должен быть один аргумент.

main.cpp
Код
C++ (Qt)
#include <QCoreApplication>
#include <QTextStream>
#include "rational.h"
 
QTextStream cin(stdin);
QTextStream cout(stdout);
 
int main(int argc, char *argv[])
{
   QCoreApplication app(argc, argv);
 
   Rational a(2, 3);
   cout << "a = " << a << endl;
   cout.flush();
 
   return app.exec();
}
 

rational.h
Код
C++ (Qt)
#ifndef RATIONAL_H
#define RATIONAL_H
 
#include <QTextStream>
 
namespace RationalStuff {
class Rational;
}
 
class Rational
{
public:
   Rational(int numerator = 0, int denominator = 1);
 
   int numerator() const;
   void setNumerator(const int numerator);
 
   int denominator() const;
   void setDenominator(const int denominator);
 
//    friend const Rational operator*(const Rational& lhs, const Rational& rhs);
//    friend const Rational operator+(const Rational& lhs, const Rational& rhs);
//    friend const Rational operator-(const Rational& lhs, const Rational& rhs);
   friend QTextStream& operator<<(QTextStream& stream, const Rational& r);
 
private:
   int mNumerator;
   int mDenominator;
};
 
#endif // RATIONAL_H
 

rational.cpp
Код
C++ (Qt)
#include "rational.h"
 
Rational::Rational(int numerator, int denominator) :
   mNumerator(numerator),
   mDenominator(denominator)
{
}
 
int Rational::numerator() const
{
   return mNumerator;
}
 
void Rational::setNumerator(const int numerator)
{
   mNumerator = numerator;
}
 
int Rational::denominator() const
{
   return mDenominator;
}
 
void Rational::setDenominator(const int denominator)
{
   mDenominator = denominator;
}
 
QTextStream &Rational::operator<<(QTextStream &stream, const Rational &r)
{
   stream << r.mNumerator << "/" << r.mDenominator;
   return stream;
}
 


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 20, 2014, 13:14
Оператор не является членом класса!

rational.cpp
Код
C++ (Qt)
QTextStream &operator<<(QTextStream &stream, const Rational &r)
{
   stream << r.mNumerator << "/" << r.mDenominator;
   return stream;
}
 


Название: Re: Как писать ООП программы?
Отправлено: OKTA от Февраль 20, 2014, 13:16
А разве не просто то, что он ждет только один параметр, а не два?))


Название: Re: Как писать ООП программы?
Отправлено: Johnik от Февраль 20, 2014, 13:18
Нет! Old прав. И я точно такой же пример приводил.


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 20, 2014, 14:07
Ну может и являться и тогда 1 аргумент. Но это обычно нежелательно как говорили выше. А "friend" автоматически означает НЕ член. Главного человек не понял

Код
C++ (Qt)
friend QTextStream& operator<<(QTextStream& stream, const Rational& r);
Внимание: теперь класс Rational уже не может быть использован без Qt.


Название: Re: Как писать ООП программы?
Отправлено: Johnik от Февраль 20, 2014, 14:16
Внимание: теперь класс Rational уже не может быть использован без Qt.
Код
C++ (Qt)
#ifdef QT_VERSION
...
#endif


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 20, 2014, 14:54
Спасибо большое! Кстати, если написать объявление в классе friend-функции и потом добавить реализацию таким образом: правой кнопкой мыши по функции -> "Refactor" -> "Add difinition in relation.cpp", то добавится такое определение: "QTextStream &Rational::operator<<" вместо "QTextStream &operator<<"

Я ещё понял такую вещь: что если операторная функция - это член класса, то она будет принимать только один аргумент (второй - через this), а если friend или свободная, то два параметра.

Помогите разобраться со следующими ошибками:

Цитировать
D:\Documents\Qt\QtOOP\0004_Shapes\Shapes\main.cpp:19: error: cannot allocate an object of abstract type 'Circle'
     Shape *pc = new Circle();
                                      ^
D:\Documents\Qt\QtOOP\0004_Shapes\Shapes\main.cpp:22: error: no matching function for call to 'Shape::calcPerimeter(double)'
     pc->calcPerimeter(2.5);
                                    ^

main.cpp
Код
C++ (Qt)
#include <QCoreApplication>
#include <QTextStream>
#include "shape.h"
#include "circle.h"
#include "rectangle.h"
 
QTextStream cin(stdin);
QTextStream cout(stdout);
 
void printShapeInfo(Shape *ps) {
   cout << "p = " << ps->perimeter() << endl;
   cout << "s = " << ps->area() << endl;
   cout.flush();
}
 
int main(int argc, char *argv[])
{
   QCoreApplication a(argc, argv);
 
   Shape *pc = new Circle();
   printShapeInfo(pc);
 
   pc->calcPerimeter(2.5);
 
   return a.exec();
}
 

shape.h
Код
C++ (Qt)
#ifndef SHAPE_H
#define SHAPE_H
 
class Shape
{
public:
   Shape();
   virtual ~Shape();
 
   virtual void calcPerimeter() = 0;
   virtual void calcArea() = 0;
 
   double perimeter();
   double area();
 
protected:
   double mPerimeter;
   double mArea;
};
 
#endif // SHAPE_H
 

shape.cpp
Код
C++ (Qt)
#include "shape.h"
 
Shape::Shape() :
   mPerimeter(0.0),
   mArea(0.0)
{
}
 
Shape::~Shape()
{
}
 
double Shape::perimeter()
{
   return mPerimeter;
}
 
double Shape::area()
{
   return mArea;
}
 

circle.h
Код
C++ (Qt)
#ifndef CIRCLE_H
#define CIRCLE_H
 
#include "shape.h"
 
class Circle : public Shape
{
public:
   Circle();
   virtual double calcPerimeter(double radius);
   virtual double calcArea(double radius);
};
 
#endif // CIRCLE_H
 

circle.cpp
Код
C++ (Qt)
#include "circle.h"
#include <cmath>
 
Circle::Circle()
{
}
 
double Circle::calcPerimeter(double radius)
{
   mPerimeter = 2.0 * M_PI * radius;
   return mPerimeter;
}
 
double Circle::calcArea(double radius)
{
   mArea = M_PI * radius * radius;
   return mArea;
}
 

rectangle.h
Код
C++ (Qt)
#ifndef RECTANGLE_H
#define RECTANGLE_H
 
#include "shape.h"
 
class Rectangle : public Shape
{
public:
   Rectangle();
   virtual double calcPerimeter(double a, double b);
   virtual double calcArea(double a, double b);
};
 
#endif // RECTANGLE_H
 

rectangle.cpp
Код
C++ (Qt)
#include "rectangle.h"
 
Rectangle::Rectangle()
{
}
 
double Rectangle::calcPerimeter(double a, double b)
{
   mPerimeter = 2.0 * (a + b);
   return mPerimeter;
}
 
double Rectangle::calcArea(double a, double b)
{
   mArea = a * b;
   return mArea;
}
 


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 20, 2014, 17:10
Помогите разобраться со следующими ошибками:

Цитировать
D:\Documents\Qt\QtOOP\0004_Shapes\Shapes\main.cpp:19: error: cannot allocate an object of abstract type 'Circle'
     Shape *pc = new Circle();
                                      ^
D:\Documents\Qt\QtOOP\0004_Shapes\Shapes\main.cpp:22: error: no matching function for call to 'Shape::calcPerimeter(double)'
     pc->calcPerimeter(2.5);
                                    ^

Все описано в сообщениях.
Класс Circle остался виртуальным, поэтому объект этого класса нельзя создать. Что бы он перестал быть виртуальным, нужно в наследуемом классе переопределить calcPerimeter и calcArea так как они определены в базовом классе.
Вторая ошибка как раз из-за того, что в базовом классе методы определены без параметров, а в наследниках с параметрами.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 20, 2014, 17:50
Внимание: теперь класс Rational уже не может быть использован без Qt.

Я кажется понял, что вот так надо:

rational.h
Код
C++ (Qt)
   template <typename stream_type>
   friend stream_type& operator<<(stream_type& stream, const Rational& r);
 

rational.cpp
Код
C++ (Qt)
template <typename stream_type>
stream_type &operator<<(stream_type &stream, const Rational &r)
{
   stream << r.mNumerator << "/" << r.mDenominator;
   return stream;
}
 

Подскажите, пожалуйста, как в main.cpp написать, чтобы работало:

Код
C++ (Qt)
#include <QCoreApplication>
#include <QTextStream>
#include "rational.h"
 
QTextStream cin(stdin);
QTextStream cout(stdout);
 
int main(int argc, char *argv[])
{
   QCoreApplication app(argc, argv);
 
   Rational a(2, 3);
   cout << "a = " << a << endl;
   cout.flush();
 
   return app.exec();
}
 


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 20, 2014, 17:57
Класс Circle остался виртуальным, поэтому объект этого класса нельзя создать. Что бы он перестал быть виртуальным, нужно в наследуемом классе переопределить calcPerimeter и calcArea так как они определены в базовом классе.
Вторая ошибка как раз из-за того, что в базовом классе методы определены без параметров, а в наследниках с параметрами.

У меня для Circle один параметр, а для Rectangle - два. Как быть?


Название: Re: Как писать ООП программы?
Отправлено: Johnik от Февраль 20, 2014, 23:16
У меня для Circle один параметр, а для Rectangle - два. Как быть?
Параметры объекта передавать в конструкторе.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 21, 2014, 08:38
Параметры объекта передавать в конструкторе.

Спасибо за ответ! Но идея, которую я хочу реализовать заключается в том, чтобы от абстрактного класса унаследовать интерфейс:

Код
C++ (Qt)
#ifndef SHAPE_H
#define SHAPE_H
 
class Shape
{
   // ...
   virtual void calcPerimeter() = 0;
   virtual void calcArea() = 0;
   // ...
};
 

А пользователю предоставить возможность создавать свои классы (унаследованные от моего) с требуемым ему набором параметром для calcPerimeter() и calcArea()

circle.h
Код
C++ (Qt)
#ifndef CIRCLE_H
#define CIRCLE_H
 
#include "shape.h"
 
class Circle : public Shape
{
public:
   Circle();
   double calcPerimeter(double radius);
   double calcArea(double radius);
};
 
#endif // CIRCLE_H
 

rectangle.h
Код
C++ (Qt)
#ifndef RECTANGLE_H
#define RECTANGLE_H
 
#include "shape.h"
 
class Rectangle : public Shape
{
public:
   Rectangle();
   double calcPerimeter(double a, double b);
   double calcArea(double a, double b);
};
 
#endif // RECTANGLE_H
 


Название: Re: Как писать ООП программы?
Отправлено: Johnik от Февраль 21, 2014, 10:17
что мешает сделать так:
Код
C++ (Qt)
#ifndef CIRCLE_H
#define CIRCLE_H
 
#include "shape.h"
 
class Circle : public Shape
{
public:
   Circle(double radius);
   double calcPerimeter();
   double calcArea();
};
 
#endif // CIRCLE_H
 
Код
C++ (Qt)
#ifndef RECTANGLE_H
#define RECTANGLE_H
 
#include "shape.h"
 
class Rectangle : public Shape
{
public:
   Rectangle(double a, double b);
   double calcPerimeter();
   double calcArea();
};
 
#endif // RECTANGLE_H
 



Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 21, 2014, 10:21
Спасибо! Я понял Вашу идею. А моя, похоже, в данном случае, неприменима.


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 21, 2014, 11:00
rational.cpp
Код
C++ (Qt)
template <typename stream_type>
stream_type &operator<<(stream_type &stream, const Rational &r)
{
   stream << r.mNumerator << "/" << r.mDenominator;
   return stream;
}
 
Когда этот оператор будет использован из др cpp (не rational.cpp)- получите ошибку линковки, т.к. template должен "находиться в той же единице трансляции", перенесите тело в конец rational.h


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 21, 2014, 11:10
Код
C++ (Qt)
class Rectangle : public Shape
{
public:
   Rectangle();
   double calcPerimeter(double a, double b);
   double calcArea(double a, double b);
};
 
Здесь Вы упустили мааааленькую деталь, но без нее - говнокод, с ней - нормально. Какая это деталь?

По поводу аргументов a, b (не имеют отношения к детали выше). Класс знает все для вычисления периметра и площади, поэтому в любом случае должны быть эти методы без всяких аргументов. Возможно Вы хотели иметь возможность вычислять их без создания класса - тогда так
Код
C++ (Qt)
public:
   Rectangle();
   static double calcPerimeter(double a, double b);
   static double calcArea(double a, double b);
 
Это может быть полезным (хоть и не полиморфным)


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 21, 2014, 15:09
Когда этот оператор будет использован из др cpp (не rational.cpp)- получите ошибку линковки, т.к. template должен "находиться в той же единице трансляции", перенесите тело в конец rational.h

Большое спасибо! Теперь работает! Получается, что все методы с template должны быть в заголовочном файле. Я про это когда-то читал, но забыл, потому что на практике ни разу не попробовал.

main.cpp
Код
C++ (Qt)
#include <QCoreApplication>
#include <QTextStream>
#include "rational.h"
 
QTextStream cin(stdin);
QTextStream cout(stdout);
 
int main(int argc, char *argv[])
{
   QCoreApplication app(argc, argv);
 
   Rational a(2, 3); // 2/3
   cout << "a = " << a << endl;
   cout.flush();
 
   return app.exec();
}
 

rational.h
Код
C++ (Qt)
#ifndef RATIONAL_H
#define RATIONAL_H
 
namespace RationalStuff {
class Rational;
}
 
class Rational
{
public:
   Rational(int numerator = 0, int denominator = 1);
 
   int numerator() const;
   void setNumerator(int numerator);
 
   int denominator() const;
   void setDenominator(int denominator);
 
   friend const Rational operator*(const Rational& lhs, const Rational& rhs);
   friend const Rational operator+(const Rational& lhs, const Rational& rhs);
   friend const Rational operator-(const Rational& lhs, const Rational& rhs);
   template <typename stream_type>
   friend stream_type& operator<<(stream_type& stream, const Rational& r);
 
private:
   int mNumerator;
   int mDenominator;
};
 
template <typename stream_type>
stream_type &operator<<(stream_type &stream, const Rational &r)
{
   stream << r.mNumerator << "/" << r.mDenominator;
   return stream;
}
 
#endif // RATIONAL_H
 

rational.cpp
Код
C++ (Qt)
#include "rational.h"
 
Rational::Rational(int numerator, int denominator) :
   mNumerator(numerator),
   mDenominator(denominator)
{
}
 
int Rational::numerator() const
{
   return mNumerator;
}
 
void Rational::setNumerator(int numerator)
{
   mNumerator = numerator;
}
 
int Rational::denominator() const
{
   return mDenominator;
}
 
void Rational::setDenominator(int denominator)
{
   mDenominator = denominator;
}
 
const Rational operator*(const Rational &lhs, const Rational &rhs)
{
   return Rational(lhs.mNumerator * rhs.mNumerator,
                   lhs.mDenominator * rhs.mDenominator);
}
 
const Rational operator+(const Rational &lhs, const Rational &rhs)
{
   Rational result;
 
   // Общий знаменатель
   int commonDenominator = lhs.denominator() * rhs.denominator();
 
   // Числитель
   int a = lhs.numerator() * rhs.denominator();
   int b = rhs.numerator() * lhs.denominator();
   result.setNumerator(a + b);
 
   // Знаменатель
   result.setDenominator(commonDenominator);
 
   return result;
}
 
const Rational operator-(const Rational &lhs, const Rational &rhs)
{
   Rational result;
 
   // Общий знаменатель
   int commonDenominator = lhs.denominator() * rhs.denominator();
 
   // Числитель
   int a = lhs.numerator() * rhs.denominator();
   int b = rhs.numerator() * lhs.denominator();
   result.setNumerator(a - b);
 
   // Знаменатель
   result.setDenominator(commonDenominator);
 
   return result;
}
 


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 21, 2014, 15:24
Здесь Вы упустили мааааленькую деталь, но без нее - говнокод, с ней - нормально. Какая это деталь?

По поводу детали пока не могу ответить. Думаю.

Возможно Вы хотели иметь возможность вычислять их без создания класса

Спасибо! Как вариант. Да, но тогда выпадает необходимость в базовом абстрактном классе. Хотелось бы, чтобы абстрактный класс был более полезен и указывал разработчикам производных классов, что нужно переопределить функции для рассчёта периметра и площади.

По поводу аргументов a, b (не имеют отношения к детали выше). Класс знает все для вычисления периметра и площади, поэтому в любом случае должны быть эти методы без всяких аргументов.

Большое спасибо! Я теперь так и сделал. Посмотрите, пожалуйста, в моём коде сейчас есть ли какие-то грубые недочёты:

main.cpp
Код
C++ (Qt)
#include <QCoreApplication>
#include <QTextStream>
#include "shape.h"
#include "circle.h"
#include "rectangle.h"
 
QTextStream cin(stdin);
QTextStream cout(stdout);
 
void printShapeInfo(Shape *ps) {
   cout << "p = " << ps->perimeter() << endl;
   cout.flush();
   cout << "s = " << ps->area() << endl;
   cout.flush();
}
 
int main(int argc, char *argv[])
{
   QCoreApplication a(argc, argv);
 
   Shape *pc = new Circle(3.0);
   pc->calcPerimeter();
   pc->calcArea();
   printShapeInfo(pc);
   delete pc;
 
   Shape *pr = new Rectangle(5, 10);
   pr->calcPerimeter();
   pr->calcArea();
   printShapeInfo(pr);
   delete pr;
 
   return a.exec();
}
 

shape.h
Код
C++ (Qt)
#ifndef SHAPE_H
#define SHAPE_H
 
class Shape
{
public:
   Shape();
   virtual ~Shape();
 
   virtual double calcPerimeter() = 0;
   virtual double calcArea() = 0;
 
   double perimeter() const;
   double area() const;
 
protected:
   double mPerimeter;
   double mArea;
};
 
#endif // SHAPE_H
 

shape.cpp
Код
C++ (Qt)
#include "shape.h"
 
Shape::Shape() :
   mPerimeter(0.0),
   mArea(0.0)
{
}
 
Shape::~Shape()
{
}
 
double Shape::perimeter() const
{
   return mPerimeter;
}
 
double Shape::area() const
{
   return mArea;
}
 

circle.h
Код
C++ (Qt)
#ifndef CIRCLE_H
#define CIRCLE_H
 
#include "shape.h"
 
class Circle : public Shape
{
public:
   Circle(double r = 0.0);
 
   /*virtual*/ double calcPerimeter();
   /*virtual*/ double calcArea();
 
   void setRadius(double r);
   double radius() const;
 
private:
   double mRadius;
};
 
#endif // CIRCLE_H
 

circle.cpp
Код
C++ (Qt)
#include "circle.h"
#include <cmath>
 
Circle::Circle(double r) :
   mRadius(r)
{
}
 
/*virtual*/ double Circle::calcPerimeter()
{
   mPerimeter = 2.0 * M_PI * mRadius;
   return mPerimeter;
}
 
/*virtual*/ double Circle::calcArea()
{
   mArea = M_PI * mRadius * mRadius;
   return mArea;
}
 
void Circle::setRadius(double r)
{
   mRadius = r;
}
 
double Circle::radius() const
{
   return mRadius;
}
 

rectangle.h
Код
C++ (Qt)
#ifndef RECTANGLE_H
#define RECTANGLE_H
 
#include "shape.h"
 
class Rectangle : public Shape
{
public:
   Rectangle(double l = 0.0, double w = 0.0);
 
   /*virtual*/ double calcPerimeter();
   /*virtual*/ double calcArea();
 
   void setLength(double l);
   double length() const;
 
   void setWidth(double w);
   double width() const;
 
private:
   double mLength;
   double mWidth;
};
 
#endif // RECTANGLE_H
 

rectangle.cpp
Код
C++ (Qt)
#include "rectangle.h"
 
Rectangle::Rectangle(double l, double w) :
   mLength(l),
   mWidth(w)
{
}
 
/*virtual*/ double Rectangle::calcPerimeter()
{
   mPerimeter = 2.0 * (mLength + mWidth);
   return mPerimeter;
}
 
/*virtual*/ double Rectangle::calcArea()
{
   mArea = mLength * mWidth;
   return mArea;
}
 
void Rectangle::setLength(double l)
{
   mLength = l;
}
 
double Rectangle::length() const
{
   return mLength;
}
 
void Rectangle::setWidth(double w)
{
   mWidth = w;
}
 
double Rectangle::width() const
{
   return mWidth;
}
 

P.S. Узнал, что если у базового класса невиртуальный деструктор, то нельзя будет уничтожить полностью объект производного класса через указатель на базовый (как у меня это сделано в main.cpp), так как не будет вызван деструктор производного класса, таким образом будет утечка памяти. Вот здесь прочитал: "Правило 7: Объявляйте деструкторы виртуальными в полиморфном базовом классе" http://www.e-reading.co.uk/chapter.php/1002058/26/Mayers_-_Effektivnoe_ispolzovanie_CPP.html


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 21, 2014, 16:25
По поводу детали пока не могу ответить. Думаю.
Ладно, не буду мучить - Вы неск раз упустили const, с точки зрения "профи" это грубая, непростительная ошибка. Очень важно дать понять модифицирует ли метод объект или нет.

По поводу периметра и площади. Ну вводить их как члены данных в базовый класс совершенно незачем. С фигурами типа Сircle, Rectangle хранить их тоже нет особого смысла т.к. их вычисление "на лету" очень быстро. Но вот появляется более сложная фигура Polygon (замкнутый контур из N вершин, причем N может быть приличным) . Вот для него уже кешировать вычисление периметра есть смысл, а площади - тем более. Развейте эту идею

Также напрашиваются общие методы:
BoundingRect() // вмещающий пр-к
IsConvex()  // выпуклая фигура или нет
Scale()

Да, и для пр-ка традиционно "Height" (а не "Length")


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 21, 2014, 20:52
Ладно, не буду мучить - Вы неск раз упустили const, с точки зрения "профи" это грубая, непростительная ошибка. Очень важно дать понять модифицирует ли метод объект или нет.

Спасибо большое! Я запомню, что если метод не модифицирует члены-данные объекта, то нужно писать const и что объекты нужно передавать в методы через ссылку на константу, к примеру: void setName(const QString& name), тогда мы гарантируем, что не испортим объект, который нам доверили по ссылке.

В моём настоящем коде члены-данные объекта модифицируются, поэтому не const:

Код
C++ (Qt)
/*virtual*/ double Rectangle::calcPerimeter()
{
   mPerimeter = 2.0 * (mHeight + mWidth);
   return mPerimeter;
}
 
/*virtual*/ double Rectangle::calcArea()
{
   mArea = mHeight * mWidth;
   return mArea;
}
 

Да, и для пр-ка традиционно "Height" (а не "Length")

Спасибо! Заменил.

Также напрашиваются общие методы:
BoundingRect() // вмещающий пр-к
IsConvex()  // выпуклая фигура или нет
Scale()

Пока это сложно. Не понимаю, к примеру, что такое Scale() (вроде масштабирование). Тут уже чувствуется визуализация (вывод на экран и OpenGL) и реализация draw(). Сейчас это направление развития для меня - неприступная крепость.

Но вот появляется более сложная фигура Polygon (замкнутый контур из N вершин, причем N может быть приличным) . Вот для него уже кешировать вычисление периметра есть смысл, а площади - тем более. Развейте эту идею

Про полигон подумаю. Спасибо за идею!


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 21, 2014, 21:07
В моём настоящем коде члены-данные объекта модифицируются, поэтому не const:
А для чего вы сделали их членами класса?


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 21, 2014, 21:36
В моём настоящем коде члены-данные объекта модифицируются, поэтому не const:
А для чего вы сделали их членами класса?

Потому что периметр и площадь - это общие параметры для всех геометрических фигур на плоскости :)


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 21, 2014, 21:48
Потому что периметр и площадь - это общие параметры для всех геометрических фигур на плоскости :)
Которые высчитываются при каждом вызове метода, для чего вы их храните в членах-данных?


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 21, 2014, 22:07
Которые высчитываются при каждом вызове метода, для чего вы их храните в членах-данных?

Да, но по моей схеме вызвать методы для расчёта нужно только один раз (а потом мы можем узнавать периметр и площадь с помощью методов: perimeter() и area(), которые расположены в базовом классе). Если меняем, к примеру, радиус, то нужно опять вызвать методы. Может конечно быть ситуация, что, пользователь изменит радиус и забудет вызвать функции для расчёта периметра и площади. Тогда может лучше вызов методов ещё добавить и в функцию setRadius() ?


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 21, 2014, 22:34
Не должно быть в интерфейсе неоднозначностей.
Если я вызову area до calcArea, он мне вернет неправильный результат.
Т.е. я должен знать и помнить, что перед получением значения я должен его вычислить с помощью другого метода. Для чего?
Пусть area и считает сразу.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 22, 2014, 07:59
Пусть area и считает сразу.

Да, это отличный вариант, но посмотрите на мой абстрактный класс Shape. Что от него останется, если я последую Вашему совету? Зачем он тогда нужен?

Код
C++ (Qt)
#ifndef SHAPE_H
#define SHAPE_H
 
class Shape
{
public:
   Shape();
   virtual ~Shape();
 
   virtual double calcPerimeter() = 0;
   virtual double calcArea() = 0;
 
   double perimeter() const;
   double area() const;
 
protected:
   double mPerimeter;
   double mArea;
};
 
#endif // SHAPE_H
 


Название: Re: Как писать ООП программы?
Отправлено: Bepec от Февраль 22, 2014, 10:35
Чтобы пользователь мог его взять и пользоваться к примеру :)


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 22, 2014, 10:39
Old, смотрите что стало из-за Вас с моим "грандиозным" проектом! Shape теперь не нужен. От всего проекта осталось два малюсеньких файла (не считая демонстрацию в main.cpp). Весь мой полиморфный мир разрушен :(

Получается, что функции для расчёта периметра и площади плохие кандидаты для применения полиморфизма? Для чего класс Shape можно применить?

Output:
Цитировать
Circle:
Radius = 5.5
Perimeter = 34.5575
Area = 95.0332

Rectangle:
Height = 5
Width = 10
Perimeter = 30
Area = 50

main.cpp
Код
C++ (Qt)
#include <QCoreApplication>
#include <QTextStream>
#include "circle.h"
#include "rectangle.h"
 
QTextStream cin(stdin);
QTextStream cout(stdout);
 
int main(int argc, char *argv[])
{
   QCoreApplication a(argc, argv);
 
   Circle *pc = new Circle;
   double radius = 5.5;
   cout << endl;
   cout << "Circle:" << endl;
   cout << "Radius = " << radius << endl;
   cout << "Perimeter = " << pc->perimeter(radius) << endl;
   cout << "Area = " << pc->area(radius) << endl;
   cout.flush();
   delete pc;
 
   Rectangle *pr = new Rectangle;
   double height = 5.0;
   double width = 10.0;
   cout << endl;
   cout << "Rectangle:" << endl;
   cout << "Height = " << height << endl;
   cout << "Width = " << width << endl;
   cout << "Perimeter = " << pr->perimeter(height, width) << endl;
   cout << "Area = " << pr->area(height, width) << endl;
   cout.flush();
   delete pr;
 
   return a.exec();
}
 

circle.h
Код
C++ (Qt)
#ifndef CIRCLE_H
#define CIRCLE_H
 
#include <cmath>
 
class Circle
{
public:
   inline double perimeter(double radius) const {
       return (2.0 * M_PI * radius);
   }
 
   inline double area(double radius) const {
       return (M_PI * radius * radius);
   }
};
 
#endif // CIRCLE_H
 

rectangle.h
Код
C++ (Qt)
#ifndef RECTANGLE_H
#define RECTANGLE_H
 
class Rectangle
{
public:
   inline double perimeter(double height, double width) const {
       return 2.0 * (height + width);
   }
 
   inline double area(double height, double width) const {
       return height * width;
   }
};
 
#endif // RECTANGLE_H
 


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 22, 2014, 11:04
объекты нужно передавать в методы через ссылку на константу, к примеру: void setName(const QString& name), тогда мы гарантируем, что не испортим объект, который нам доверили по ссылке.
Не только. Константная ссылка разрешает создание временного объекта, напр
Код
C++ (Qt)
setName("test");
Здесь будет создан временный (безымянный) объект QString, он будет подан в ф-цию и удален после того как ф-ция вернет управления. А если аргумент - не константная ссылка - так нельзя.

Конечно это "совсем просто", но вещь базовая и ее надо изучить очень тщательно


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 22, 2014, 11:15
Не только. Константная ссылка разрешает создание временного объекта, напр
Код
C++ (Qt)
setName("test");
Здесь будет создан временный (безымянный) объект QString, он будет подан в ф-цию и удален после того как ф-ция вернет управления. А если аргумент - не константная ссылка - так нельзя.
Конечно это "совсем просто", но вещь базовая и ее надо изучить очень тщательно

Спасибо! Попытаюсь понять на примере. Почему он мне выдаёт сообщение:

Цитировать
main.cpp:19: error: no matching function for call to 'Person::Person(const char [5])'
     Person ivan("Ivan");
                             ^

main.cpp
Код
C++ (Qt)
#include <QCoreApplication>
#include <QTextStream>
 
QTextStream cin(stdin);
QTextStream cout(stdout);
 
class Person {
public:
   void setName(const QString& name) {
       mName = name;
   }
 
   QString name() const {
       return mName;
   }
private:
   QString mName;
};
 
int main(int argc, char *argv[])
{
   QCoreApplication a(argc, argv);
 
   Person ivan("Ivan");
 
   return a.exec();
}
 


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 22, 2014, 11:20
Old, смотрите что стало из-за Вас с моим "грандиозным" проектом! Shape теперь не нужен. От всего проекта осталось два малюсеньких файла (не считая демонстрацию в main.cpp). Весь мой полиморфный мир разрушен :(

Получается, что функции для расчёта периметра и площади плохие кандидаты для применения полиморфизма? Для чего класс Shape можно применить?
Напротив, они отличные кандидаты для полиморфизма.
Код
C++ (Qt)
class Circle
{
public:
   inline double perimeter(double radius) const {
       return (2.0 * M_PI * radius);
Такое вычисление имеет смысл, но оно никак не зависит от самого класса, его членов. Значит это static метод. Но так Вам надо иметь радиус "на стороне". Хорошо увязать это вместе, напр
Код
C++ (Qt)
class Circle : public Shape
{
public:
...
   static double perimeter(double radius)
  {
       return (2.0 * M_PI * radius);
  }
 
  virtual double perimeter( void ) const
  {
     return perimeter(radius);
  }
 
private:  
 double m_radius;  
 


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 22, 2014, 11:24
Спасибо! Попытаюсь понять на примере. Почему он мне выдаёт сообщение:
А разве у Вас есть конструктор принимающий QString? И зачем метод name возвращает QString по значению? Для этого нет никаких оснований, правильно вернуть константную ссылку


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 22, 2014, 11:47
Old, смотрите что стало из-за Вас с моим "грандиозным" проектом! Shape теперь не нужен. От всего проекта осталось два малюсеньких файла (не считая демонстрацию в main.cpp). Весь мой полиморфный мир разрушен :(
Почему? Полиморфизм можно оставить. :)
Смотрите:
Код
C++ (Qt)
class Shape
{
public:
   virtual ~Shape() {}
 
   virtual double perimeter() const = 0;
   virtual double area() const = 0;
};
 

Код
C++ (Qt)
class Circle : public Shape
{
public:
   explicit Circle( double radius ) : m_radius( radius )
   {
   }
 
   double perimeter() const {
       return (2.0 * M_PI * m_radius);
   }
 
   double area() const {
       return (M_PI * m_radius * m_radius);
   }
 
private:
    double m_radius;
};
 

Тогда можно будет считать perimeter и area для любой фигуры, не задумываясь какая она конкретно.



Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 22, 2014, 12:26
Общее замечание - излишнее упрощение. Не хочется возиться с полигоном - ведь это якобы "не имеет отношения к С++". Формально так, на деле - нет. Будет больше ф-ционала - будет и больше архитектуры, проблем что нужно решать. А месить простейшие круг и пр-к - да тут и классы-то (по большому счету) не нужны. И вообще, культурный человек должен уметь подсчитать площадь полигона  :)   


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 22, 2014, 18:31
Большое спасибо за ответы! Я их постепенно буду разбирать :)

А разве у Вас есть конструктор принимающий QString?

Большое спасибо! Это я поторопился. Добавил конструктор принимающий const QString&.

И зачем метод name возвращает QString по значению? Для этого нет никаких оснований, правильно вернуть константную ссылку

Да, точно! Ведь переменная mName будет всегда и это не тот случай, когда мы не можем возвращать ссылку (то есть, в нашем случае, объект, на который указывает ссылка, не будет уничтожен): http://www.e-reading.co.uk/chapter.php/1002058/56/Mayers_-_Effektivnoe_ispolzovanie_CPP.html

Вот нужно будет мне ещё понять, почему возвращать именно константную ссылку... А если она будет неконстантная?

Вот, что я понял. Получается, что если мы вызываем метод вот так:

Код:
ivan.setName("Ivan");

то конструируется новый временный объект, который передаётся в функцию через ссылку, поэтому не вызывается конструктор копирования и это быстрее по сравнению с передачей по значению. Хотя мы врядли передадим большое имя, но лучше приучаться сразу передавать через константную ссылку. Кстати, я пробовал писать без const, вот так: void setName(QString& name) выдаёт:

Цитировать
main.cpp:31: error: no matching function for call to 'Person::setName(const char [5])'
     ivan.setName("Ivan");
                        ^

А если так: void setName(const QString& name), то нормально:

main.cpp
Код
C++ (Qt)
#include <QCoreApplication>
#include <QTextStream>
 
QTextStream cin(stdin);
QTextStream cout(stdout);
 
class Person {
public:
   Person() {}
 
   Person(const QString& name) {
       mName = name;
   }
 
   void setName(const QString& name) {
       mName = name;
   }
 
   const QString& name() const {
       return mName;
   }
private:
   QString mName;
};
 
int main(int argc, char *argv[])
{
   QCoreApplication a(argc, argv);
 
   Person ivan;
   ivan.setName("Ivan");
 
   cout << ivan.name() << endl;
   cout.flush();
 
   return a.exec();
}
 


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 22, 2014, 18:55
А если так: void setName(const QString& name), то нормально:
..
Код
C++ (Qt)
   Person(const QString& name) {
       mName = name;
   }
 
Не совсем, это конструктор, правильно (во всяком случае точнее) использовать инициализацию
Код
C++ (Qt)
Person(const QString& name) : mName(name) {}
 

По поводу "изменилась ссылка", "быстрее" и.т.п. Не в этом дело. Пример: пусть есть ф-ция
Код
C++ (Qt)
QRect GetRect( ... some args..)
В чем разница между
Код
C++ (Qt)
QRect R = GetRect(...);  // вариант 1
 
и
Код
C++ (Qt)
QRect R;
R = GetRect(...);  // вариант 2
 
???


Название: Re: Как писать ООП программы?
Отправлено: Hrundel от Февраль 23, 2014, 02:39
В ООП нужно рассуждать структурно. Одна из лучших структур наиболее ярко демонстрирурующая ООП - это дерево. Любое дерево. Так например, классификация животного мира.

Базовый класс от которого происходят все животные и у них У ВСЕХ есть свойcтва базового класса = животное

У животного есть одно и очень важное свойсто - оно живое.
Раз оно живое значит у него есть как минимум три состояния.
1. рождение или появление на свет
2. жизнь
3. умирание или смерть.

Код
C++ (Qt)
class Animal
{
public:
      Animal():alive(false){}
      ~Animal(){}
 
      bool isAlive(){return alive;}  // жив ли ?
      void birth(){alive = true;}    // рождение
      void death(){alive = false;} // смерть
 
private:
      bool alive;
}
 

От этого класса наследуются царства хордовые и безпозвоночные

Код
C++ (Qt)
class Vertebrates : public Animal
{
public:
       Vertebrates(int input)vertebras(inpupt), spine(ture){}
       ~Vertebrates(){}
 
       int getCountOfVertebras(){return vertebras;} // получить информацию о кол-ве позвонков
       bool hasSpine(){return spine;}
private:
       int vertebras;
       bool spine;
}
 
От царства безпозвоночных наследуются Черви и Насекомые
От царства хордовых Рыбы, Амфибии, Птицы и Млекопитающие
От млекопетающих звери и еще пара *(уже не помню)
От зверей сумчатые, плацентарные и еще один *(опять не помню)

и так далее до какой-нибудь мыши полевки обыкновенной.

При этом у каждого нового класса в этом дереве есть свои новые функции или особенности.
Но каждый из них наследует все свойства классов от которых происходит.

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

Для того, чтобы составить такую сложную иерархию - нужен план. Для составления такого плана используется UML. Иначе потеряешь обозримость проекта. Разрабы используют или Entrprice Architekt или Visual Paradigm. Я предпочитаю второе. Самое приятное в этих программах то, что после составления очень сложной и увесистой модели можно сгенерировать код в C++. На Visual Paradigm код получается рабочий.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 23, 2014, 14:44
В ООП нужно рассуждать структурно. Одна из лучших структур наиболее ярко демонстрирурующая ООП - это дерево. Любое дерево. Так например, классификация животного мира.

Спасибо за идею с животными! Интересно поразвивать будет. Кстати, у Гради Буч вся книга посвящена таким деревьям (такой подход применим во всех областях науки, человеческой жизни, природы, игр и т.д.). Я введение прочитал. Хорошо, что напомнили. Нужно её проработать до конца.

На всякий случай напоминаю:
Перевод: http://rutracker.org/forum/viewtopic.php?t=3343958
Оригинал: http://kickass.to/object-oriented-analysis-and-design-with-applications-3rd-editio-t2753820.html

Каждый из классов может иметь малюсенькую функцию, поэтому не обязательно каждый класс прописывать в отдельном файле. Их можно все писать в одном заголовочном файле.
Вот это пока неочевидно. Примеров никаких не видел и не придумывал. Я заметил, что когда делаешь что-то, то обычно первый раз делаешь неправильно, а потом, когда видишь, как правильно, и переделываешь своё под правильное, то запоминается гораздо лучше. Несколько классов в одном файле, это пока для меня непостижимо.

Для того, чтобы составить такую сложную иерархию - нужен план. Для составления такого плана используется UML. Иначе потеряешь обозримость проекта. Разрабы используют или Entrprice Architekt или Visual Paradigm. Я предпочитаю второе. Самое приятное в этих программах то, что после составления очень сложной и увесистой модели можно сгенерировать код в C++. На Visual Paradigm код получается рабочий.
Большое спасибо! Вот такие инструменты мне обязательно нужно освоить :)

По поводу "изменилась ссылка", "быстрее" и.т.п. Не в этом дело. Пример: пусть есть ф-ция
Спасибо! Чуть позже доберусь до этого. Пока не подсказывайте. Сам подумаю :)

Я восстановил полиморфизм и добавил возможность считать периметр и площадь через статические функции.

Примечание. Упростил себе работу с несколькими ветками развития проекта. Из консоли (из под Far'а) переключаю ветки в git и Qt Creator моментально перегружает проект. Пошагово описал здесь: http://www.prog.org.ru/topic_26393_0.html

Теперь мы можешь установить радиус (с помощью функции setRadius(double radius)), потом вызвать полиморфные функции perimeter() и area().

Я понял свою ошибку, когда писал calcPerimeter() и calcArea(). Я боялся, что производить расчёт при каждом вызове - это плохой стиль. Я думал, что нужно один раз посчитать, а пересчитывать только при смене параметров. Периметр и площадь считаются быстро. Но вот если у нас будет сложная фигура (я унаследую её от Shape). Вот создам я новый класс Полигон (многоугольник) и появятся проблемы с пересчётом. Сейчас прорабатываю эту идею.

Теперь приходит понимание, что хорошо спроектированная система будет лучше реагировать на изменения требований. Меня напрягает, что при смене требований мне нужно переписывать всю систему целиком. Хорошо, что пока система не очень большая (мягко говоря). Надеюсь, с опытом будет приходить понимание и скорость.

Old, я видел в Вашем коде (из ответа #58) ключевое слово explicit перед конструктором. Я думаю, что это связано с приведением типов. Сейчас изучаю это провило: "Правило 27: Не злоупотребляйте приведением типов" http://www.e-reading.co.uk/chapter.php/1002058/69/Mayers_-_Effektivnoe_ispolzovanie_CPP.html

Остановился на таком варианте. Пожалуйста, укажите на грубые недочёты, если они есть.

Output
Цитировать
static perimeter: 18.8496
p = 18.8496
s = 28.2743
p = 30
s = 50

main.cpp
Код
C++ (Qt)
#include <QCoreApplication>
#include <QTextStream>
#include "shape.h"
#include "circle.h"
#include "rectangle.h"
 
QTextStream cin(stdin);
QTextStream cout(stdout);
 
void printShapeInfo(Shape *ps) {
   cout << "p = " << ps->perimeter() << endl;
   cout.flush();
   cout << "s = " << ps->area() << endl;
   cout.flush();
}
 
int main(int argc, char *argv[])
{
   QCoreApplication a(argc, argv);
 
   Shape *pc = new Circle(3.0);
   cout << "static perimeter: " << Circle::perimeter(3.0) << endl;
   printShapeInfo(pc);
   delete pc;
 
   Shape *pr = new Rectangle(5, 10);
   printShapeInfo(pr);
   delete pr;
 
   return a.exec();
}
 

shape.h
Код
C++ (Qt)
#ifndef SHAPE_H
#define SHAPE_H
 
class Shape
{
public:
 
   Shape() {}
   virtual ~Shape() {
 
   }
 
   virtual double perimeter() const = 0;
   virtual double area() const = 0;
};
 
#endif // SHAPE_H
 

circle.h
Код
C++ (Qt)
#ifndef CIRCLE_H
#define CIRCLE_H
 
#include "shape.h"
#include <cmath>
 
class Circle : public Shape
{
public:
   Circle(double radius = 0.0) : m_radius(radius) {
   }
 
   static double perimeter(double radius) {
       return (2.0 * M_PI * radius);
   }
 
   static double area(double radius) {
       return (M_PI * radius * radius);
   }
 
   virtual double perimeter() const {
       return perimeter(m_radius);
   }
 
   virtual double area() const {
       return area(m_radius);
   }
 
   inline double radius() const {
       return m_radius;
   }
 
   inline void setRadius(double radius) {
       m_radius = radius;
   }
 
private:
   double m_radius;
};
 
#endif // CIRCLE_H
 

rectangle.h
Код
C++ (Qt)
#ifndef RECTANGLE_H
#define RECTANGLE_H
 
#include "shape.h"
 
class Rectangle : public Shape
{
public:
 
   Rectangle(double height = 0.0, double width = 0.0) :
       m_height(height),
       m_width(width)
   {
   }
 
   static double perimeter(double height, double width) {
       return 2.0 * (height + width);
   }
 
   static double area(double height, double width) {
       return height * width;
   }
 
   virtual double perimeter() const {
       return perimeter(m_height, m_width);
   }
 
   virtual double area() const {
       return area(m_height, m_width);
   }
 
   inline double height() const {
       return m_height;
   }
 
   inline double width() const {
       return m_width;
   }
 
   inline void setHeight(double height) {
       m_height = height;
   }
 
   inline void setWidth(double width) {
       m_width = width;
   }
 
private:
   double m_height;
   double m_width;
};
 
#endif // RECTANGLE_H
 


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 23, 2014, 14:49
Скажите, пожалуйста, а реально ли реализовать функцию draw() для моих классов через OpenGL? Это очень сложно?


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 23, 2014, 15:28
Скажите, пожалуйста, а реально ли реализовать функцию draw() для моих классов через OpenGL? Это очень сложно?
Реально и не сложно, только эта тема уже будет не ООП. :)


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 23, 2014, 15:53
Периметр и площадь считаются быстро. Но вот если у нас будет сложная фигура (я унаследую её от Shape). Вот создам я новый класс Полигон (многоугольник) и появятся проблемы с пересчётом. Сейчас прорабатываю эту идею.
Очень многие решения зависят от того "а что за плечами", т.е. какой код уже написан? Сейчас у Вас "аж ничего", простейшие Circle/Rectangle переделываются за неск минут как угодно. В результате получается довольно легкое "порхание бабочки", в котором нет ничего плохого - но и толку немного. Поэтому-то люди и советуют типа "возьми реальную задачу". Но то легко сказать, а реально - стоит ли на много недель (минимум) углубляться в "нечто" (специфику/матчасть) только для целей обучения? С этой точки зрения полигон - хороший вариант. Он и достаточно сложен (так резво как с  простыми шейпами не выйдет) - и достаточно прост, ну максимум день на освоение простой геометрии. В общем, как говорил один мой заказчик:
Цитировать
Ну подумай-подумай. А я пока чайку нагною
:)

По поводу животных. Красиво, впечатляет. Но в библии есть ответ и на это - ну на то она и библия.


Название: Re: Как писать ООП программы?
Отправлено: Авварон от Февраль 23, 2014, 19:32
А разве у Вас есть конструктор принимающий QString? И зачем метод name возвращает QString по значению? Для этого нет никаких оснований, правильно вернуть константную ссылку

Неправильно. С точки зрения производительности - да, с точки зрения всего остального - нет. Во-первых, теряется гибкость (если было 2 связанных геттера - Document::filePath() и Document::name() (aka QFileInfo(filePath()).fileName()) то что, при кешировании имени менять сигнатуру? нарушается бинарная совместимость, да и код странный - один метод возвращает const QString &, 2й QString). Во-вторых, это небезопасно - может найтись умник, к-ый снимет const со ссылки и возьмет да и поменяет. Не, ну а чо - "квикфикс, сроки горят, потом исправлю").

ЗЫ: Ну то есть если вы юзаете std::string, где корову забанили, то это имеет смысл. Для QString смысла нет.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 24, 2014, 10:10
Скажите, пожалуйста, а реально ли реализовать функцию draw() для моих классов через OpenGL? Это очень сложно?
Реально и не сложно, только эта тема уже будет не ООП. :)

Спасибо! Вы правы. Специфику OpenGL мы здесь обсуждать не будем. Предлагаю взглянуть на рисование фигур с точки зрения ООП (а OpenGL - это просто будет второстепенный инструмент). Объекту какого класса предложить рисование фигур? Мне предлагали сделать функцию draw() полиморфной в Shape, а потом реализацию писать в дочерних классах, потому что "функция знает как себя рисовать" (что-то в этом роде). А я считаю, что нужно завести для рисования специальный класс с методом draw(). В этот метод мы будем передавать константную ссылку на Shape (и ещё смещение по x и y от начала координат, а так же толщину линии) Вот так:

viewer.h
Код
C++ (Qt)
class Viewer : public QGLWidget
{
public:
   // ...
   void draw(const Shape &ps, int xOffset, int yOffset, int pointSize = 2);
   // ...
}
 

Но теперь я в тупике. Как объект класса Viewer отличит круг от прямоугольника? А если будет другая фигура? Может передавать в draw массив координат точек вершин (который будет внутри Shape)? Как вы полагаете?


Название: Re: Как писать ООП программы?
Отправлено: Johnik от Февраль 24, 2014, 10:34
Но теперь я в тупике. Как объект класса Viewer отличит круг от прямоугольника? А если будет другая фигура? Может передавать в draw массив координат точек вершин (который будет внутри Shape)? Как вы полагаете?

Здесь точно та же ситуация как и с std::ostream или QTextStream


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 24, 2014, 11:08
Неправильно. С точки зрения производительности - да, с точки зрения всего остального - нет. Во-первых, теряется гибкость (если было 2 связанных геттера - Document::filePath() и Document::name() (aka QFileInfo(filePath()).fileName()) то что, при кешировании имени менять сигнатуру? нарушается бинарная совместимость, да и код странный - один метод возвращает const QString &, 2й QString). Во-вторых, это небезопасно - может найтись умник, к-ый снимет const со ссылки и возьмет да и поменяет. Не, ну а чо - "квикфикс, сроки горят, потом исправлю").
Не в курсе о каком кешировании Вы говорите.
Код
C++ (Qt)
const QString & filePath1(..) const;
QString filePath2(..) const;
QString & filePath3(..);
 
Это все вещи разные. В первом случае ясно показано что строка - член класса, вызов очень быстр и ничем не чреват. Часто вызывающий все равно копирует строку в переменную - но то уже его дело. А во втором ясно только что класс умеет выдать строку, а как - дело темное. Может просто вернет член, а может и будет как-то вычислять. Возможно этот вызов затратен или вообще будет выдано не то что надо - напр у класса еще не установлены нужные данные. Поэтому возвращать по значению "на всякий случай" не кажется мне хорошим стилем.

Третий вариант в большинстве случаев просто плох - оказывается что геттеры/сеттеры просто игрушки, так, "чтобы по книжке было". А дошло до дела - вот и (слегка) замаскированный "public"  :)


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 24, 2014, 11:19
Мне предлагали сделать функцию draw() полиморфной в Shape, а потом реализацию писать в дочерних классах, потому что "функция знает как себя рисовать" (что-то в этом роде). А я считаю, что нужно завести для рисования специальный класс с методом draw().
Обоснуйте свое решение

В этот метод мы будем передавать константную ссылку на Shape (и ещё смещение по x и y от начала координат, а так же толщину линии) Вот так:
А откуда Вы возьмете смещения? Они явно принадлежат шейпу, его член(ы). Иначе как Вы будете их хранить? Контейнер шейпов + еще (параллельно) контейнер смещений?  :)

Толщина линии. Из той же оперы: цвет, заливать внутренности или нет и.т.п.. Здесь возможны варианты. Вполне нормально если сама шейпа знает свою толщину линии - напр сейчас она "выбрана". Т.е. тоже член. Но также возможно что рисующий хочет какую-то одну толщину  для всех. Как это увязать?


Название: Re: Как писать ООП программы?
Отправлено: Hrundel от Февраль 24, 2014, 11:35
Но теперь я в тупике. Как объект класса Viewer отличит круг от прямоугольника? А если будет другая фигура? Может передавать в draw массив координат точек вершин (который будет внутри Shape)? Как вы полагаете?

Ну с этим-то как раз проще всего - на то и полиморфизм. Мне кажеться ты опять думаешь не в ту сторону. После генерализации используются ТОЛЬКО листья дерева. Это значит, что к руту (корню) Shape тебе уже обращаться не нужно.

Объекты потомки Shape, по логики вещей, не могут существовать без Viewer, иначе их существование становится безсмысленным. Поэтому создаваться они должны для Viewer. То есть внутри этого класса.

Вот, это как раз и есть вопросы которыми в основном занят разработчик архитектуры. Как сделать все логичным, оправданным, понятным, простым и удобным!

Поэтому если ты хочешь рисовать круг, то пишешь

Код
C++ (Qt)
Circle *crcl = new Circle();
crcl->draw(x, y, rad);
 

Мне предлагали сделать функцию draw() полиморфной в Shape, а потом реализацию писать в дочерних классах, потому что "функция знает как себя рисовать" (что-то в этом роде).

А я считаю, что нужно завести для рисования специальный класс с методом draw(). В этот метод мы будем передавать константную ссылку на Shape (и ещё смещение по x и y от начала координат, а так же толщину линии) Вот так:

Думаю, что твой путь не самый простой для реализации, да и по понятности кода, в нем будет меньше логики.

Просто напиши в Shape виртуальную функцию и реализуй для каждого отдельно, так проще, понятнее, логичнее.

И вообще Shape должен содержать все возможные виртуальные функции, которые могут быть в дальнейшем использованы для потомков.
Например:

move(int x, int y), hide(), show(), scale(float u, float v), setFillColor(QColor*), setShapeColor(QColor*)  и все тому подобное. Если оно, кончно, планируется к использованию.


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 24, 2014, 12:44
Объекты потомки Shape, по логики вещей, не могут существовать без Viewer, иначе их существование становится безсмысленным. Поэтому создаваться они должны для Viewer. То есть внутри этого класса.
Не вижу никаких оснований для такого вывода. Напр объекты могут быть загружены из файла и сохранены в др формате - никакого вьюера и близко нет. Или наоборот, могут быть использована неск вьюерами сразу.

Код
C++ (Qt)
Circle *crcl = new Circle();
crcl->draw(x, y, rad);
 
Ну да, а откуда Circle возьмет контекст рисования?


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 24, 2014, 13:21
Это все вещи разные.
Так Авварон вам про это и говорит.
Это настолько разные вещи, что у этих методов разные сигнатуры и при изменении прототипа с первого на второй (или наоборот) полетит ABI.
А менять их может заставить жизнь при развитии проекта. Например, вы написали такой метод:
Код
C++ (Qt)
const QString & filePath1(..) const;
который просто возвращает ссылку на поле класса, а через год, его нужно поменять так, что бы он начал возвращать временную строку, потому что этот путь уже не просто ссылка на поле, а формируется внутри filePath1 по хитрому алгоритму:
Код
C++ (Qt)
const QString & filePath1(..) const
{
   QString result = prefix() + m_filePath + postfix();
   return result;
}
 
Так просто это сделать не получиться, нужно будет отказываться от возврата константной ссылки, вместе с бинарной совместимостью.
Приплыли.
Поэтому, и нужно возвращать по значению. :)


Название: Re: Как писать ООП программы?
Отправлено: Hrundel от Февраль 24, 2014, 13:21
Объекты потомки Shape, по логики вещей, не могут существовать без Viewer, иначе их существование становится безсмысленным. Поэтому создаваться они должны для Viewer. То есть внутри этого класса.
Не вижу никаких оснований для такого вывода. Напр объекты могут быть загружены из файла и сохранены в др формате - никакого вьюера и близко нет. Или наоборот, могут быть использована неск вьюерами сразу.

Ну... Тут ты меня сразу уговорил :)

Код
C++ (Qt)
Circle *crcl = new Circle();
crcl->draw(x, y, rad);
 
Ну да, а откуда Circle возьмет контекст рисования?

Я так понял, что он хочет это для OpenGL использовать. Следовательно, напишет внутри GL-ные функции рисования и вперед. Или класс должен быть универсальным? И подходить для QPainter?


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 24, 2014, 20:07
Вот аргументы почему draw() нужно сделать методом класса Viewer, а не методом класса Shape:

Фигура - это объект, который не может совершать действий (к примеру, рисовать сама себя). Да, фигура, в моём случае, может расcчитать свою площадь и т.д., но для пользователя это незаметно, ведь он запрашивает площадь так: s = circle.area(). Можно сказать, что фигура обладает определённым набором характеристик (цвет, координаты и т.д.).

В моём случае, объект класса Viewer - это художник. У него есть холст и набор красок. Мы отдаём ему фигуру (одну за другой и viewer сохраняет их в массив), которая хранит в себе список своих характеристик. Художнику не нужно знать, что это за фигура. Он смотрит на список характеристик и находит там координаты точек. Выставляет все точки на холст и соединяет определёнными линиями, закрашивает определённым цветом, располагает фигуры в определённых местах и т.д.

А "компьютерному художнику" приходится хранить у себя массив фигур, чтобы перерисовывать, когда мы, к примеру, меняем размер окна (или перекрываем окно другим окном).

Я ещё не до конца реализовал. С помощью addForPainting(const myShapes::Shape &ps) я буду добавлять фигуру для рисования в закрытый массив std::vector<myShapes::Shape> shapes; и перерисовывать с помощью draw() весь массив фигур из shapes.

viewer.h
Код
C++ (Qt)
#ifndef VIEWER_H
#define VIEWER_H
 
#include <QGLWidget>
#include "shape.h"
 
#include <vector>
 
class Viewer : public QGLWidget
{
public:
   // ...
   void addForPainting(const myShapes::Shape &ps);
 
   // ...
private:
   std::vector<myShapes::Shape> shapes;
   void draw(const myShapes::Shape &ps, int xOffset, int yOffset);
};
 
#endif // VIEWER_H
 


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 24, 2014, 20:17
Намучился с одной ошибкой. Оказывается свои классы нужно прятать в своё пространство имён. Мой класс Rectangle начал конфликтовать с включением #include <QGLWidget> Пришлось сделать так:

shape.h
Код
C++ (Qt)
#ifndef SHAPE_H
#define SHAPE_H
 
namespace myShapes {
 
class Shape
{
public:
   // ...
};
 
}
 
#endif // SHAPE_H
 

rectangle.h
Код
C++ (Qt)
#ifndef RECTANGLE_H
#define RECTANGLE_H
 
#include "shape.h"
 
namespace myShapes {
 
class Rectangle : public Shape
{
public:
   // ...
};
 
}
 
#endif // RECTANGLE_H
 


Название: Re: Как писать ООП программы?
Отправлено: Hrundel от Февраль 24, 2014, 20:39
Вот аргументы почему draw() нужно сделать методом класса Viewer, а не методом класса Shape
...

[/quote]
Ну да, а откуда Circle возьмет контекст рисования?

Игорь, я тебя сильно уважаю, но тут ты сказал не подумав! Конечно, дико извеняюсь - но фигня это всё !!!

Если хочешь помещать потомков Shape в QGraphicsView, то ничего такого не надо!!!
Все потомки имеют метод paint и рисуют себя сами!!!
И для OpenGL та же ерунда действует.

В заголовке
Код
C++ (Qt)
class Shape : public QObject, public QGraphicsItem
{
       Q_OBJECT
 
   public:
       Shape ();
       ~Shape ();
       QRectF boundingRect() const;
       void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
 
       ... // и так далее
}
 

Дальше все от него наследуются, переопределяют paint и рисуют что хотят.


Название: Re: Как писать ООП программы?
Отправлено: Hrundel от Февраль 24, 2014, 20:47
Оказывается свои классы нужно прятать в своё пространство имён. Мой класс Rectangle начал конфликтовать с включением #include <QGLWidget>

И здесь дико извеняюсь - полная ерунда!!!


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 25, 2014, 08:07
Реализовать рисование у меня пока не получается. Вернусь к этому позже.

Помогите мне, пожалуйста, понять вот это правило: "Правило 37: Никогда не переопределяйте наследуемое значение аргумента функции по умолчанию" http://www.e-reading.bz/chapter.php/1002058/95/Mayers_-_Effektivnoe_ispolzovanie_CPP.html

Сначала автор приводит плохой пример, что не стоит переопределять аргумент поумолчанию наследуемой виртуальной функции. Так как виртуальные функции связываются динамически, а аргументы поумолчанию - статически:

main.cpp
Код
C++ (Qt)
#include <QCoreApplication>
#include <QDebug>
 
// классы для представления геометрических фигур
class Shape {
public:
   enum ShapeColor { Red, Green, Blue };
 
   // все фигуры должны предоставлять функцию для рисования
   virtual void draw(ShapeColor color = Red) const = 0;
   // ...
 
   virtual ~Shape() {
 
   }
};
 
class Rectangle: public Shape {
public:
   // заметьте, другое значение параметра по умолчанию – плохо!
   virtual void draw(ShapeColor color = Green) const {
       qDebug() << color;
   }
};
 
class Circle: public Shape {
public:
   virtual void draw(ShapeColor color) const {
       qDebug() << color;
   }
};
 
int main(int argc, char *argv[])
{
   QCoreApplication a(argc, argv);
 
   Shape *pr = new Rectangle;
   pr->draw();
   delete pr;
 
   return a.exec();
}
 

Чтобы было правильно, нужно написать то же самый код только изменить одну единственную строку (то есть убрать аргумент поумолчанию из класса потомка)

Код
C++ (Qt)
class Rectangle: public Shape {
public:
   virtual void draw(ShapeColor color) const {
       qDebug() << color;
   }
};
 

Но я не могу понять зачем автор приводит подход называемый: идиома невиртуального интерфейса (NVI) (см. код далее) Что нам это даёт? Чем это лучше предыдущего подхода (когда мы просто не переопределяем аргумент)? При подходе NVI мы так же не можем переопределить аргумент поумолчанию, так как в C++ перезагружать невиртуальные функции нельзя.

Код
C++ (Qt)
#include <QCoreApplication>
#include <QDebug>
 
class Shape {
public:
   enum ShapeColor { Red, Green, Blue };
   void draw(ShapeColor color = Red) const // теперь – невиртуальная
   {
       doDraw(color); // вызов виртуальной функции
   }
   // ...
private:
   virtual void doDraw(ShapeColor color) const = 0; // реальная работа выполняется в этой функции
};
 
class Rectangle: public Shape {
public:
   // ...
private:
   virtual void doDraw(ShapeColor color) const { // обратите внимание на отсутствие у аргумента значения по умолчанию
       qDebug() << color;
   }
};
 
int main(int argc, char *argv[])
{
   QCoreApplication a(argc, argv);
 
   Shape *pr = new Rectangle;
   pr->draw();
   delete pr;
 
   return a.exec();
}
 


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 25, 2014, 09:19
Я понял. Автор использовал идиому невиртуального интерфейса (NVI), чтобы пользователь класса Shape не смог переопределить draw(), то есть не стал бы писать свой аргумент поумолчанию. Пользователь увидит, что draw() невиртуальна и не станет её переопределять (ибо нельзя).


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 25, 2014, 10:54
Так просто это сделать не получиться, нужно будет отказываться от возврата константной ссылки, вместе с бинарной совместимостью.
Приплыли.
Да, напр было такое использование
Код
C++ (Qt)
const QString & base = obj.filePath1(..);
 
Это придется переделать - ну, на мой взгляд, в этом больше хорошего. Возможно я просто смирюсь с копированием, но может и придумаю новое решение - если это место критично. И компилятор меня "ткнет носиком". А в погоне за (мнимой) универсальностью легко можно загнать проблему "вглубь"


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 25, 2014, 11:02
ну, на мой взгляд, в этом больше хорошего.
А что в этом хорошего?

А в погоне за (мнимой) универсальностью легко можно загнать проблему "вглубь"
В погоне за мнимой преждевременной оптимизацией можно упустить гораздо более важные проблемы. :)


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 25, 2014, 11:40
Помогите мне, пожалуйста, понять вот это правило: "Правило 37: Никогда не переопределяйте наследуемое значение аргумента функции по умолчанию" http://www.e-reading.bz/chapter.php/1002058/95/Mayers_-_Effektivnoe_ispolzovanie_CPP.html
Мне кажется Вы погнались за второстепенными вещами, а ведь уже получили массу гораздо более полезных уроков - неплохо бы их заучить основательно

1) Не давайте общих имен (типа Rectangle) - велика вероятность они пересекутся. Простой и хороший способ - начинать свой класс (тип) с большой буквы, напр я использую "С". Написав CRectangle ясно видно - это мой класс. Др букву "T" я использую для своих typedef. Имена членов классов лучше начинать с "m" или "m_"

2) Использование namespace - палка о двух концах. Не стоит его заводить "просто так", напр в Вашем случае легко обойтись без него.

3) Очень важная вещь прошла мимо Вашего внимания: базовый инструментарий. Вы привлекли Qt - это важное, принципиальное решение. Тогда встает вопрос - так может и шейпы лучше делать средствами Qt? Напр освоить QGraphicsItem. Да, так Вы много не на-архитектурите, увы - все уйдет в чтение букваря. Но практический результат будет на порядок выше. Задаваться вопросом "а не изобретаю ли я велосипед?" приходится всем и всегда.  

Ладно, вернемся к Вашему проектированию
Вот аргументы почему draw() нужно сделать методом класса Viewer, а не методом класса Shape:

Фигура - это объект, который не может совершать действий (к примеру, рисовать сама себя). Да, фигура, в моём случае, может расcчитать свою площадь и т.д., но для пользователя это незаметно, ведь он запрашивает площадь так: s = circle.area(). Можно сказать, что фигура обладает определённым набором характеристик (цвет, координаты и т.д.).

В моём случае, объект класса Viewer - это художник. У него есть холст и набор красок. Мы отдаём ему фигуру (одну за другой и viewer сохраняет их в массив), которая хранит в себе список своих характеристик. Художнику не нужно знать, что это за фигура. Он смотрит на список характеристик и находит там координаты точек. Выставляет все точки на холст и соединяет определёнными линиями, закрашивает определённым цветом, располагает фигуры в определённых местах и т.д.

А "компьютерному художнику" приходится хранить у себя массив фигур, чтобы перерисовывать, когда мы, к примеру, меняем размер окна (или перекрываем окно другим окном).

Я ещё не до конца реализовал. С помощью addForPainting(const myShapes::Shape &ps) я буду добавлять фигуру для рисования в закрытый массив std::vector<myShapes::Shape> shapes; и перерисовывать с помощью draw() весь массив фигур из shapes.
1) Типичная (очень популярная) ошибка - неудачный выбор std::vector. Вспомните как "уплывали" адреса - теперь уже трудно будет подавать элементы вектора по указателю/ссылке, все время придется оглядываться "а не изменился ли вектор". Здесь хороший выбор QList

2) С какой это стати "художник" хранит все шейпы? Они могут прекрасно жить без всякого художника (см пример выше). Да, машине рисования они должны быть известны (иначе нечего рисовать), но это все, добавлением/удалением/редактированием шейпов рисование не занимается.

3) Аргументы "фигура не должна уметь себя рисовать" пока неубедительны, и что Вы предлагаете взамен - неясно. Попробуйте ответить на вопрос "а что плохого если фигура сама себя рисует?", он уже звучал выше

Да, и терминов типа "холст" лучше избегать, уж очень отдает дилетантством :) К тому же в современном рисовании нет аналога/подобия "холста"

 


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 25, 2014, 11:46
2) Использование namespace - палка о двух концах. Не стоит его заводить "просто так", напр в Вашем случае легко обойтись без него.
Можно подробней о "двух концах", в чем по вашему может быть проблема и почему по вашему его не стоит заводить просто так? И когда же по вашему его стоит заводить? :)
А я бы рекомендовал его использовать почаще, ибо он прекрасно справляется со своей ролью. Нужно просто с ним разобраться один раз.


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 25, 2014, 13:01
Можно подробней о "двух концах", в чем по вашему может быть проблема и почему по вашему его не стоит заводить просто так? И когда же по вашему его стоит заводить? :)
А я бы рекомендовал его использовать почаще, ибо он прекрасно справляется со своей ролью. Нужно просто с ним разобраться один раз.
Пример где я мучался продолжая разработку предшественников. Первый написал
Код
C++ (Qt)
namespace TriangulateUtils {
 
class TriangleLoop {
...
 
Ничего плохого нет. Второй писал др фрагмент и сделал так
Код
C++ (Qt)
namespace FontTriangulateUtils {
typedef TriangulateUtils::TriangleLoop TriangleLoop;
 
Т.к. "сопля была слишком длинной". Третий перелил это в свой namespace. В результате я не мог быстро посмотреть описание самого класса, браузер выходит на typedef - и все  :) Много времени съедается на отслеживание пересечений, а отделаться using не удается.


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 25, 2014, 13:09
Много времени съедается на отслеживание пересечений, а отделаться using не удается.
Дааа. Главное что бы эти "предшественники" не пользовались шаблонами, никогда. А с дуру можно что хош сломать. :)

Совет остается в силе:
Нужно просто с ним разобраться один раз.


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 25, 2014, 14:21
Совет остается в силе:
Нужно просто с ним разобраться один раз.
Так резво не выйдет. Надо пописать типа "MyNamespace::" раз так тыщу, чтобы почувствовать уместно/нужно ли оно или так, для понту


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 25, 2014, 14:24
Так резво не выйдет. Надо пописать типа "MyNamespace::" раз так тыщу, чтобы почувствовать уместно/нужно ли оно или так, для понту
Ну это только если с ними не разобраться "MyNamespace::" нужно писать раз так тыщу. Если не компетентен, то должен страдать. Да. Так везде. :)


Название: Re: Как писать ООП программы?
Отправлено: _OLEGator_ от Февраль 25, 2014, 14:37
Если не компетентен, то должен страдать. Да. Так везде. :)

Браво, аплодирую)


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 25, 2014, 14:55
Ну это только если с ними не разобраться "MyNamespace::" нужно писать раз так тыщу. Если не компетентен, то должен страдать. Да. Так везде. :)
Так Вы же советуете применять их почаще - не я. И человеку свойственно переоценивать собственную компетентность - как и недооценивать компетентность других  :)

Вот хоть эти шейпы. Ничего против namespace возразить не могу, вроде уместен. Но с др стороны имена классов Rectangle и Circle все равно плоховаты - так и надо это исправить. После этого какая необходимость в namespace? Аж никакой, ну и чего с ним спешить? Добавить его никогда не поздно, не надо решать проблемы до их поступления.


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 25, 2014, 15:04
Вот хоть эти шейпы. Ничего против namespace возразить не могу, вроде уместен. Но с др стороны имена классов Rectangle и Circle все равно плоховаты - так и надо это исправить. После этого какая необходимость в namespace? Аж никакой, ну и чего с ним спешить? Добавить его никогда не поздно, не надо решать проблемы до их поступления.
Все проблемы нужно решать заранее, потом решение может быть очень дорогим.
Про бинарную совместимость напомнить? Часто нельзя так просто поменять сигнатуры функций и методов, а библиотек написано уже огромное количество и колизии имен очень возможны. В крупных проектах.
А проблем с пространствами нет никаких, и ничего по тыще раз набирать не нужно. Нужно разобраться. :)


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 25, 2014, 15:45
Все проблемы нужно решать заранее, потом решение может быть очень дорогим.
Не думаю что Вам всегда удавалось все решить заранее :) Скорее всего начиная задачу Вы делаете вещи хорошо зарекомендовавшие себя в прошлом (в том числе и сразу заводите namespace). Ну так на здоровье - но есть и др подходы

а библиотек написано уже огромное количество и колизии имен очень возможны. В крупных проектах.
С этим никто не спорит, но совершенно незачем самому напрашиваться на коллизии давая такое имя как Rectangle. Скромнее надо быть :)


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 25, 2014, 15:50
С этим никто не спорит, но совершенно незачем самому напрашиваться на коллизии давая такое имя как Rectangle. Скромнее надо быть :)
Это даже не смешно, а какое имя я должен дать? Такое "asdasd"?

Еще раз повторю, в пространствах имен нет никаких проблем, не нужно по тыще раз ничего набирать, если научиться ими пользоваться. Поэтому, отказываться от них по вымышленным причинам не вижу смысла.


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 25, 2014, 18:17
Это даже не смешно, а какое имя я должен дать? Такое "asdasd"?
К таким извращениям я не призывал - достаточно добавить "личный префикс" который обычно 1 буква, полезно во всех отношениях.

Еще раз повторю, в пространствах имен нет никаких проблем, не нужно по тыще раз ничего набирать, если научиться ими пользоваться. Поэтому, отказываться от них по вымышленным причинам не вижу смысла.
Вы же не забывайте добавлять типа "я считаю, мое мнение.." :) А то звучит слишком категорично. У Вас одна точка зрения, у меня другая (и ей тоже не один день), это нормально.

Ладно, что-то ТС примолк. Ох и резво он стартовал - как бы не "перегорел"  :)


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 25, 2014, 18:24
К таким извращениям я не призывал - достаточно добавить "личный префикс" который обычно 1 буква, полезно во всех отношениях.
Так это корявая попытка сделать то, что делает namespace. :) Для чего извращаться, если есть полноценное решение.

Вы же не забывайте добавлять типа "я считаю, мое мнение.." :) А то звучит слишком категорично.
Вовсе нет.

У Вас одна точка зрения, у меня другая (и ей тоже не один день), это нормально.
Пока что вы не показали не одного убедительного довода, кроме незнания предмета.

Ладно, что-то ТС примолк. Ох и резво он стартовал - как бы не "перегорел"  :)
А что там резвого, по моему кучу времени он занимается какой-то ерундой с двумя классами в которых два метода. Я бы сказал, что он ничего не делает.



Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 25, 2014, 21:30
Помогите, пожалуйста, найти ошибку. Говорит:

Цитировать
GameCharacter\functions_for_healthcalc.h:9: error: 'GameCharacter' does not name a type
 int defaultHealthCalc(const GameCharacter& gc);
                                        ^

В этом файле:

Код
C++ (Qt)
#ifndef FUNCTIONS_FOR_HEALTHCALC_H
#define FUNCTIONS_FOR_HEALTHCALC_H
 
#include "gamecharacter.h"
 
namespace GameStuff {
 
// функция алгоритма по умолчанию для вычисления жизненной силы персонажа
int defaultHealthCalc(const GameCharacter& gc);
 
// функции вычисления жизненной силы с разным поведением
int loseHealthQuickly(const GameCharacter& gc);
int loseHealthSlowly(const GameCharacter& gc);
 
}
 
#endif // FUNCTIONS_FOR_HEALTHCALC_H
 

Если несложно - скомпилируйте у себя. Вот отсюда можно скачать ("Download ZIP" справа) https://github.com/8Observer8/GameCharacter/tree/strategyWithPointers

Очень буду признателен. Заранее спасибо.

P.S. Это попытка посмотреть в деле этот пример: http://www.e-reading.bz/chapter.php/1002058/88/Mayers_-_Effektivnoe_ispolzovanie_CPP.html


Название: Re: Как писать ООП программы?
Отправлено: gil9red от Февраль 25, 2014, 21:43
Код
C++ (Qt)
namespace GameStuff {
 
class GameCharacter;
 
// функция алгоритма по умолчанию для вычисления жизненной силы персонажа
int defaultHealthCalc(const GameCharacter& gc);
 
// функции вычисления жизненной силы с разным поведением
int loseHealthQuickly(const GameCharacter& gc);
int loseHealthSlowly(const GameCharacter& gc);
 
}

Код
C++ (Qt)
int main(int argc, char *argv[])
{
   QCoreApplication a(argc, argv);
 
   // однотипные персонажи с разным поведением относительно здоровья
   GameStuff::EvilBadGay ebg1(GameStuff::loseHealthQuickly);
   GameStuff::EvilBadGay ebg2(GameStuff::loseHealthSlowly);
 
   return a.exec();
}
 

Код
C++ (Qt)
namespace GameStuff {
 
class GameCharacter
{
public:
   GameCharacter();
 
   typedef int (*HealthCalcFunc)(const GameCharacter&);
 
   explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
       : healthFunc(hcf)
   {}
 
   int healthValue() const;
   int doHealthValue() const;
 
//    int healthValue() const {
//        return healthFunc(*this);
//    }
 
private:
   HealthCalcFunc healthFunc;
};
 
}
 


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 25, 2014, 21:48
2gil9red Там не только это. Теперь вроде все, но мне кажется ТС хотел вариант с указателем на функцию. Я оставил его. :)
8Observer8 Держите.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 25, 2014, 21:50
Спасибо огромное, парни! Завтра доделаю :)


Название: Re: Как писать ООП программы?
Отправлено: gil9red от Февраль 25, 2014, 21:54
2gil9red Там не только это. Теперь вроде все, но мне кажется ТС хотел вариант с указателем на функцию. Я оставил его. :)
8Observer8 Держите.
Не успел =)


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 26, 2014, 09:15
Объясните, пожалуйста, почему в этом коде нужно объявлять класс "class GameCharacter;" Я же включаю заголовок #include "gamecharacter.h":

Код
C++ (Qt)
#ifndef FUNCTIONS_FOR_HEALTHCALC_H
#define FUNCTIONS_FOR_HEALTHCALC_H
 
#include "gamecharacter.h"
 
namespace GameStuff {
 
class GameCharacter;
 
// функция алгоритма по умолчанию для вычисления жизненной силы персонажа
int defaultHealthCalc(const GameCharacter& gc);
 
// функции вычисления жизненной силы с разным поведением
int loseHealthQuickly(const GameCharacter& gc);
int loseHealthSlowly(const GameCharacter& gc);
 
}
 
#endif // FUNCTIONS_FOR_HEALTHCALC_H
 


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 26, 2014, 09:32
Почти всё исправил. Помогите, пожалуйста, доделать. Сейчас говорит, что:

Цитировать
GameCharacter\gamecharacter.h:15: error: 'defaultHealthCalc' is not a member of 'GameStuff'
     explicit GameCharacter(HealthCalcFunc hcf = GameStuff::defaultHealthCalc)

Код: https://github.com/8Observer8/GameCharacter/tree/strategyWithPointers


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 26, 2014, 10:01
Я пытаюсь вот этот пример реализовать: http://www.e-reading.bz/chapter.php/1002058/89/Mayers_-_Effektivnoe_ispolzovanie_CPP.html

Подключаю заголовок:
Код
C++ (Qt)
#include <functional>
 

Пишу вот такую строчку:
Код
C++ (Qt)
typedef std::tr1::function HealthCalcFunc;
 

Но компилятор не находит tr1. Почему? Как написать?



Название: Re: Как писать ООП программы?
Отправлено: Johnik от Февраль 26, 2014, 10:06
Но компилятор не находит tr1. Почему? Как написать?
std::function


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 26, 2014, 10:27
Но компилятор не находит tr1. Почему? Как написать?
std::function

Говорит, что:

Цитировать
main.cpp:8: error: 'function' in namespace 'std' does not name a type
     typedef std::function func;
                ^

Код
C++ (Qt)
#include <QCoreApplication>
#include <functional>
 
int main(int argc, char *argv[])
{
   QCoreApplication app(argc, argv);
 
   typedef std::function func;
 
   return app.exec();
}
 


Название: Re: Как писать ООП программы?
Отправлено: Johnik от Февраль 26, 2014, 10:36
указывать надо так:
Код
C++ (Qt)
typedef std::function<void()> someFunc;


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 26, 2014, 10:39
Всё равно говорит, что:

Цитировать
main.cpp:8: error: 'function' in namespace 'std' does not name a type
     typedef std::function func;
                ^

Код
C++ (Qt)
#include <QCoreApplication>
#include <functional>
 
int main(int argc, char *argv[])
{
   QCoreApplication app(argc, argv);
 
   typedef std::function<void()> func;
 
   return app.exec();
}
 


Название: Re: Как писать ООП программы?
Отправлено: Johnik от Февраль 26, 2014, 10:45
Какой у вас компилятор?


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 26, 2014, 10:47
Вот такая сборка Qt: Qt 5.2.0 for Windows 32-bit (MinGW 4.8, OpenGL, 689 MB)


Название: Re: Как писать ООП программы?
Отправлено: Johnik от Февраль 26, 2014, 10:48
в .pro необходимо добавить:
Код:
QMAKE_CXXFLAGS += -std=c++11


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 26, 2014, 10:50
в .pro необходимо добавить:
Код:
QMAKE_CXXFLAGS += -std=c++11

Спасибо огромное! Работает :)


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 26, 2014, 11:01
Только меня смущает, что имя "function" не отображается в подсказках:

(http://i7.pixs.ru/storage/6/7/2/137png_3528066_11027672.png)


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 26, 2014, 12:19
Помогите, пожалуйста, понять и исправить эту ошибку:

Цитировать
042_typename\main.cpp:44: error: expected template-name before '<' token
 class Derived: public Base<T>::Nested { // СЃРїРёСЃРѕРє базовых классов:
                                      ^

Код
C++ (Qt)
#include <QCoreApplication>
 
class Base
{
public:
   Base() {}
   class Nested
   {
   public:
       Nested() {}
   };
};
 
template <typename T>
class Derived: public Base<T>::Nested { // список базовых классов:
public:                                 // typename не допускается
   explicit Derived(int x) :
       Base<T>::Nested(x) /* идентификатор базового класса
                             * в списке инициализации членов:
                             * typename не допускается
                             */

   {
       typename Base<T>::Nested temp; /* использование вложенного
                                       * зависимого имени типа не как
                                       * идентификатора базового
                                       * класса в списке инициализации
                                       * членов: typename необходимо
                                       */

   }
};
 
int main(int argc, char *argv[])
{
   QCoreApplication a(argc, argv);
 
   return a.exec();
}
 

P.S. Это код из правила: http://www.e-reading.bz/chapter.php/1002058/106/Mayers_-_Effektivnoe_ispolzovanie_CPP.html


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 26, 2014, 12:26
Я нашёл ошибку :)

Код
C++ (Qt)
template <typename T>
class Base
{
public:
   Base() {}
   class Nested
   {
   public:
       Nested() {}
   };
};
 


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 26, 2014, 12:49
Помогите, пожалуйста, понять следующую вещь. Я так понял, что когда мы передаём объекты элементарных типов (int, double и т.д.), то передавать по ссылке на константу или по значению - разницы нет. Для объектов отстальных типов нужно использовать передачу по ссылке на константу. А как обстоит дело и итераторами? Передавать по ссылке на константу или по значению?

Код
C++ (Qt)
#include <QCoreApplication>
#include <vector>
#include <QDebug>
 
/**
* Локальная копия объекта, на который указывает итератор
*/

template <typename IterT>
void workWithIterator(const IterT& iter)
{
   typedef typename std::iterator_traits<IterT>::value_type value_type;
   value_type temp(*iter);
   qDebug() << temp;
}
 
int main(int argc, char *argv[])
{
   QCoreApplication a(argc, argv);
 
   std::vector<int> arr;
   arr.push_back(5);
   arr.push_back(7);
 
   workWithIterator(arr.begin());
 
   return a.exec();
}
 


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 26, 2014, 13:23
По значению. Вообще если класс "достаточно мал" - он обычно подается/возвращается по значению. Напр (Q)Point, (Q)Rect, (Q)Size


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 26, 2014, 13:28
Спасибо! В данном случае - понятно. А если это класс Person, в котором: имя, фамилия, отчество и дата рождения? Это довольно маленький класс.


Название: Re: Как писать ООП программы?
Отправлено: Johnik от Февраль 26, 2014, 13:46
По значению. Вообще если класс "достаточно мал" - он обычно подается/возвращается по значению. Напр (Q)Point, (Q)Rect, (Q)Size
в Qt эти классы в большинстве мест передаются по константной ссылке.

Спасибо! В данном случае - понятно. А если это класс Person, в котором: имя, фамилия, отчество и дата рождения? Это довольно маленький класс.
Если класс больше чем int, double и т.д., то он обычно передается по константной ссылке.

По поводу итераторов:
Итераторы содержат как правило всего лишь один указатель на какой-либо узел, поэтому они передается по значению.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 26, 2014, 13:49
Большое спасибо!  :)


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 26, 2014, 13:50
Спасибо! В данном случае - понятно. А если это класс Person, в котором: имя, фамилия, отчество и возраст? Это довольно маленький класс.
Хороший показатель: есть ли оператор + (фактически он говорит что класс будет обыгрываться по значению). Др показатель - использует ли класс хоть какие-то распределения памяти - напр строки используют. Ну и редко но все же бывает что подача по значению выгодна задаче. Пример
Код
C++ (Qt)
typedef double Matrix[16];
void InvertMatrix( Matrix & dst, Matrix src );
В чем замысел программиста подавшего второй аргумент по значению?


Название: Re: Как писать ООП программы?
Отправлено: Hrundel от Февраль 26, 2014, 20:07
У меня в итоге только один вопрос: научился ли ТС  писать ООП программы? ;D


Название: Re: Как писать ООП программы?
Отправлено: BuRn от Февраль 26, 2014, 20:23
У меня в итоге только один вопрос: научился ли ТС  писать ООП программы? ;D
ИМХО тема из серии "Как скачать интернет"


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 26, 2014, 20:41
Пример
Код
C++ (Qt)
typedef double Matrix[16];
void InvertMatrix( Matrix & dst, Matrix src );
В чем замысел программиста подавшего второй аргумент по значению?

Замысел в том, чтобы не испортить матрицу-источник.

Мне кажется, здесь ошибка (здесь нет матрицы) и нужно было так написать:

Код
C++ (Qt)
#include <QCoreApplication>
 
const std::size_t NR = 5;
const std::size_t NC = 10;
 
typedef int m1[NC];
typedef m1 Matrix[NR];
 
void InvertMatrix(Matrix &dst, Matrix src) {
 
}
 
int main(int argc, char *argv[])
{
   QCoreApplication a(argc, argv);
 
   Matrix matrix;
 
   return a.exec();
}
 


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 27, 2014, 07:45
Я хочу реализовать свою идею с рисованием фигуры до рабочего состояния. И своими глазами увидеть почему нужно функцию draw() делать чисто виртуальной в базовом классе, и как её написать для каждого класса потомка. И какие ещё проблемы всплывут во время реализации.

Вот моя идея, на примере класса Rectangle:

- В базовом абстрактном классе Shape, я реализовал хранение пар координат в динамическом массиве. Координаты точек рассчитываются во время создания объекта и сохраняются в массив. Точку можно получить по индексу:

Код
C++ (Qt)
#ifndef SHAPE_H
#define SHAPE_H
 
#include <vector>
#include <utility>
 
namespace myShapes {
 
class Shape
{
public:
   // ...
 
   std::pair<int, int> point(int index) const {
       return m_points[index];
   }
 
protected:
   void addPoint(int x, int y) {
       m_points.push_back(std::make_pair(x, y));
   }
 
private:
   std::vector<std::pair<int, int> > m_points;
   //...
};
 
}
 
#endif // SHAPE_H
 

В функции main() я создаю объект класса Viewer и "скармливаю" ему объекты с динамическим типом Rectangle. Я хочу чтобы объект класса Viewer хранил копии объектов Rectangle и др. для перерисовывания. Хранить копии объектов нужно для функции paintGL(), которая вызывается, когда объект нужно перерисовать.

Но я пришёл к такой ситуации, что мне оказывается нужно хранить объекты класса Shape, а этого нельзя сделать, потому что нельзя создать объекты абстрактного класса.

Код
C++ (Qt)
#ifndef VIEWER_H
#define VIEWER_H
 
#include <QGLWidget>
#include "shape.h"
 
#include <vector>
 
class Viewer : public QGLWidget
{
public:
   Viewer(QWidget *pwgt = 0);
 
   void addForPainting(const myShapes::Shape &ps) {
       m_shapes.push_back(ps);
   }
 
protected:
   virtual void initializeGL();
   virtual void resizeGL(int nWidth, int nHeight);
   virtual void paintGL();
 
private:
   std::vector<myShapes::Shape> m_shapes;
};
 
#endif // VIEWER_H
 


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 27, 2014, 08:04
Зато можно хранить указатель или ссылку на абстрактный класс.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 27, 2014, 08:08
Да, но у меня в main объекты быстро удаляются:

Код
C++ (Qt)
int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
 
   Viewer viewer;
   viewer.resize(400, 200);
 
   myShapes::Shape *pr1 = new myShapes::Rectangle(5.0, 10.0);
   myShapes::Shape *pr2 = new myShapes::Rectangle(5.0, 10.0);
   myShapes::Shape *pr3 = new myShapes::Rectangle(5.0, 10.0);
   myShapes::Shape *pr4 = new myShapes::Rectangle(5.0, 10.0);
   myShapes::Shape *pr5 = new myShapes::Rectangle(5.0, 10.0);
 
   viewer.addForPainting(*pr1);
   viewer.addForPainting(*pr2);
   viewer.addForPainting(*pr3);
   viewer.addForPainting(*pr4);
   viewer.addForPainting(*pr5);
 
   delete pr1;
   delete pr2;
   delete pr3;
   delete pr4;
   delete pr5;
 
   viewer.show();
   return a.exec();
}
 


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 27, 2014, 08:24
Работайте с умными указателями, тогда объекты указателей в функции создания фигур быстро удаляться, а сами объекты фигур остануться жить, пока указатели на них будут храниться в списке viewer.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 27, 2014, 08:33
Работайте с умными указателями, тогда объекты указателей в функции создания фигур быстро удаляться, а сами объекты фигур остануться жить, пока указатели на них будут храниться в списке viewer.

Огромное спасибо! Я теперь смогу изучить работу с умными указателями на практике :)

P.S. Позже напишу о результатах.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 27, 2014, 11:50
Посмотрите, пожалуйста, мой маленький демонстрационный пример. Корректно ли я работаю с умными указателями:

Output:
Цитировать
Name of the Shape: Rectangle
Name of the Shape: Circle

Код
C++ (Qt)
#include <QCoreApplication>
#include <memory>
#include <vector>
#include <string>
#include <iostream>
 
//----------------- class Shape -----------------
class Shape {
public:
   Shape() {
   }
   virtual ~Shape() {
 
   }
 
   const std::string& name() const {
       return m_name;
   }
 
protected:
   std::string m_name;
};
 
//----------------- class Rectangle -------------
class Rectangle : public Shape
{
public:
   Rectangle() {
       m_name = "Rectangle";
   }
};
 
 
//----------------- class Circle -------------
class Circle : public Shape
{
public:
   Circle() {
       m_name = "Circle";
   }
};
 
//----------------- class Viewer -------------
class Viewer {
public:
   void addForPaiting(std::shared_ptr<Shape> pshape) {
       m_pshapes.push_back(pshape);
   }
 
   void showShapes() const {
       for (std::size_t i = 0; i < m_pshapes.size(); ++i) {
           std::cout << "Name of the Shape: " << m_pshapes[i]->name() << std::endl;
       }
   }
 
private:
   std::vector<std::shared_ptr<Shape> > m_pshapes;
};
 
int main(int argc, char *argv[])
{
   QCoreApplication a(argc, argv);
 
   std::shared_ptr<Shape> pr(new Rectangle);
   std::shared_ptr<Shape> pc(new Circle);
 
   Viewer viewer;
   viewer.addForPaiting(pr);
   viewer.addForPaiting(pc);
 
   viewer.showShapes();
 
   return a.exec();
}
 


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 27, 2014, 12:32
Код
C++ (Qt)
   std::vector<std::pair<int, int> > m_points;
 
Такие сопли надо убирать с помощью typedef, напр
Код
C++ (Qt)
   typedef std::pair<int, int> TPoint;
   typedef std::vector<TPoint> > TVector;
   TVectot m_points;
 

В функции main() я создаю объект класса Viewer и "скармливаю" ему объекты с динамическим типом Rectangle. Я хочу чтобы объект класса Viewer хранил копии объектов Rectangle и др. для перерисовывания. Хранить копии объектов нужно для функции paintGL(), которая вызывается, когда объект нужно перерисовать.
Ну конечно бред собачий - но это неизбежно при любом самостоятельном проектировании :) "Хранить копии объектов" - не лезет ни в какие ворота. Простой. хороший и очень популярный способ - viewer знает контейнер УКАЗАТЕЛЕЙ на базовый класс Shape и может вызвать виртуальный draw для каждого из них. Тогда правда неясно что делает массив(ы) точек.

Но этот подход - не единственный, а иногда и не лучший. Напр задача не просто рисовать OpenGL, но и кешировать данные рисования в карте (VBO). Тогда может ставить вопрос так:

- каждая фигура умеет создавать свои "данные рисования" (напр массив точек) которые хранятся вьюером умеющим их отображать. Это конечно совсем не "копии объектов". Заметим что придется попотеть с такими данными для такого "простейшего" объекта как Circle.

Да, и вот если бы у Вас был шейп Polygon - возможно и возникла бы идея метода Convert2Polygon. А так мало кода - много измышлений  :)


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 27, 2014, 13:31
Посмотрите, пожалуйста, мой маленький демонстрационный пример. Корректно ли я работаю с умными указателями:
Добавьте отладочный вывод в конструкторы и деструкторы фигур и все увидите. :)
Учитесь проверять самостоятельно, придумывайте как что можно проверить, иначе вы всегда будете за кем то ходить и спрашивать правильно или нет. ;)


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 27, 2014, 14:19
Код
C++ (Qt)
   std::vector<std::pair<int, int> > m_points;
 
Такие сопли надо убирать с помощью typedef, напр
Код
C++ (Qt)
   typedef std::pair<int, int> TPoint;
   typedef std::vector<TPoint> > TVector;
   TVectot m_points;
 

Да, точно! Большое спасибо! :)

может вызвать виртуальный draw для каждого из них.

Почему-то все примеры с фигурами (для демонстрации полиморфизма) содержат чисто виртуальную функцию draw(). Но я до сих пор не могу понять, как это реализовать на простейшем примере, используя QGLWidget. Я вижу только один выход - каждый объект статического типа Shape хранит в себе массив точек.

Заметим что придется попотеть с такими данными для такого "простейшего" объекта как Circle.

Circle - это многоугольник с бессконечным количеством вершин. К примеру, можно ограничиться шестиугольником :)

Да, и вот если бы у Вас был шейп Polygon - возможно и возникла бы идея метода Convert2Polygon. А так мало кода - много измышлений  :)

Polygon - это слишком сложно. Я не знаю, как считать площать многоугольника. Тут интеграл, наверное, нужен.

Посмотрите, пожалуйста, мой маленький демонстрационный пример. Корректно ли я работаю с умными указателями:
Добавьте отладочный вывод в конструкторы и деструкторы фигур и все увидите. :)
Учитесь проверять самостоятельно, придумывайте как что можно проверить, иначе вы всегда будете за кем то ходить и спрашивать правильно или нет. ;)

Ошибок там нет. Во всяком случае, компилируется и выводит результат. Я просто хотел убедиться, что правильно применил shared_ptr :)


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 27, 2014, 14:47
"Хранить копии объектов" - не лезет ни в какие ворота. Простой. хороший и очень популярный способ - viewer знает контейнер УКАЗАТЕЛЕЙ на базовый класс Shape

Я боялся, что кто-нибудь удалит объекты и viewer крякнет. Я не знаю, как бы я выкрутился без shared_ptr


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 27, 2014, 14:49
Ура-а-а! Рисует! :D

(http://i7.pixs.ru/storage/3/2/8/138png_9115939_11042328.png)

Скажите, пожалуйста, как можно развить ООП дальше в этом примере с фигурами? У меня есть идея сделать Rectangle шаблоном. Ведь для вывода на экран нужны целые значения и в тоже время хочется иметь возможность считать площадь в вещественном виде. Хотя... всё равно неявно из double в int преобразуется (для функций рисования). Просто для практики попробую.

Нужно будет, наверное, ещё повороты (ориентацию под углом) как-то реализовать.

main.cpp
Код
C++ (Qt)
#include <QApplication>
#include "shape.h"
#include "rectangle.h"
#include "viewer.h"
#include <utility>
#include <iostream>
#include <memory>
 
int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
 
   Viewer viewer;
   viewer.resize(400, 200);
 
   std::shared_ptr<myShapes::Shape> pr1(new myShapes::Rectangle(100.0, 25.0));
   std::shared_ptr<myShapes::Shape> pr2(new myShapes::Rectangle(100.0, 25.0));
   std::shared_ptr<myShapes::Shape> pr3(new myShapes::Rectangle(100.0, 25.0));
   std::shared_ptr<myShapes::Shape> pr4(new myShapes::Rectangle(100.0, 25.0));
 
   viewer.addForPainting(pr1);
   viewer.addForPainting(pr2);
   viewer.addForPainting(pr3);
   viewer.addForPainting(pr4);
 
   viewer.show();
   return a.exec();
}
 

viewer.h
Код
C++ (Qt)
#ifndef VIEWER_H
#define VIEWER_H
 
#include <QGLWidget>
#include "shape.h"
 
#include <vector>
#include <memory>
 
class Viewer : public QGLWidget
{
public:
   Viewer(QWidget *pwgt = 0);
 
   void addForPainting(std::shared_ptr<myShapes::Shape> ps) {
       m_pshapes.push_back(ps);
   }
 
protected:
   virtual void initializeGL();
   virtual void resizeGL(int nWidth, int nHeight);
   virtual void paintGL();
 
private:
   std::vector<std::shared_ptr<myShapes::Shape> > m_pshapes;
   void draw(std::shared_ptr<myShapes::Shape> ps, int xOffset, int yOffset);
};
 
#endif // VIEWER_H
 

viewer.cpp
Код
C++ (Qt)
#include "viewer.h"
#include <utility>
#include <QDebug>
 
Viewer::Viewer(QWidget* pwgt) :
   QGLWidget(pwgt)
{
}
 
void Viewer::draw(std::shared_ptr<myShapes::Shape> ps, int xOffset, int yOffset)
{
   glPointSize(2.0);
   glBegin(GL_LINE_LOOP);
   glColor3f(0.0, 0.0, 0.0);
   for (int i = 0; i < ps->amountOfPoints(); ++i) {
       std::pair<int, int> point = ps->point(i);
//        qDebug() << "x = " << point.first << "; xOffset = " << xOffset;
//        qDebug() << "y = " << point.second << "; yOffset = " << yOffset;
       int x = point.first + xOffset;
       int y = point.second + yOffset;
       glVertex2f(x, y);
   }
//    qDebug() << "";
   glEnd();
}
 
/*virtual*/ void Viewer::initializeGL()
{
   qglClearColor(Qt::white);
}
 
/*virtual*/ void Viewer::resizeGL(int nWidth, int nHeight)
{
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glViewport(0, 0, (GLint)nWidth, (GLint)nHeight);
   glOrtho(0, 400, 200, 0, -1, 1);
}
 
/*virtual*/ void Viewer::paintGL()
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
   int xGeneralOffset = 50;
   int yGeneralOffset = 100;
   int xOffset = 0;
   int yOffset = 0;
   for (std::size_t i = 0; i < m_pshapes.size(); ++i) {
       if (( (i+1) % 2) != 0) { // Odd, one-based
           xOffset = xGeneralOffset;
           yOffset += yGeneralOffset;
       } else {
           xOffset = xGeneralOffset + 150;
       }
       if (i == 0 || i == 1) {
           yOffset = 50;
       }
       draw(m_pshapes[i], xOffset, yOffset);
   }
}
 

rectangle.h
Код
C++ (Qt)
#ifndef RECTANGLE_H
#define RECTANGLE_H
 
#include "shape.h"
 
namespace myShapes {
 
class Rectangle : public Shape
{
public:
 
   Rectangle(double width = 0.0, double height = 0.0) :
       m_height(height),
       m_width(width)
   {
       addPoint(0, 0);
       addPoint(width, 0);
       addPoint(width, height);
       addPoint(0, height);
   }
 
   static double perimeter(double height, double width) {
       return 2.0 * (height + width);
   }
 
   static double area(double height, double width) {
       return height * width;
   }
 
   virtual double perimeter() const {
       return perimeter(m_height, m_width);
   }
 
   virtual double area() const {
       return area(m_height, m_width);
   }
 
   inline double height() const {
       return m_height;
   }
 
   inline double width() const {
       return m_width;
   }
 
   inline void setHeight(double height) {
       m_height = height;
   }
 
   inline void setWidth(double width) {
       m_width = width;
   }
 
private:
   double m_height;
   double m_width;
};
 
}
 
#endif // RECTANGLE_H
 

shape.h
Код
C++ (Qt)
#ifndef SHAPE_H
#define SHAPE_H
 
#include <vector>
#include <utility>
 
namespace myShapes {
 
class Shape
{
public:
 
   Shape() {
   }
 
   virtual ~Shape() {
   }
 
   virtual double perimeter() const = 0;
   virtual double area() const = 0;
 
   inline void setPoinstSize(int pointSize = 2) {
       m_pointSize = pointSize;
   }
 
   inline int pointSize() const {
       return m_pointSize;
   }
 
   std::pair<int, int> point(int index) const {
       return m_points[index];
   }
 
   int amountOfPoints() {
       return m_points.size();
   }
 
protected:
   void addPoint(int x, int y) {
       m_points.push_back(std::make_pair(x, y));
   }
 
private:
   int m_pointSize;
   std::vector<std::pair<int, int> > m_points;
};
 
}
 
#endif // SHAPE_H
 


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 27, 2014, 16:16
Скажите, пожалуйста, как можно развить ООП дальше в этом примере с фигурами?

У меня есть идея сделать Rectangle шаблоном.
Пока все это напоминает дамскую прогулку
Цитировать
Полторы мили пешком, назад в коляске
Сделал аж Rectangle - ну вот и типа "освоил". А делать тот же Circle - та ну его, "мы устали". Код почистить тоже времени нет, тут же такие вкусные плюшечки ("умные" указатели и.т.п). Да и вообще, черновая работа не для нас, мы будем "обобщать" (с помощью шаблонов и др пакостей).

Плохо, очень плохо  :'(


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 27, 2014, 16:31
(с помощью шаблонов и др пакостей).
Ну да, шаблоны вы тоже не осилили... :)

Вы не знаете и половины языка, на котором пишете, вот это действительно:
Плохо, очень плохо

А человек только начал разбираться, у него все еще впереди. Хотя задачи он ставит себе очень синтетические, но не вы ли ему предложили этот идиотизм? ;)


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 28, 2014, 07:59
Igors, это неокончательный вариант кода, а всего лишь черновик. Я его ещё буду улучшать.

Сейчас разбираюсь с шаблонами. Начал переписывать и разбирать код из этого правила: http://www.e-reading.bz/chapter.php/1002058/108/Mayers_-_Effektivnoe_ispolzovanie_CPP.html

Вроде сначала всё было понятно, а теперь запутался и ошибки не знаю, как исправить. Если несложно, скомпилируйте, пожалуйста, у себя:

Код
C++ (Qt)
#include <QCoreApplication>
#include <string>
#include <iostream>
 
//---------------------------------------------------------------
class CompanyA {
public:
   // ...
   void sendClearText(const std::string& msg);
   void sendEncryptedText(const std::string& msg);
   // ...
};
 
void CompanyA::sendClearText(const std::string& msg) {
   std::cout << "sendClearText of CompanyA: " << msg << std::endl;
}
 
void CompanyA::sendEncryptedText(const std::string& msg) {
   std::cout << "sendEncryptedText of CompanyA: " << msg << std::endl;
}
 
//---------------------------------------------------------------
class CompanyB {
public:
   // ...
   void sendClearText(const std::string& msg);
   void sendEncryptedText(const std::string& msg);
   // ...
};
 
void CompanyB::sendClearText(const std::string& msg) {
   std::cout << "sendClearText of CompanyB: " << msg << std::endl;
}
 
void CompanyB::sendEncryptedText(const std::string& msg) {
   std::cout << "sendEncryptedText of CompanyB: " << msg << std::endl;
}
 
//---------------------------------------------------------------
// этот класс не представляет функции sendCleartext
class CompanyZ {
public:
   // ...
   void sendEncrypted(const std::string& msg);
   // ...
};
 
void CompanyZ::sendEncrypted(const std::string& msg) {
   std::cout << "sendEncryptedText of CompanyZ: " << msg << std::endl;
}
 
//---------------------------------------------------------------
// полная специализация MsgSender; отличается от общего шаблона
// только отсутствием функции sendCleartext
template <>
class MsgSender<CompanyZ> {
public:
   // ...
   void sendSecret(const MsgInfo& info)
   {
       std::string msg;
       // создать msg из info
       msg = info.msg + " " + msg;
       CompanyZ c;
       c.sendEncrypted(msg);
   }
};
 
//---------------------------------------------------------------
// классы для других компаний
// ...
 
//---------------------------------------------------------------
// класс, содержащий информацию, используемую для создания сообщения
class MsgInfo {
public:
   std::string msg;
};
 
//---------------------------------------------------------------
template <typename Company>
class MsgSender {
public:
   // конструктор, деструктор и т. п.
   // ...
 
   void sendClear(const MsgInfo& info)
   {
       std::string msg;
       // создать msg из info
       msg = info.msg + " " + msg;
       Company c;
       c.sendClearText(msg);
   }
 
   // аналогично sendClear, но вызывает c.sendEncrypted(msg)
   void sendSecret(const MsgInfo& info)
   {
       std::string msg;
       // создать msg из info
       // ...
       Company c;
       c.sendEncrypted(msg);
   }
};
 
//---------------------------------------------------------------
template <typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
   // второй способ (см. о чём идёт речь ниже)
   // сообщает компилятору о том, что
   // sendClear есть в базовом классе
   //using MsgSender<Company>::sendClear;
   // ...
 
   void sendClearMsg(const MsgInfo& info)
   {
       // записать в протокол перед отправкой;
       std::cout << "It is written in protocol before sending" << std::endl;
 
       // вызвать функцию из базового класса
       // этот код не будет компилироваться, так как в шаблонных классах
       // компилятор не ищет в базовом
       // sendClear(info);
 
       // первый способ: чтобы код компилировался нужно написать:
       this->sendClear(info);
 
       // второй способ: using MsgSender<Company>::sendClear;
       // sendClear(info);
 
       // третий способ: явно указать, что
       // вызываемая функция находится в базовом классе:
       // MsgSender<Company>::sendClear(info);
       // примечание: но этот способ хуже прочих, посколько если
       // вызываемая функция виртуальна, то явная квалификация
       // отключает динамическое связывание.
 
       // записать в протокол после отправки;
       std::cout << "It is written in protocol after sending" << std::endl;
   }
   // ...
};
 
//---------------------------------------------------------------
int main(int argc, char *argv[])
{
   QCoreApplication a(argc, argv);
 
   MsgInfo msginfo;
   msginfo.msg = "Heloooo!";
   MsgSender<CompanyA> msgSender;
   msgSender.sendClear(msginfo);
 
   return a.exec();
}
 


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 28, 2014, 09:24
Я думаю, что проблема в следующем коде, потому что когда я его убираю в комминтарии, то всё работает.

Но я так и не могу понять, что не так в этом коде:

Код
C++ (Qt)
//---------------------------------------------------------------
// полная специализация MsgSender; отличается от общего шаблона
// только отсутствием функции sendCleartext
template <>
class MsgSender<CompanyZ> {
public:
   // ...
   void sendSecret(const MsgInfo& info)
   {
       std::string msg;
       // создать msg из info
       msg = info.msg + " " + msg;
       CompanyZ c;
       c.sendEncrypted(msg);
   }
};
 


Название: Re: Как писать ООП программы?
Отправлено: VPS от Февраль 28, 2014, 10:15
Сейчас разбираюсь с шаблонами. Начал переписывать и разбирать код из этого правила: http://www.e-reading.bz/chapter.php/1002058/108/Mayers_-_Effektivnoe_ispolzovanie_CPP.html

Вроде сначала всё было понятно, а теперь запутался и ошибки не знаю, как исправить. Если несложно, скомпилируйте, пожалуйста, у себя

Если у Вас весь код написан в одном файле, то Вы пытаетесь определить полную спецификацию класса раньше, чем шаблон.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 28, 2014, 10:18
Если у Вас весь код написан в одном файле, то Вы пытаетесь определить полную спецификацию класса раньше, чем шаблон.

Да, точно! Спасибо огромное! :)


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 28, 2014, 10:50
Ну да, шаблоны вы тоже не осилили... :)

Вы не знаете и половины языка, на котором пишете,
Ну чего Вы склочничаете когда Вас никто не трогал? :) И чего оно Вас так волнует сколько я знаю? :)

Igors, это неокончательный вариант кода, а всего лишь черновик. Я его ещё буду улучшать.
Книга что Вы штудируете интересна, но обратите внимание на слова улучшить структуру. Так вот сначала надо уметь создать хоть какую-то структуру. Этого умения у Вас пока нет, зато охотно впитываете вещи которые, по существу, требуют механического запоминания - но не принятия своих решений. Пусть вещи эти совсем неплохи - но главного они не заменят. К сожалению, эта ситуация очень типична, часто смотрю текст - ни классов, ни ф-ционала, ничего по уму нет, "зато" шаблонов навернул с 3 короба. Типа "больше всякой всячины" = лучше, мол, "хорошо знает язык"  :) :'(


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 28, 2014, 11:38
И чего оно Вас так волнует сколько я знаю?
Так вы же постоянно пытаетесь судить о том, что не знание: шаблоны, пространства имён, стандартная библиотека, и пытаетесь отговорить людей это изучать. :)
Но, если вы это не осилили это не значит , что никто не осилить. В основной массе специалисты спокойно используют все возможности С++.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 28, 2014, 12:34
Объяните, пожалуйста, почему n = 10?

(http://i6.pixs.ru/storage/5/5/4/139png_4134182_11052554.png)

Проект: https://github.com/8Observer8/Shapes/tree/drawingShapes (https://github.com/8Observer8/Shapes/tree/drawingShapes)


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 28, 2014, 13:06
Всё, исправил ошибку: в меню выбрал Build -> Clean Project "Shapes" -> Build -> Rebuild Project "Shapes"

(http://i6.pixs.ru/storage/8/4/8/140png_3020937_11052848.png)


Название: Re: Как писать ООП программы?
Отправлено: VPS от Февраль 28, 2014, 13:25
Маленький комментарий к коду: вместо использования "new" при создании "std::shared_ptr", вроде как рекомендуют использовать "std::make_shared".

П.С.: только эта функция не позволяет задавать пользовательскую операцию удаления.


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 28, 2014, 18:14
И что, если клиент вызывает Circle::setRadius - продолжает рисоваться старый?


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 28, 2014, 18:32
И что, если клиент вызывает Circle::setRadius - продолжает рисоваться старый?

Новый должен рисоваться. Вот только почему-то компилятор ищет имя "setRadius" в базовом классе. Ошибка такая:

Цитировать
Shapes\main.cpp:31: error: 'class myShapes::Shape' has no member named 'setRadius'
     pc->setRadius(25.0);
           ^

На строке: pc->setRadius(25.0);

Код
C++ (Qt)
#include <QApplication>
#include "shape.h"
#include "rectangle.h"
#include "circle.h"
#include "triangle.h"
#include "viewer.h"
#include <memory>
 
int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
 
   Viewer viewer;
   viewer.resize(400, 200);
 
   std::shared_ptr<myShapes::Shape> pt(new myShapes::Triangle(30.0, 50.0));
   std::shared_ptr<myShapes::Shape> pr2(new myShapes::Rectangle(100.0, 25.0));
   std::shared_ptr<myShapes::Shape> pr3(new myShapes::Rectangle(100.0, 25.0));
   std::shared_ptr<myShapes::Shape> pc(new myShapes::Circle(50.0));
 
   viewer.addForPainting(pt);
   viewer.addForPainting(pc);
   viewer.addForPainting(pr2);
   viewer.addForPainting(pr3);
 
   viewer.show();
 
   pc->setRadius(25.0);
 
   return a.exec();
}
 


Название: Re: Как писать ООП программы?
Отправлено: Johnik от Февраль 28, 2014, 18:40
Потому как базовый класс Shape не знает метода setRadius()


Название: Re: Как писать ООП программы?
Отправлено: gil9red от Февраль 28, 2014, 18:43
Потому как базовый класс Shape не знает метода setRadius()
И по логике, не обязан, ибо не у всех фигур есть радиус :)


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 28, 2014, 18:48
Потому как базовый класс Shape не знает метода setRadius()
То конечно хороший вопрос, но пусть сначала вынесет генерацию из конструктора и хоть как-то наладит взаимодействие классов. А то пока только песиков наводит.


Название: Re: Как писать ООП программы?
Отправлено: Johnik от Февраль 28, 2014, 18:50
Пусть учится


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 28, 2014, 18:54
Получается, что при передачи в функцию через указатель на базовый класс полиморфизм работает, а если в том же блоке, то нет? Непонятно...


Название: Re: Как писать ООП программы?
Отправлено: Johnik от Февраль 28, 2014, 18:58
...а если в том же блоке...
что в том же блоке?


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 28, 2014, 19:05
Я имею виду, что объявляем указатель на базовый класс и инициализируем его объектом производного:

Код
C++ (Qt)
   std::shared_ptr<myShapes::Shape> pc(new myShapes::Circle(50.0));
   pc->setRadius(25.0);
 

И это не работает. А если так же с функцией, то работает:

Код
C++ (Qt)
void Viewer::draw(std::shared_ptr<myShapes::Shape> ps, int xOffset, int yOffset)
 


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 28, 2014, 19:21
А если так же с функцией, то работает:
Да ладно. ;)
Попробуйте там вызвать setRadius.

Если вы хотите пользоваться частным (не общим) функционалом производного класса, то придется приводить указатель к этому классу.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 28, 2014, 19:50
Значит, я полиморфизм неправильно понял. Видимо, перепутал с ситуацией, когда в производном переопределяется функция с ключевым словом "virtual".

Статический полиморфизм (с шаблонами) пока в сторону. Нужно лучше динамический проработать.

А как привести?

Код
C++ (Qt)
(myShapes::Shape)pc->setRadius(25.0);
 


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 28, 2014, 19:53
А как привести?
std::dynamic_pointer_cast<T>( ptr );

И лучше сишные касты оставить для си.


Название: Re: Как писать ООП программы?
Отправлено: gil9red от Февраль 28, 2014, 20:02
А как привести?
std::dynamic_pointer_cast<T>( ptr );

И лучше сишные касты оставить для си.

Правило 27 :) (http://www.e-reading.bz/chapter.php/1002058/69/Mayers_-_Effektivnoe_ispolzovanie_CPP.html)
Цитировать
Предпочитайте приведения в стиле C++ старому стилю. Их легче увидеть, и они более избирательны.


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 28, 2014, 20:05
Код
C++ (Qt)
void Viewer::draw(std::shared_ptr<myShapes::Shape> ps, int xOffset, int yOffset)
 
Сама по себе эта запись ничем не ошибочна, но ЗАЧЕМ программист так делал? Если (допустим) я не в теме, то решил бы просто:

Агв, вероятно Shape может быть создана временно и уже нигде не хранится, будет удалена автоматычно после отрисовки.  

Если же программист не планировал этого то ему следовало ограничиться простым
Код
void Viewer::draw(Shape * ps, int xOffset, int yOffset)
 
Не засоряя мозги ни себе ни людям. Принцип "кашу маслом не испортишь" в программировании неприменим


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 28, 2014, 20:07
но ЗАЧЕМ программист так делал?
Потому что программист использует shared_ptr. :)

Если (допустим) я не в теме, то...
нужно пойти подучиться.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 28, 2014, 20:15
А как привести?
std::dynamic_pointer_cast<T>( ptr );

И лучше сишные касты оставить для си.


Огромное спасибо! Работает!

Вот что-то радиус не меняет.  :(

Правило 27 :) (http://www.e-reading.bz/chapter.php/1002058/69/Mayers_-_Effektivnoe_ispolzovanie_CPP.html)
Цитировать
Предпочитайте приведения в стиле C++ старому стилю. Их легче увидеть, и они более избирательны.

Спасибо! Я пропустил это правило. :) Не подряд читаю. А только если что-то нужно. Вот сейчас нужно, и надо прочитать :)


Агв, вероятно Shape может быть создана временно и уже нигде не хранится, будет удалена автоматычно после отрисовки.  

Если же программист не планировал этого то ему следовало ограничиться простым
Код
void Viewer::draw(Shape * ps, int xOffset, int yOffset)
 
Не засоряя мозги ни себе ни людям. Принцип "кашу маслом не испортишь" в программировании неприменим

В моём viewer'e хранится массив указателей на внешние объекты. Пользователь может вызвать delete для внешних объектов. shared_ptr спасает тем, что в нём есть счётчик ссылок. Пока есть хоть одна ссылка на объект, то он не удалится.

Viewer'у необходимо перерисовывать пока программа работает. Вот ситуация, когда объекты удалились до их показа:

Код
C++ (Qt)
int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
 
   Viewer viewer;
   viewer.resize(400, 200);
 
   myShapes::Shape *pr1 = new myShapes::Rectangle(5.0, 10.0);
   myShapes::Shape *pr2 = new myShapes::Rectangle(5.0, 10.0);
   myShapes::Shape *pr3 = new myShapes::Rectangle(5.0, 10.0);
   myShapes::Shape *pr4 = new myShapes::Rectangle(5.0, 10.0);
   myShapes::Shape *pr5 = new myShapes::Rectangle(5.0, 10.0);
 
   viewer.addForPainting(*pr1);
   viewer.addForPainting(*pr2);
   viewer.addForPainting(*pr3);
   viewer.addForPainting(*pr4);
   viewer.addForPainting(*pr5);
 
   delete pr1;
   delete pr2;
   delete pr3;
   delete pr4;
   delete pr5;
 
   viewer.show();
   return a.exec();
}
 


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 28, 2014, 20:19
И лучше сишные касты оставить для си.
Предпочитайте приведения в стиле C++ старому стилю. Их легче увидеть, и они более избирательны.
Также очень популярное правило лишь по той причине что.. "его легко выучить"  :) Ну правда, стоит лишь запомнить "С приведение = плохо. С++ приведение = хорошо" - и все (якобы) освоил :)  В действительности "С приведение" с полным правом может считаться "expert mode" - все оно прекрасно сделает, вот только если неправильно - нет ошибки, ничего не скажет. Ну так ведь на то и "эксперт"  :)


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 28, 2014, 20:21
Вот что-то радиус не меняет.  :(
Так он и не будет. У вас весь просчет окружности происходит в конструкторе. Вам нудно вынести его в отдельный метод и вызывать его после любого изменения радиуса.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 28, 2014, 20:25
Вот что-то радиус не меняет.  :(
Так он и не будет. У вас весь просчет окружности происходит в конструкторе. Вам нудно вынести его в отдельный метод и вызывать его после любого изменения радиуса.

А-а-а точно! :) Большущее спасибо! :)


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 28, 2014, 20:26
Viewer'у необходимо перерисовывать пока программа работает. Вот ситуация, когда объекты удалились до их показа:
И что, удаленные объекты все равно рисуются ???
Ну хз, может в радиофизике "так надо" (др разумного объяснения не вижу)


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 28, 2014, 20:33
Нет конечно :)

Это пример плохого кода. Их можно удалить и после показа, тогда viewer'у тоже нечего будет рисовать.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 28, 2014, 20:51
А с shared_ptr указатели не нужно удалять, и viewer'у это наруку :)

Код
C++ (Qt)
#include <QApplication>
#include "shape.h"
#include "rectangle.h"
#include "circle.h"
#include "triangle.h"
#include "viewer.h"
#include <utility>
#include <iostream>
#include <memory>
#include <QDebug>
 
int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
 
   Viewer viewer;
   viewer.resize(400, 200);
 
   std::shared_ptr<myShapes::Shape> pr2(new myShapes::Rectangle(100.0, 25.0));
   std::shared_ptr<myShapes::Shape> pr3(new myShapes::Rectangle(100.0, 25.0));
   std::shared_ptr<myShapes::Shape> pt(new myShapes::Triangle(30.0, 50.0));
   std::shared_ptr<myShapes::Shape> pc(new myShapes::Circle(50.0));
 
   viewer.addForPainting(pt);
   viewer.addForPainting(pc);
   viewer.addForPainting(pr2);
   viewer.addForPainting(pr3);
 
   viewer.show();
 
   std::dynamic_pointer_cast<myShapes::Circle>(pc)->setRadius(25.0);
 
   return a.exec();
}
 


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 28, 2014, 21:04
А вот по поводу перерасчёта координат при изменении радиуса. Я должен очистить массив координат и заполнить поновой.

Может же возникнуть такая ситуация, что viewer узнает размер массива координат и начнёт считывать точки. А вот в это время пользователь изменит радиус. Массив координат очистится. И viewer обратится по неправильному индексу.


Название: Re: Как писать ООП программы?
Отправлено: Igors от Февраль 28, 2014, 21:09
А с shared_ptr указатели не нужно удалять, и viewer'у это наруку :)
??? Поясните идею. Вот допустим радиус Circle изменился - надо перерисовать сцену с учетом нового радиуса. Причем здесь удалять/не удалять? Ведь число рисуемых объектов никак не изменилось


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 28, 2014, 21:12
А с shared_ptr указатели не нужно удалять, и viewer'у это наруку :)
??? Поясните идею. Вот допустим радиус Circle изменился - надо перерисовать сцену с учетом нового радиуса. Причем здесь удалять/не удалять? Ведь число рисуемых объектов никак не изменилось

Я не понял вопроса :)


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 28, 2014, 21:37
Ну правда, стоит лишь запомнить "С приведение = плохо. С++ приведение = хорошо" - и все (якобы) освоил :)
Вам нужно переставать смотреть видеокурсы "С++ за 5 дней". :)
Вроде вы уже наскакивали на виртуальное наследование.

В действительности "С приведение" с полным правом может считаться "expert mode" - все оно прекрасно сделает, вот только если неправильно - нет ошибки, ничего не скажет. Ну так ведь на то и "эксперт"  :)
А здесь можно тешить себя как угодно. Можете даже считать его "God mode". :)


Название: Re: Как писать ООП программы?
Отправлено: Old от Февраль 28, 2014, 21:39
Может же возникнуть такая ситуация, что viewer узнает размер массива координат и начнёт считывать точки. А вот в это время пользователь изменит радиус. Массив координат очистится. И viewer обратится по неправильному индексу.
Такая ситуация возможна только, если программа использует несколько потоков. Но тут и средства будут другие.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Февраль 28, 2014, 21:53
Может же возникнуть такая ситуация, что viewer узнает размер массива координат и начнёт считывать точки. А вот в это время пользователь изменит радиус. Массив координат очистится. И viewer обратится по неправильному индексу.
Такая ситуация возможна только, если программа использует несколько потоков. Но тут и средства будут другие.

Да, точно! Получается, что когда мы внутри функции отрисовки, то ничего другого выполняться не будет. И так же, когда координаты генерируем и в это время нужно перерисовать, то код отрисовки "будет ждать" пока не выполнится генерация координат.


Название: Re: Как писать ООП программы?
Отправлено: Johnik от Март 01, 2014, 00:13
Значит, я полиморфизм неправильно понял. Видимо, перепутал с ситуацией, когда в производном переопределяется функция с ключевым словом "virtual".

Статический полиморфизм (с шаблонами) пока в сторону. Нужно лучше динамический проработать.

А как привести?

Код
C++ (Qt)
(myShapes::Shape)pc->setRadius(25.0);
 
Предлагаю пока оставить приведение типов в покое и проработать все без него, тем более оно тут пока и не нужно.
Вообще стоит понять, где нужен полиморфизм, а где нет.

Ваш код можно написать примерно так:
Код:
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
 
    Viewer viewer;
    viewer.resize(400, 200);
 
    myShapes::Rectangle pr2(100.0, 25.0);
    myShapes::Rectangle pr3(100.0, 25.0);
    myShapes::Triangle pt(30.0, 50.0);
    myShapes::Circle pc(50.0);
 
    viewer.addForPainting(&pt);      // объявление должно выглядеть так: void Viewer::addForPainting(myShapes::Shape* shape)
    viewer.addForPainting(&pc);
    viewer.addForPainting(&pr2);
    viewer.addForPainting(&pr3);
 
    viewer.show();
 
    // и тогда код без проблем можно будет написать:
    //std::dynamic_pointer_cast<myShapes::Circle>(pc)->setRadius(25.0);
    pc.setRadius(25.0);

    return a.exec();
}


Название: Re: Как писать ООП программы?
Отправлено: Old от Март 01, 2014, 03:19
Ваш код можно написать примерно так:
Ну если цель только избавиться от приведения, то достаточно в коде ТС просто изменить тип указателя:

Код
C++ (Qt)
#include <QApplication>
#include "shape.h"
#include "rectangle.h"
#include "circle.h"
#include "triangle.h"
#include "viewer.h"
#include <utility>
#include <iostream>
#include <memory>
#include <QDebug>
 
int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
 
   Viewer viewer;
   viewer.resize(400, 200);
 
   std::shared_ptr<myShapes::Shape> pr2(new myShapes::Rectangle(100.0, 25.0));
   std::shared_ptr<myShapes::Shape> pr3(new myShapes::Rectangle(100.0, 25.0));
   std::shared_ptr<myShapes::Shape> pt(new myShapes::Triangle(30.0, 50.0));
   std::shared_ptr<myShapes::Circle> pc(new myShapes::Circle(50.0));
 
   viewer.addForPainting(pt);
   viewer.addForPainting(pc);
   viewer.addForPainting(pr2);
   viewer.addForPainting(pr3);
 
   viewer.show();
 
   pc->setRadius(25.0);
 
   return a.exec();
}
 


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Март 01, 2014, 07:41
std::shared_ptr<myShapes::Circle> pc(new myShapes::Circle(50.0));
myShapes::Circle pc(50.0);

Эти два примера абсолютно эквивалентные. В первом - объект создаётся динамически (из кучи), во втором - статически (в стеке).

Теперь я понял, что объекты не обязаны иметь статический тип Shape для передачи в функцию, которая принимает указатель на объект базового класса.


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Март 01, 2014, 07:54
Я понял, что в моём примере идеально подходят умные указатели (shared_ptr). Viewer, принимая указатели на внешние объекты, может быть спокоен, что объект действительно существует (кстати, должен ли он проверять непустой ли указатель?) Допустим, я решил не использовать умные указатели и единственный способ заставить viewer рисовать - это не удалять объекты выделенные из кучи. Если я точно знаю, что объектов будет не слишком много, то могу ли я себе позволить не удалять объекты выделенные динамически? Даже с умными указателями, объекты просуществуют столько же, как если не удалять с помощью delete. Вот так:

Код
C++ (Qt)
#include <QApplication>
#include "shape.h"
#include "rectangle.h"
#include "circle.h"
#include "triangle.h"
#include "viewer.h"
#include <utility>
#include <memory>
#include <QDebug>
 
int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
 
   Viewer viewer;
   viewer.resize(400, 200);
 
   myShapes::Triangle *pt = new myShapes::Triangle(30.0, 50.0);
   myShapes::Circle *pc = new myShapes::Circle(50.0);
   myShapes::Rectangle *pr1 = new myShapes::Rectangle(100.0, 25.0);
   myShapes::Rectangle *pr2(new myShapes::Rectangle(100.0, 25.0));
 
//    std::shared_ptr<myShapes::Triangle> pt(new myShapes::Triangle(30.0, 50.0));
//    std::shared_ptr<myShapes::Circle> pc(new myShapes::Circle(50.0));
//    std::shared_ptr<myShapes::Rectangle> pr1(new myShapes::Rectangle(100.0, 25.0));
//    std::shared_ptr<myShapes::Rectangle> pr2(new myShapes::Rectangle(100.0, 25.0));
 
   viewer.addForPainting(pt);
   viewer.addForPainting(pc);
   viewer.addForPainting(pr1);
   viewer.addForPainting(pr2);
 
   viewer.show();
 
   pc->setRadius(25.0);
 
   return a.exec();
}
 


Название: Re: Как писать ООП программы?
Отправлено: Old от Март 01, 2014, 09:21
Я понял, что в моём примере идеально подходят умные указатели (shared_ptr).
Как и для всего остального. :)

(кстати, должен ли он проверять непустой ли указатель?)
Конечно. Пользователь класса Viewer может легко передать пустой указатель, поэтому лучше всего проверять указатель при добавлении объекта в методе addForPainting.

могу ли я себе позволить не удалять объекты выделенные динамически?
Сможете, главное не забыть удалить их потом.

Воспользуйтесь typedef'ами, с ними значительно понятней.
Код
C++ (Qt)
#include <QApplication>
#include "shape.h"
#include "rectangle.h"
#include "circle.h"
#include "triangle.h"
#include "viewer.h"
#include <utility>
#include <memory>
#include <QDebug>
 
namespace myShapes
{
 
typedef std::shared_ptr<Triangle> TrianglePtr;
typedef std::shared_ptr<Circle> CirclePtr;
typedef std::shared_ptr<Rectangle> RectanglePtr;
 
}
 
using namespace myShapes;
 
int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
 
   Viewer viewer;
   viewer.resize(400, 200);
 
   TrianglePtr pt(new myShapes::Triangle(30.0, 50.0));
   CirclePtr pc(new myShapes::Circle(50.0));
   RectanglePtr pr1(new myShapes::Rectangle(100.0, 25.0));
   RectanglePtr pr2(new myShapes::Rectangle(100.0, 25.0));
 
   viewer.addForPainting(pt);
   viewer.addForPainting(pc);
   viewer.addForPainting(pr1);
   viewer.addForPainting(pr2);
 
   viewer.show();
 
   pc->setRadius(25.0);
 
   return a.exec();
}
 


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Март 01, 2014, 09:30
Воспользуйтесь typedef'ами, с ними значительно понятней.
Вот то, что нужно! Видно сразу силу пространств имём, умных указателей и typedef'ов :)

могу ли я себе позволить не удалять объекты выделенные динамически?
Сможете, главное не забыть удалить их потом.

А где их тогда удалить в этом примере:

Код
C++ (Qt)
#include <QApplication>
#include "shape.h"
#include "rectangle.h"
#include "circle.h"
#include "triangle.h"
#include "viewer.h"
#include <utility>
#include <memory>
#include <QDebug>
 
int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
 
   Viewer viewer;
   viewer.resize(400, 200);
 
   myShapes::Triangle *pt = new myShapes::Triangle(30.0, 50.0);
   myShapes::Circle *pc = new myShapes::Circle(50.0);
   myShapes::Rectangle *pr1 = new myShapes::Rectangle(100.0, 25.0);
   myShapes::Rectangle *pr2(new myShapes::Rectangle(100.0, 25.0));
 
//    std::shared_ptr<myShapes::Triangle> pt(new myShapes::Triangle(30.0, 50.0));
//    std::shared_ptr<myShapes::Circle> pc(new myShapes::Circle(50.0));
//    std::shared_ptr<myShapes::Rectangle> pr1(new myShapes::Rectangle(100.0, 25.0));
//    std::shared_ptr<myShapes::Rectangle> pr2(new myShapes::Rectangle(100.0, 25.0));
 
   viewer.addForPainting(pt);
   viewer.addForPainting(pc);
   viewer.addForPainting(pr1);
   viewer.addForPainting(pr2);
 
   viewer.show();
 
   pc->setRadius(25.0);
 
   return a.exec();
}
 


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Март 01, 2014, 09:36
Воспользуйтесь typedef'ами, с ними значительно понятней.
Код
C++ (Qt)
#include <QApplication>
#include "shape.h"
#include "rectangle.h"
#include "circle.h"
#include "triangle.h"
#include "viewer.h"
#include <utility>
#include <memory>
#include <QDebug>
 
namespace myShapes
{
 
typedef std::shared_ptr<Triangle> TrianglePtr;
typedef std::shared_ptr<Circle> CirclePtr;
typedef std::shared_ptr<Rectangle> RectanglePtr;
 
}
 
using namespace myShapes;
 
int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
 
   Viewer viewer;
   viewer.resize(400, 200);
 
   TrianglePtr pt(new myShapes::Triangle(30.0, 50.0));
   CirclePtr pc(new myShapes::Circle(50.0));
   RectanglePtr pr1(new myShapes::Rectangle(100.0, 25.0));
   RectanglePtr pr2(new myShapes::Rectangle(100.0, 25.0));
 
   viewer.addForPainting(pt);
   viewer.addForPainting(pc);
   viewer.addForPainting(pr1);
   viewer.addForPainting(pr2);
 
   viewer.show();
 
   pc->setRadius(25.0);
 
   return a.exec();
}
 

Уточнить имена нужно:
Код
C++ (Qt)
   myShapes::TrianglePtr pt(new myShapes::Triangle(30.0, 50.0));
   myShapes::CirclePtr pc(new myShapes::Circle(50.0));
   myShapes::RectanglePtr pr1(new myShapes::Rectangle(100.0, 25.0));
   myShapes::RectanglePtr pr2(new myShapes::Rectangle(100.0, 25.0));
 


Название: Re: Как писать ООП программы?
Отправлено: Old от Март 01, 2014, 09:53
А где их тогда удалить в этом примере:
Например, в деструкторе Viewer.


Название: Re: Как писать ООП программы?
Отправлено: Old от Март 01, 2014, 09:54
Уточнить имена нужно:

Мы же разрешили это пространство имен:
Код
C++ (Qt)
using namespace myShapes;
 


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Март 01, 2014, 10:03
Уточнить имена нужно:
Мы же разрешили это пространство имен:
Код
C++ (Qt)
using namespace myShapes;
 

А да, извените, не заметил :)

А где их тогда удалить в этом примере:
Например, в деструкторе Viewer.

Да, похоже, это правильный ответ. Но что плохого в том, что мы НЕ будем применять delete к объектам? В моём приложении объекты в любом случае живут до конца, хоть это умный указатель, хоть особождение в деструкторе Viewer. Ведь при завершении приложения вся память возвращается системе.

Код
C++ (Qt)
#include <QApplication>
#include "shape.h"
#include "rectangle.h"
#include "circle.h"
#include "triangle.h"
#include "viewer.h"
#include <utility>
#include <memory>
#include <QDebug>
 
int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
 
   Viewer viewer;
   viewer.resize(400, 200);
 
   myShapes::Triangle *pt = new myShapes::Triangle(30.0, 50.0);
   myShapes::Circle *pc = new myShapes::Circle(50.0);
   myShapes::Rectangle *pr1 = new myShapes::Rectangle(100.0, 25.0);
   myShapes::Rectangle *pr2(new myShapes::Rectangle(100.0, 25.0));
 
//    std::shared_ptr<myShapes::Triangle> pt(new myShapes::Triangle(30.0, 50.0));
//    std::shared_ptr<myShapes::Circle> pc(new myShapes::Circle(50.0));
//    std::shared_ptr<myShapes::Rectangle> pr1(new myShapes::Rectangle(100.0, 25.0));
//    std::shared_ptr<myShapes::Rectangle> pr2(new myShapes::Rectangle(100.0, 25.0));
 
   viewer.addForPainting(pt);
   viewer.addForPainting(pc);
   viewer.addForPainting(pr1);
   viewer.addForPainting(pr2);
 
   viewer.show();
 
   pc->setRadius(25.0);
 
   return a.exec();
}
 


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Март 01, 2014, 14:14
Плюс shared_ptr ещё в том, что пользователю (класса Viewer) не нужно бояться, что viewer удалит объект через указатель :)


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Март 02, 2014, 08:12
Помогите, пожалуйста, разобраться почему не стирается старое изображение?

(http://i.pixs.ru/storage/2/1/8/142png_4847648_11075218.png)

Когда я меняю радиус:

main.cpp
Код
C++ (Qt)
#include <QApplication>
#include "shape.h"
#include "rectangle.h"
#include "circle.h"
#include "triangle.h"
#include "viewer.h"
#include <utility>
#include <memory>
#include <QDebug>
 
namespace myShapes
{
 
typedef std::shared_ptr<Triangle> TrianglePtr;
typedef std::shared_ptr<Circle> CirclePtr;
typedef std::shared_ptr<Rectangle> RectanglePtr;
 
}
 
using namespace myShapes;
 
int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
 
   Viewer viewer;
   viewer.resize(400, 200);
 
   TrianglePtr pt(new myShapes::Triangle(30.0, 50.0));
   CirclePtr pc(new myShapes::Circle(50.0));
   RectanglePtr pr1(new myShapes::Rectangle(100.0, 25.0));
   RectanglePtr pr2(new myShapes::Rectangle(100.0, 25.0));
 
   viewer.addForPainting(pt);
   viewer.addForPainting(pc);
   viewer.addForPainting(pr1);
   viewer.addForPainting(pr2);
 
   viewer.show();
 
   pc->setRadius(25.0);
 
   return a.exec();
}
 

Как в функции перерисовки стереть старое изображение?

viewer.cpp
Код
C++ (Qt)
#include "viewer.h"
#include <utility>
#include <QDebug>
 
Viewer::Viewer(QWidget* pwgt) :
   QGLWidget(pwgt)
{
}
 
void Viewer::draw(std::shared_ptr<myShapes::Shape> ps, int xOffset, int yOffset)
{
   glPointSize(2.0);
   glBegin(GL_LINE_LOOP);
   glColor3f(0.0, 0.0, 0.0);
   for (int i = 0; i < ps->amountOfPoints(); ++i) {
       std::pair<int, int> point = ps->point(i);
//        qDebug() << "x = " << point.first << "; xOffset = " << xOffset;
//        qDebug() << "y = " << point.second << "; yOffset = " << yOffset;
       int x = point.first + xOffset;
       int y = point.second + yOffset;
       glVertex2f(x, y);
   }
//    qDebug() << "";
   glEnd();
}
 
/*virtual*/ void Viewer::initializeGL()
{
   qglClearColor(Qt::white);
}
 
/*virtual*/ void Viewer::resizeGL(int nWidth, int nHeight)
{
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glViewport(0, 0, (GLint)nWidth, (GLint)nHeight);
   glOrtho(0, 400, 200, 0, -1, 1);
}
 
/*virtual*/ void Viewer::paintGL()
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
   int xGeneralOffset = 50;
   int yGeneralOffset = 100;
   int xOffset = 0;
   int yOffset = 0;
   for (std::size_t i = 0; i < m_pshapes.size(); ++i) {
       if (( (i+1) % 2) != 0) { // Odd, one-based
           xOffset = xGeneralOffset;
           yOffset += yGeneralOffset;
       } else {
           xOffset = xGeneralOffset + 150;
       }
       if (i == 0 || i == 1) {
           yOffset = 50;
       }
       draw(m_pshapes[i], xOffset, yOffset);
   }
}
 


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Март 02, 2014, 08:17
Вот весь проект: https://github.com/8Observer8/Shapes/tree/drawingShapes


Название: Re: Как писать ООП программы?
Отправлено: Igors от Март 02, 2014, 08:25
Но что плохого в том, что мы НЕ будем применять delete к объектам? В моём приложении объекты в любом случае живут до конца, хоть это умный указатель, хоть особождение в деструкторе Viewer. Ведь при завершении приложения вся память возвращается системе.
Так развивайте архитектуру, заводите класс "сцена", который будет хранить созданные объекты, загружать/выгружать их в файл и др. В этом и состоит основная работа: спроектировать хорошие классы, наладить их четкое взаимодействие. А лепить глобалы в main то так, "поменяйте мне подгузник"  :)


Название: Re: Как писать ООП программы?
Отправлено: Igors от Март 02, 2014, 08:38
Помогите, пожалуйста, разобраться почему не стирается старое изображение?
Circle::calcCoordinates должна сначала очистить вектор точек, а потом его заполнить

Как в функции перерисовки стереть старое изображение?
По умолчанию машина рисования сама стирает все перед каждым новым paint


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Март 02, 2014, 09:00
Igors, спасибо огромное! Исправлю ошибки. Всё таки есть ещё куда развивать этот проектик :)

P.S. Я хочу чуть позже реализовать рисование в 3D. Мне кажется, что архитектура моего приложения несколько изменится. Как я понял в OpenGL все объекты состоят из треугольников и\или квадратов. Мои классы фигур будут хранить больше координат. Получается, что для круга мне нужно будет окружность задавать через координаты треугольников  :-\


Название: Re: Как писать ООП программы?
Отправлено: Igors от Март 02, 2014, 09:31
Всё таки есть ещё куда развивать этот проектик :)
Поверьте, Вы его практически даже не начали  :)

P.S. Я хочу чуть позже реализовать рисование в 3D. Мне кажется, что архитектура моего приложения несколько изменится. Как я понял в OpenGL все объекты состоят из треугольников и\или квадратов. Мои классы фигур будут хранить больше координат. Получается, что для круга мне нужно будет окружность задавать через координаты треугольников  :-\
Ладно, поговорим об архитектуре. Вот у Вас Circle имеет radius. На первый взгляд это не вызывает сомнений, ну как же круг без радиуса? Это же его "неотъемлемое" свойство. Однако большой круг или маленький - все равно "круг", форма-то (shape) одна. Теперь более конкретно: каждый раз, создавая Circle мы создаем и массив координат для него, а в перспективе этих данных еще больше. Создал юзер напр 1000 объектов - и у нас 1000 массивов (вероятно совершенно одинаковых). А ведь легко сделать умнее: хранить один "эталонный" массив для радиуса = 1, и в момент рисования скалить его до нужного. Да, но тогда выходит сам shape еще недостаточен, нужен еще класс (вот он и будет хранить радиус), а какой? Впрочем у Вас и так shape недостаточен, позицию он не хранит, пока задаете от фонаря.

Вообще программист всегда хорошо знает чего он хочет от класса, что класс должен уметь и чего нет (последнее часто более важно). Записать это словами в файл - совсем не вредно. А Вы заболтили все проектирование и бегаете с какой-то бижутерией/аксессуарами  :)


Название: Re: Как писать ООП программы?
Отправлено: 8Observer8 от Апрель 03, 2014, 13:42
Многие пишут, что это самая лучшая книга по ООП (объектно-ориентированному программированию и проектированию). Я только начал. Написано просто. Может кто-то ещё не читал и кому-то эта информация пригодится.

Приёмы объектно-ориентированного проектирования. Паттерны проектирования
Год: 2010
Автор: Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж.
Язык: Русский
Количество страниц: 366
Ссылка: http://rutracker.org/forum/viewtopic.php?t=3193196

Описание: В предлагаемой книге описываются простые и изящные решения типичных задач, возникающих в объектно-ориентированном проектировании. Паттерны появились потому, что многие разработчики искали пути повышения гибкости и степени повторного использования своих программ. Найденные решения воплощены в краткой и легко применимой на практике форме. Авторы излагают принципы использования паттернов проектирования и приводят их каталог. Таким образом, книга одновременно решает две задачи. Во-первых, здесь демонстрируется роль паттернов в создании архитектуры сложных систем. Во-вторых, применяя содержащиеся в справочнике паттерны, проектировщик сможет с легкостью разрабатывать собственные приложения.

Издание предназначено как для профессиональных разработчиков, так и для программистов, осваивающих объектно-ориентированное проектирование.