Корректное и удобное использование указателей и ссылок.
От: Andreev  
Дата: 19.07.05 08:14
Оценка:
У меня возникли сомнения: в своем проекте, который, должен возвращать по текущей позиции указателя на экране элемент, написал следующий код:
enum EL_TYPES
{
  EL_NULL, EL_TEXT, EL_LABEL, EL_EDIT, EL_LIST
};

class CElement
{
public:
           CElement( void );
  void     init( void );
  EL_TYPES type( void ){ return el_type; };

  . . .

  int      x;
  int      y;
  int      width;

  . . .

protected:
  EL_TYPES el_type;
  char*    el_data;
};

class CScreen
{
public:
             CScreen( void ){ init(); };
  void       init( void );

  . . .

  CElement&  get_element( int x, int y )
  {
    //if ( x + length > owner->width() ) return false;
    for ( int i = 0; i < count(); i++ )
    {
      if ( elements[i]->y == y && elements[i]->x <= x && elements[i]->x + elements[i]->width > x ) return *elements[i];
    }
    return *((CElement*)NULL);
}

  CElement& operator [] ( int index )
  {
    if ( index == -1 ) return *((CElement*)NULL);
    else return *elements[index];
  };

  . . .

protected:
  char    name[64];
  std::vector<CElement*> elements;
};

????????????????????????
Вопрос сводится к вызову метода get_element и оператора [] в классе CScreen. Красиво ли будет выглядеть код:
????????????????????????
CScreen& s = get_current_screen();
CElement& e = s.get_element( m_caret_pos.x, m_caret_pos.y );
if ( &e && e.type() == EL_TEXT )
{
  . . .
}

или все-таки вместо ссылке здесь красивее было бы использовать указатель:
  CElement*  get_element( int x, int y )

соответственно:
CElement* e = s.get_element( m_caret_pos.x, m_caret_pos.y );
if ( e && e->type() == EL_TEXT )

!!!!!!!!!!!!!!!!!!!!!!!!
Почему я использовал ссылку – потому, что можно перегрузить оператор EL_TYPES:
!!!!!!!!!!!!!!!!!!!!!!!!
  operator EL_TYPES ( void ) { return type(); };

и, тогда:
if ( &e && e == EL_TEXT )
{
  . . .
}

, что мне кажется – очень удобным.

Спасибо за внимание.
Критикуйте. (-:
Не забывайте, пожалуйста, использовать теги [c]...[/c], [code]...[/code] и т.п. для выделения фрагментов кода. -- ПК
Re: Корректное и удобное использование указателей и ссылок.
От: Нахлобуч Великобритания https://hglabhq.com
Дата: 19.07.05 08:28
Оценка:
Здравствуйте, Andreev, Вы писали:

A>У меня возникли сомнения: в своем проекте, который, должен возвращать по текущей позиции указателя на экране элемент, написал следующий код:


...
A>  CElement&  get_element( int x, int y )
A>  {
A>    //if ( x + length > owner->width() ) return false;
A>    for ( int i = 0; i < count(); i++ )
A>    {
A>      if ( elements[i]->y == y && elements[i]->x <= x && elements[i]->x + elements[i]->width > x ) return *elements[i];
A>    }
A>    return *((CElement*)NULL);A> // Руки обрывать!
}

A>  CElement& operator [] ( int index )
A>  {
A>    if ( index == -1 ) return *((CElement*)NULL); // Аналогично
A>    else return *elements[index];
A>  };
A>  std::vector<CElement*> elements; // Опасно, но не критично
A>};


A>????????????????????????

A>Вопрос сводится к вызову метода get_element и оператора [] в классе CScreen. Красиво ли будет выглядеть код:
A>????????????????????????

A>CScreen& s = get_current_screen();
A>CElement& e = s.get_element( m_caret_pos.x, m_caret_pos.y );
A>if ( &e && e.type() == EL_TEXT ) // Coding Guidelines: Never Switch on Type
A>{
A>  . . .
A>}


A>Спасибо за внимание.

