ООП и C++

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

Модератор: Преподаватели

ООП и C++

Сообщение Telnov » 05 сен 2011, 06:58

Классический полиморфный контейнер рассматривается на лекциях по ООП:
ScreenHunter_21 Sep. 05 07.56.gif
ScreenHunter_21 Sep. 05 07.56.gif [ 13.16 KiB | Просмотров: 76997 ]


Код: выделить все
// Полиморфный контейнер. Способ №1 (от Страуструпа).
#include <list>
#include <iostream>
#include <stdio.h>
using namespace std;
class Polymorph {                                 // абстрактный класс
public:
   virtual Polymorph& get()=0;         // чистая виртуальная функция
};

class Int:public Polymorph {                 // класс целых чисел, потомок Polymorph
   int X;
public:
   Int(int x=0) { X=x; }
   Int& get() { cout << "Object Int: " << X << endl; return *this; }
};

class Str:public Polymorph {                // класс строк, потомок Polymorph
   char* S;
public:
   Str(char* s="xyz") { S=new char[strlen(s)+1]; strcpy(S,s); }
   Str& get() { cout << "Object Str: " << S << endl; return *this; }
};

class Point:public Polymorph {             // класс точек на плоскости, потомок Polymorph
   int X, Y;
public:
   Point(int x=0, int y=0) { X=x; Y=y; }
   Point& get() { cout << "Object Point: X= " << X << ", Y= " << Y<< endl; return *this; }
};

int main() {
   Int n; Str m; Point k;                   // три объекта: "целые числа", "строки", "точки на плоскости"
   list<Polymorph*> c;                       // контейнер указателей на объекты класса Polymorph
   c.push_front(&n);
   c.push_front(&m);
   c.push_front(&k);
   for(list<Polymorph*>::iterator p=c.begin();p!=c.end(); p++) (*p)->get();
   system("pause");         // с помощью итератора смотрим, как работает полиморфный контейнер
}                                     

Имеется ещё около десятка способов, отличных от приведенного, чтобы создать полиморфный контейнер.
Кто-нибудь владеет этими способами? Призовые 5 баллов к рейтингу студента за каждый новый способ.
В.Тельнов
Аватар пользователя
Telnov
Преподаватель
 
Сообщений: 324
Изображения: 5
Зарегистрирован: 05 сен 2011, 00:19
Благодарил (а): 1 раз.
Поблагодарили: 10 раз.

Re: ООП и C++

Сообщение Иван » 23 ноя 2013, 22:21

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

Полиморфизм бывает:
1)Параметрическим;
2)Ситуативным (ad hoc);
3)Полиморфизм подтипов(включения).

А так же существует ряд паттернов для разных реализаций полиморфизма.

Ждите на неделе несколько способов. Они уже в разработке.
Аватар пользователя
Иван
Студент
 
Сообщений: 49
Зарегистрирован: 06 сен 2013, 08:41
Благодарил (а): 0 раз.
Поблагодарили: 0 раз.

Re: ООП и C++

Сообщение Telnov » 24 ноя 2013, 09:11

Хорошо, давайте, выкладывайте.
Для того форум и существует.
В.Тельнов
Аватар пользователя
Telnov
Преподаватель
 
Сообщений: 324
Изображения: 5
Зарегистрирован: 05 сен 2011, 00:19
Благодарил (а): 1 раз.
Поблагодарили: 10 раз.

Re: ООП и C++

Сообщение Иван » 24 ноя 2013, 19:52

Поскольку вопрос касается именно создания такого контейнера, то буду приводить все возможные примеры, в том числе исключающие работу с содержимым контейнера. Некоторым обывателям на форуме будет полезно знать, как делать не надо.
Аватар пользователя
Иван
Студент
 
Сообщений: 49
Зарегистрирован: 06 сен 2013, 08:41
Благодарил (а): 0 раз.
Поблагодарили: 0 раз.

Re: ООП и C++

Сообщение Иван » 24 ноя 2013, 19:58

Случай №1.

Создаем контейнер указателей на что угодно. Да, он будет хранить в себе указатели на всевозможные объекты, но обратиться к каким-либо методам возможности не будет. Написание такого кода приведет только к лишней трате времени. Кому интересно, смогут проверить неработоспособность способа совершив обход по контейнеру.

Код: выделить все
#include "stdafx.h"
#include <list>
#include <iostream>
#include <stdio.h>
using namespace std;


class INT      // Класс целых чисел
{
   int X ;
public:
   INT(int x = 123) { X = x ; }
    void  get() { cout << "INT = " << X << endl;}
};
 
class STR      // Класс строк
{
   char* S;
public:
   STR(char* s = "oop") { S = new char[strlen(s)+1]; strcpy(S,s);}
    void  get() { cout << "STR = " << S << endl;}
};


 
int main()
{
   list<void*> c; //Список пар указателей на объекты INT и STR
    INT Obj1;
   STR Obj2;
   c.push_front(&Obj1);
   c.push_front(&Obj2);
   system("pause");
};                 
Аватар пользователя
Иван
Студент
 
Сообщений: 49
Зарегистрирован: 06 сен 2013, 08:41
Благодарил (а): 0 раз.
Поблагодарили: 0 раз.

Re: ООП и C++

Сообщение Иван » 24 ноя 2013, 20:10

Случай №2.

«Curiously Recurring Template Pattern» (CRTP) — наиболее часто используемая альтернатива динамическому связыванию. CRTP используется для реализации статического полиморфизма. Статический полиморфизм достигает подобного эффекта благодаря виртуальным функциям, позволяя выбирать перегруженные методы в производных классах на этапе компиляции, а не во время выполнения. Используя CRTP, классы INT и STR наследуют шаблонный класс Base, реализующий их интерфейс. Для правильного удаления экземпляров производного класса, Base наследует класс Deletor, который определяет виртуальный деструктор. Однако у данного способа имеется существенный минус — невозможность обратиться к методам элементов контейнера, т. к. класс Delector не включает в себя методы производных классов. Но контейнр создается и заполняется.

Код: выделить все
#include "stdafx.h"
#include <list>
#include <iostream>
#include <stdio.h>
using namespace std;


class Deletor
{
public:
   virtual ~Deletor() {}
};


template<typename T>
class Base: public Deletor
{
public:
    void  Run() { return static_cast<T*>(this)->get(); }
};


class INT : public Base<INT>      // Класс целых чисел
{
   int X ;
public:
   INT(int x = 123) { X = x ; }
    void  get() { cout << "INT = " << X << endl;}
};
 
class STR : public Base<STR>      // Класс строк
{
   char* S;
public:
   STR(char* s = "oop") { S = new char[strlen(s)+1]; strcpy(S,s);}
    void  get() { cout << "STR = " << S << endl;}
};


 
int main()
{
   list<Deletor*> c; //Список указателей на объект Deletor
        INT Obj1;
   STR Obj2;
   c.push_front(&Obj1);
   c.push_front(&Obj2);
   Obj1.Run();
   Obj2.Run();
   system("pause");
};                 


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

Код: выделить все
#include "stdafx.h"
#include <list>
#include <iostream>
#include <stdio.h>
using namespace std;


class Deletor
{
public:
   virtual ~Deletor() {}
   virtual void Run() = 0 ;
};


template<typename T>
class Base: public Deletor
{
public:
    void  Run() { return static_cast<T*>(this)->get(); }
};


class INT : public Base<INT>      // Класс целых чисел
{
   int X ;
public:
   INT(int x = 123) { X = x ; }
    void  get() { cout << "INT = " << X << endl;}
};
 
class STR : public Base<STR>      // Класс строк
{
   char* S;
public:
   STR(char* s = "oop") { S = new char[strlen(s)+1]; strcpy(S,s);}
    void  get() { cout << "STR = " << S << endl;}
};


 
int main()
{
   list<Deletor*> c;  //Список указателей на объект Deletor
    INT Obj1;
   STR Obj2;
   c.push_front(&Obj1);
   c.push_front(&Obj2);
   for (list<Deletor*>::iterator it = c.begin() ; it!=c.end() ; it++ ) (*it)->Run(); //С  помощью итератора смотрим, как работает данный метод.
   system("pause");
};                 
Аватар пользователя
Иван
Студент
 
Сообщений: 49
Зарегистрирован: 06 сен 2013, 08:41
Благодарил (а): 0 раз.
Поблагодарили: 0 раз.

Re: ООП и C++

Сообщение Иван » 24 ноя 2013, 21:33

Случай №3.