A> Критикуйте. (-:

HgLab: Mercurial Server and Repository Management for Windows
Re: Корректное и удобное использование указателей и ссылок.
От: Centaur Россия  
Дата: 19.07.05 14:43
Оценка: 39 (2)
Здравствуйте, Andreev, Вы писали:

A>У меня возникли сомнения: в своем проекте, который, должен возвращать по текущей позиции указателя на экране элемент, написал следующий код:


A>enum EL_TYPES
A>{
A>  EL_NULL, EL_TEXT, EL_LABEL, EL_EDIT, EL_LIST
A>};


Антипаттерн «Код типа». Возможные последствия: трудноподдерживаемые длинные switch (code) { }. Лечение: рефакторинг Replace Type Code with Subclasses [1].

class Element
{
public:
  virtual ~Element() = 0;
};
Element::~Element() {}

class NullElement : public Element
{
public:
  ~NullElement()
  {}
};

class TextElement : public Element  // …
// …


A>  CElement&  get_element( int x, int y )
A>  {
A>    for ( int i = 0; i < count(); i++ )
A>    {
A>      if ( elements[i]->y == y && 
          elements[i]->x <= x && 
          elements[i]->x + elements[i]->width > x )
        {
          return *elements[i];
        }
A>    }
A>    return *((CElement*)NULL);
A>}

Ссылка не имеет права быть нулевой. Верни, например, new NullElement [2].

A>Вопрос сводится к вызову метода get_element и оператора [] в классе CScreen. Красиво ли будет выглядеть код:


A>CScreen& s = get_current_screen();
A>CElement& e = s.get_element( m_caret_pos.x, m_caret_pos.y );
A>if ( &e && e.type() == EL_TEXT )
A>{
A>  . . .
A>}

A>или все-таки вместо ссылке здесь красивее было бы использовать указатель:

A>  CElement*  get_element( int x, int y )

A>соответственно:

A>CElement* e = s.get_element( m_caret_pos.x, m_caret_pos.y );
A>if ( e && e->type() == EL_TEXT )


Красивее — будет такое:

s.get_element(caretPos).doSomething(может, быть, ещё, какие-нибудь, параметры);


а уже doSomething реализован в NullElement как {}, в TextElement — как надо, в LabelElement — как-то ещё. Если таких блоков много, то можно использовать паттерн Visitor [3]:

class ElementVisitor;

class Element
{
public:
  virtual void accept(ElementVisitor&) = 0;
};

class NullElement : public Element
{
public:
  void accept(ElementVisitor&);
};

class TextElement : public Element
{
public:
  void accept(ElementVisitor&);
};

//…

class ElementVisitor
{
public:
  virtual void visit(NullElement&) = 0;
  virtual void visit(TextElement&) = 0;
  //…
};

void NullElement::accept(ElementVisitor& v)
{
  v.visit(*this);
}

void TextElement::accept(ElementVisitor& v)
{
  v.visit(*this);
}

//…

// где-то далеко

class DoSomethingWithTextElement : public ElementVisitor
{
public:
  void visit(NullElement&) {}
  void visit(TextElement& te)
  {
    // do something with te
  }
  //…
};

s.get_element(pos).accept(DoSomethingWithTextElement());


A>Почему я использовал ссылку – потому, что можно перегрузить оператор EL_TYPES:


A> operator EL_TYPES ( void ) { return type(); };


A>и, тогда:

A>if ( &e && e == EL_TEXT )
A>{
A>  . . .
A>}

A>, что мне кажется – очень удобным.

А мне — опасным. Неявные преобразования типов вообще нужно хорошо обдумывать.

Ссылки на литературу:
[1] Martin Fowler, Kent Beck et al. Refactoring: Improving the Design of Existing Code. p. 181.
[2] Jim Hyslop, Herb Sutter. Null References. // C++ Report, 12(6), June 2000.
[3] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. p. 331.
Re[2]: Корректное и удобное использование указателей и ссыло
От: Andreev  
Дата: 21.07.05 06:23
Оценка:
Здравствуйте, Centaur:

Большое СПАСИБО, мне очень понравился ответ !!!
Re[2]: Корректное и удобное использование указателей и ссыло
От: Andreev  
Дата: 21.07.05 09:44
Оценка:
Здравствуйте, Centaur.

Для этого кода еще существует одна проблема:

Изначально строились 3-и класса CPanel, CScreen и CElement. Все эти классы существуют как некий документ и не более.
Панель — это класс некоторой панели(физическое устройство), которая в свою очередь содержит набор экранов(логические единицы панели), а экраны содержат элементы(логические единицы экрана).

Задача заключается в том, чтобы собрать редактор для добавления экранов в панель и расположения элементов внутри экранов, задавая им некоторые свойства в зависимости от типов. В конечном итоге, при помощи методов store() выгрузить все эти данные в программу управления реальной панелью, которая позволит получить на панели логическую систему меню, сообщений и форм ввода/вывода.
Программа-транслятор, для реальной панели, так-же написана на c++, за исключением платформы(в настоящий момент используется DOS).

По-этому не хотелось делать несколько реализаций классов CPanel, CScreen и CElement для каждой платформы, а сделать всего-лишь один вариант с методами load() и store(). После чего, использовать эти классы в зависимости от того в какой программе они используются (редактор или транслятор), как некое хранилище данных.

Если использовать механизм наследования и создавать потомков класса CElement с готовыми методами для использования их в редакторе (т.е. некие методы DoSomething()), то в результате окажется, что при повторном использовании кода этого класса в трансляторе у меня появится неиспользуемый код, т.к. в трансляторе свойства элементов не меняются и остаются постоянными.

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

Можно, конечно, унаследовать класс CPanel и получить что-то типа CEditablePanel и CTranslatedPanel, но все равно методы редактирования и трансляции будут разными (я не вижу необходимости создавать над всем этим класс), а структура документа (набор свойств элементов) постоянна. И необходим класс для элемента, который мог бы сообщить где он находится на экране, какого он типа и какие данные содержит — больше ничего.

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

Я, так-же, полностью согласен с высказываниями по поводу возврата пустых ссылок (про руки). Это уже на самом деле был плод больной фантазии невыспавшегося организма. Хотя должен признать, что изначально идея null-referenses мне очень понравилась (хотя это всего-лишь мое мнение, которое не совпадает с концепцией ссылок в с++), но я во благо Человечества решил от нее отказаться .

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

Удачи.
Re[3]: Корректное и удобное использование указателей и ссыло
От: Centaur Россия  
Дата: 21.07.05 16:39
Оценка:
Здравствуйте, Andreev, Вы писали:

A>Для этого кода еще существует одна проблема:


A>Изначально строились три класса CPanel, CScreen и CElement. Все эти классы существуют как некий документ и не более.

A> Панель — это класс некоторой панели(физическое устройство), которая в свою очередь содержит набор экранов(логические единицы панели), а экраны содержат элементы(логические единицы экрана).

A> Задача заключается в том, чтобы собрать редактор для добавления экранов в панель и расположения элементов внутри экранов, задавая им некоторые свойства в зависимости от типов. В конечном итоге, при помощи методов store() выгрузить все эти данные в программу управления реальной панелью, которая позволит получить на панели логическую систему меню, сообщений и форм ввода/вывода.

A> Программа-транслятор, для реальной панели, так-же написана на c++, за исключением платформы(в настоящий момент используется DOS).

А что, под DOS таки существуют компиляторы C++?

A> Поэтому не хотелось делать несколько реализаций классов CPanel, CScreen и CElement для каждой платформы, а сделать всего-лишь один вариант с методами load() и store(). После чего, использовать эти классы в зависимости от того в какой программе они используются (редактор или транслятор), как некое хранилище данных.


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

A> Если использовать механизм наследования и создавать потомков класса CElement с готовыми методами для использования их в редакторе (т.е. некие методы DoSomething()), то в результате окажется, что при повторном использовании кода этого класса в трансляторе у меня появится неиспользуемый код, т.к. в трансляторе свойства элементов не меняются и остаются постоянными.


Любой мало-мальски уважающий себя компилятор выбросит неиспользуемый код и не будет включать его в финальный бинарник.

A> Кроме того — если отказаться от задания типов элементам, то транслятору будет достаточно тяжело определить какого типа элемент он обрабатывает.


Ему не нужно определять, ему нужно подсунуть элементу соответствующим образом реализованного visitor’а.
Re[4]: Корректное и удобное использование указателей и ссыло
От: Cyberax Марс  
Дата: 21.07.05 17:09
Оценка:
Centaur wrote:

> A> Задача заключается в том, чтобы собрать редактор для добавления

> экранов в панель и расположения элементов внутри экранов, задавая им
> некоторые свойства в зависимости от типов. В конечном итоге, при
> помощи методов store() выгрузить все эти данные в программу управления
> реальной панелью, которая позволит получить на панели логическую
> систему меню, сообщений и форм ввода/вывода.
> A> Программа-транслятор, для реальной панели, так-же написана на c++,
> за исключением платформы(в настоящий момент используется DOS).
> А что, под DOS таки существуют компиляторы C++?

DigitalMars, GCC, Watcom.

--
С уважением,
Alex Besogonov (alexy@izh.com)
Posted via RSDN NNTP Server 1.9
Sapienti sat!
Re[4]: Корректное и удобное использование указателей и ссыло
От: Andreev  
Дата: 22.07.05 07:20
Оценка:
Здравствуйте, Centaur, Вы писали:

C>А что, под DOS таки существуют компиляторы C++?


Я использую BC++ 5.0. Поскольку у меня контроллер на 386SX, то получается достаточно удобно и оптимизация поддерживается где-то до 150-го Пентиума. Не надо писать неудобный код на ассемблере (в смысле основные блоки у меня описаны классами). Для самих-же модулей ввода-вывода (используется кристал Atmel 8015) приходится писать на asm-е, хотя там и есть подобие C, но у меня нет компилятора (лень скачать).


C>Я так понимаю, в редакторе у этих классов одно поведение, в трансляторе другое? И набор методов разный. И всё разное, кроме представления данных. Так зачем хотеть реализовать оба проекта на одних и тех же классах?


Идея в том, что эти классы являются контейнерами для данных. Они должны выдавать описание свойств тех или инных объектов. Поведение-же этих объектов отличается, по-этому, методы реализованы на основе других классов. Единственными методами могут быть методы проверки корректности данных в этом классе (по идее, конечно).

C>Любой мало-мальски уважающий себя компилятор выбросит неиспользуемый код и не будет включать его в финальный бинарник.


Я согласен, но во-первых не уверен с данным утверждением для BC++ 5.0, а во-вторых, как правило, если ф-ция виртуальная, то она всеравно остается, поскольку очень тяжело на 100% определить, что не будет вызова этой ф-ции.


C>Ему не нужно определять, ему нужно подсунуть элементу соответствующим образом реализованного visitor’а.


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

С уважением.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.