Рассмотрим одну из возможных реализаций паттерна «Variant».
Приведу пример лишь для создания контейнера, т.к. класс, реализующий полиморфизм очень громоздкий и для полноценной работы с объектами, хранящимися в контейнере придется изрядно попотеть. Так же в коде приведу пример создания полиморфных объектов.

Небольшая прелюдия:

1)Цель паттерна — предоставить возможность осуществлять единообразную работу с разнотипными данными. Например, эти разнотипные данные можно хранить в одном контейнере.

2)Основной недостаток - воспользовавшись паттерном «Variant», программист освобождает компилятор от контроля типов и перетаскивает эту функцию на собственные плечи.

Код: выделить все
#include "stdafx.h"
#include "stdlib.h"
#include "list"
#include "memory"
#include "string"
#include <iostream>
using namespace std;



//Класс полиморфного объекта
class variant
{

public:

   //Шаблон перегрузки оператора = (используется при создании объекта)
    template <class T>
    variant& operator = (T const& t)
    {
        typedef type<T> assign_type;
        object = std::auto_ptr<assign_type>(new assign_type(t));
        return *this;
    }


    template <class T>
    operator T ()
    {
        typedef type<T> assign_type;
        assign_type& type = dynamic_cast<assign_type&>(*object);
        return type.get();
    }
   

private:

    class base
    {
    public:

        virtual ~base() {}
    };

    typedef std::auto_ptr<base> base_ptr;

    template <class T>
    class type : public base
    {
    public:

        type(T const& t)
        : object(t)
        {
        }

        T get() const
        {
            return object;
        }

    private:

        T object;
    };

    base_ptr object;

};

struct dummy
{
    int a;
    int b;
    int c;
};

int main()
{
    variant v1, v2, v3, v4;

    v1 = 2;
    v2 = 5.0f;
    v3 = string("Pot of gold");
    v4 = dummy();
    int         i = v1;
    float       f = v2;
    string      s = v3;
    dummy       d = v4;
   list<variant*> C ;
   C.push_front(&v1);
   C.push_front(&v2);
   C.push_front(&v3);
   C.push_front(&v4);
   system("pause");
   list<variant*>::iterator it;
    return 0;
}
Аватар пользователя
Иван
Студент
 
Сообщений: 49
Зарегистрирован: 06 сен 2013, 08:41
Благодарил (а): 0 раз.
Поблагодарили: 0 раз.

Re: ООП и C++

Сообщение Иван » 24 ноя 2013, 22:41

Случай №4.

Существует замечательная библиотека с именем boost .
Вот ссылка с руководством по ее установке для VS2010. http://evilcoderr.blogspot.ru/2013/01/boost-c-vs-2010.html
Полиморфизм обеспечивается автоматически. Создатели библиотеки сделали все за нас. В современном программировании зачастую использует ее.
Перейдем к главному.
any - безопасный общий контейнер для единичного значения любого типа.

Легок в использовании. Для его освоения достаточно базовых знаний языка.
Однако для вывода содержимого такого контейнера необходимо вручную регистрировать хранящиеся в нем типы, перегружать оператор (<<) и несколько других изюминок, что, естественно, я делать не буду, т.к. это смахивает на серьезную лабораторную работу.

В этом же примере приведу пример создания и обхода контейнера, при котором на экран консоли выведутся имена типов хранящихся в нем объектов.

Листинг кода:
Код: выделить все
#include "stdafx.h"
#include "list"
#include "stdlib.h"
#include "boost/any.hpp"
#include "iostream"
using namespace std;




int _tmain(int argc, _TCHAR* argv[])
{
   list<boost::any> c ;
   c.push_front(string("Polymorph"));
   c.push_front(5.2);
   c.push_front(10);
   for (list<boost::any>::iterator it = c.begin() ; it != c.end() ; it++) cout << (*it).type().name() << endl;
   system("pause");
   return 0;
}
Аватар пользователя
Иван
Студент
 
Сообщений: 49
Зарегистрирован: 06 сен 2013, 08:41
Благодарил (а): 0 раз.
Поблагодарили: 0 раз.

Пока неплохо

Сообщение Telnov » 24 ноя 2013, 22:48

Насчет предпоследнего. Так называемый паттерн Variant не относится к числу признанных в профессиональном сообществе паттернов.
Строго говоря, это вообще не паттерн, а попытка замахнуться на паттерн. Соответствующие определения читайте в лекциях по курсу СВП.

Тем не менее, в качестве варианта полиморфизма решение Variant обычно принимается, с некоторой натяжкой. Запишите себе в актив +5 призовых баллов.

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

Ваши выкладки небезынтересны. Продолжайте заниматься.
Всё подробно обсудим, когда закончатся лекции, т.е. в декабре.
В.Тельнов
Аватар пользователя
Telnov
Преподаватель
 
Сообщений: 324
Изображения: 5
Зарегистрирован: 05 сен 2011, 00:19
Благодарил (а): 1 раз.
Поблагодарили: 10 раз.

Re: ООП и C++

Сообщение Иван » 25 ноя 2013, 02:09

Случай №5.

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

Код: выделить все
class Polymorph //Абстрактный класс
{
public:
   virtual void get() = 0;
};

class INT : public Polymorph  //Класс контейнеров целых чисел
{
   list<int> c;
public:
   void Int(int x) {c.push_front(x);}
   void get() { for ( list<int>::iterator it = c.begin() ; it!=c.end() ; it++) cout << (*it) << endl;}
};

class STR : public Polymorph //Класс контейнеров строк
{
   list<char*> c;
public:
   void Str(char* s){ char* S = new char[strlen(s)+1]; strcpy(S, s); c.push_front(S);}
   void get(){for (list<char*>::iterator it=c.begin() ; it!=c.end() ; it++ ) cout << (*it) << endl;}
};


int _tmain(int argc, _TCHAR* argv[])
{
   INT I ;
   STR S ;
   for (int i = 1 ; i < 11 ; i++)  //Заполняем контейнер для целых
      I.Int(i);
        //Заполняем контейнер для строк
   S.Str("OOP");
   S.Str("POLYMORHIZM");
   S.Str("NASLEDOVANIE");
        //Создаем контейнер указателей на абстрактный класс
   list<Polymorph*> c;
   c.push_front(&I);
   c.push_front(&S);
        //Смотрим, как заработало
   for (list<Polymorph*>::iterator it = c.begin() ; it!=c.end() ; it++) (*it)->get();
   system("pause");
   return 0;
}



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

А еще сортировать легче.
Аватар пользователя
Иван
Студент
 
Сообщений: 49
Зарегистрирован: 06 сен 2013, 08:41
Благодарил (а): 0 раз.
Поблагодарили: 0 раз.

Re: ООП и C++

Сообщение Иван » 13 дек 2013, 22:35

Случай №6.

Сегодня поговорим о делегировании.

Описание из Википедии:
Делегирование (англ. Delegation) — основной шаблон проектирования, в котором объект внешне выражает некоторое поведение, но в реальности передаёт ответственность за выполнение этого поведения связанному объекту.

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

Код: выделить все

#include "stdafx.h"
#include "stdlib.h"
#include "list"
#include "iostream"
using namespace std;

class Polymorph   //Абстрактный класс
{
public:
   virtual void get() = 0; //чистая виртуальная функция
};

class INT : public Polymorph //Класс целых чисел, потомок Polymorph
{
   int X;
public:
   INT(int x) { X = x ;}            //Конструктор общего вида
   void get() {cout << X << endl ;} //Отображение на экране
};

class STR : public Polymorph //Класс строк, потомок Polymorph
{
   char* S;
public:
   STR(char* s) { S = new char[strlen(s)+1] ; strcpy(S , s) ;} //Конструктор общего вида
   void get() {cout << S << endl;}  //Отображение на экране
};

class SW : public Polymorph   //Класс SW - делегирует либо классу INT , либо классу STR
{
private: Polymorph* P ;   //Указатель на объект класса Polymorph
public:
   SW(int x): P (new INT(x) ) {};    //Конструктор общего вида(для класса INT)
   SW(char* s): P (new STR(s) ) {};  //Конструктор общего вида(для класса STR)
   void get(){P->get();}   //Отображение на экране

};

typedef list<SW> List;

int _tmain(int argc, _TCHAR* argv[])
{
   //Создаем объекты
   SW Ob1(1);
   SW Ob2("DELEGAT");
   SW Ob3("Learn Polymorph");
   SW Ob4("Next element is number 17");
   SW Ob5(17);
   List L;
   //Закладываем элементы в список
   L.push_back(Ob1);
   L.push_back(Ob2);
   L.push_back(Ob3);
   L.push_back(Ob4);
   L.push_back(Ob5);
   //С помощью итератора выводим на экран интересующую нас информацию.
   for (List::iterator it = L.begin() ; it!=L.end() ; it++) { it->get();}
   system ("pause");
   return 0;
}



От себя добавлю, что такой метод организации полиморфизма часто оказывается проигрышным по скорости работы.
Многие программисты считают его использование антипаттерном.
А вот лично мне кажется удобным способ создания объектов. Сказать честно, тот же пример Страуструпа выглядит гораздо изящнее, работает быстрее и даже код будет выглядеть компактнее.

С интересом выслушаю/прочитаю любые замечания по представленному варианту.

Предложенный метод несколько неуклюж, но как вариант может быть принят. Вам +5 призовых баллов.
Ссылаясь на паттерны (шаблоны проектирования софта), пожалуйста апеллируйте не к Интернету и к Википедии, а к авторитетным изданиям, например:

Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. Приемы объектно-ориентированного проектирования. Паттерны проектирования. – СПб: Питер, 2001. – 368 с.
(есть в Облачном кабинете, см. курс Специальные вопросы программирования).

В.Тельнов
Аватар пользователя
Иван
Студент
 
Сообщений: 49
Зарегистрирован: 06 сен 2013, 08:41
Благодарил (а): 0 раз.
Поблагодарили: 0 раз.

Re: ООП и C++

Сообщение Иван » 19 дек 2013, 00:28

Случай №7.

Не спится. Решил немного побаловаться с классами. Еще один простейший вариант ниже. Если уж и выбирать между структурами и объединениями, то с последними код будет работать эффективнее. Могут возникнуть проблемы с освобождением памяти. Постараюсь повозиться с деструктором еще.

Код: выделить все
#include "stdafx.h"
#include "stdlib.h"
#include "vector"
#include "iostream"
using namespace std;

class Polymorph //Класс объединений Polymorph
{   
   union //Объединение трех типов
   {
      int x1 ;
      double x2;
      char* s;
   } p;
   enum {a , b , c } ID;   //Перечисления возьмем за индекс
public:
   ~Polymorph(){}      //Деструктор
   Polymorph(int I){ p.x1 = I; ID = a;} //Конструктор под int
   Polymorph(double D) { p.x2 = D ; ID = b;} //Конструктор под double
   Polymorph(char* S){ p.s = new char[strlen(S)+1] ; strcpy(p.s , S) ; ID = c;} //Конструктор под char*

   void get()  //Метод для вывода в поток
   {
      switch(ID) //Проверяем состояние ID
      {
      case 0 : {cout << p.x1 ; break;}
      case 1 : {cout << p.x2 ;  break;}
      case 2 : {cout << p.s ; break;}
      }
      cout << endl;
   }
};



int _tmain(int argc, _TCHAR* argv[])
{
   //Создаем объекты
   Polymorph P1(1) , P2(125) , P3("UNION") , P4(13.123) , P5("I LIKE PROGRAMMING") ;
   typedef vector<Polymorph> Vec;
   Vec V;
   //Закладываем объекты в вектор(массив)
   V.push_back(P1);
   V.push_back(P2);
   V.push_back(P3);
   V.push_back(P4);
   V.push_back(P5);
   //Любуемся результатом работы
   for(Vec::iterator it = V.begin() ; it != V.end() ; it++) { it->get();}
      system("pause");
   return 0;
}
Аватар пользователя
Иван
Студент
 
Сообщений: 49
Зарегистрирован: 06 сен 2013, 08:41
Благодарил (а): 0 раз.
Поблагодарили: 0 раз.

Re: ООП и C++

Сообщение theslime » 03 июн 2019, 22:57

Изображение

Почему fopen может выдавать NULLptr но не выдавать ошибки?

Внимательно изучаем документацию:
http://www.c-cpp.ru/content/fopen
http://www.c-cpp.ru/content/ferror
и читаем лекцию:
http://docs.google.com/file/d/0B0jk0QU2 ... E9zZXd3V3c

В.Тельнов
Аватар пользователя
theslime
Студент
 
Сообщений: 3
Зарегистрирован: 05 фев 2019, 17:48
Благодарил (а): 4 раз.
Поблагодарили: 1 раз.


Вернуться в Вопросы программирования

Кто сейчас на форуме

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 1