Руководство для начинающих по теме: Типы как объекты (Часть 2)
 

    Введение.

    Добро пожаловать во вторую часть урока. В этой части я предполагаю, что вы прочитали 1 часть , попытались понять примеры, и поэкспериментировали. Теперь я расскажу то, что я не включил в 1 часть.

    Индексированные свойства.

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

    Type foo
      Declare Property bar(ByVal index As Integer, ByVal value As Integer)
      Declare Property bar(ByVal index As Integer) As Integer
      dummy As Integer
    End Type 

    Property foo.bar(ByVal index As Integer, ByVal value As Integer)
      Print "Property set, index=" & index & ", value=" & value 
    End Property

    Property foo.bar(ByVal index As Integer) As Integer
      Print "Property get, index=" & index
      Property = 0
    End Property

    Dim baz As foo

    baz.bar(0) = 42
    Print baz.bar(0)


    Как вы можете видеть, декларация для нашего индексированного свойства очень похожа на обычную, за исключением того, что на этот раз мы добавили аргумент для индекса. Я включил элемент Integer, потому что тип должен иметь по крайней мере один элемент данных. Как вы можете видеть, при вызове свойство использует (0), чтобы обозначить, что мы хотим получить или задать нулевой индекс, так же, как для обычного массива. Теперь я покажу вам более полезный пример, и ниже дам описание его:

    Type foo
      Declare Constructor(ByVal num_elements As Integer)
      Declare Destructor()
      Declare Property bar(ByVal index As Integer, ByVal value As Integer)
      Declare Property bar(ByVal index As Integer) As Integer
    Private:
      x As Integer Ptr
      size As Integer
    End Type 

    Constructor foo(ByVal num_elements As Integer)
      x = CAllocate(num_elements * SizeOf(Integer))
      size = num_elements
    End Constructor

    Destructor foo()
      Deallocate(x)
    End Destructor

    Property foo.bar(ByVal index As Integer, ByVal value As Integer)
      If (index >= 0) And (index < size) Then
        x[index] = value
      Else
        Error 6
      End If
    End Property

    Property foo.bar(ByVal index As Integer) As Integer
      If (index >= 0) And (index < size) Then
        Property = x[index]
      Else
        Error 6
      End If
    End Property

    Dim baz As foo = foo(10)

    baz.bar(1) = 42
    Print baz.bar(1)


    На этот раз, я добавил конструктор и деструктор, который будет выделять и освобождать массив динамической памяти x, с количеством элементов, указанным в конструкторе. Далее при вызове функции свойств, я проверяю: если индекс находится в пределах границ массива, то будет выполняться запрошенное действие получения или установки. Если указанный индекс находится вне границ, то произойдет ошибка 'Error 6' , которая прервёт программу FB, выведя 'out of bounds error'. При желании можно установить собственную процедуру обработки ошибок. Попробуйте этот код, изменив запись ' baz.bar(1) = 42'  на ' baz.bar(10) = 42', и вы увидите обработку ошибок в действии, ведь мы указали только 10 элементов (индекс 0-9).

    Конструктор копирования.

    Конструктор копирования — особый тип конструктора, который используется для копирования из существующего объекта. Когда вы пишете код следующим образом:

    Type foo
    ...
    End Type

    Dim As foo a
    Dim As foo b = a


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

    Type foo
      Declare Constructor(ByRef obj As foo)
      ...
    End Type


    This will come in very useful for a reason I will now explain.

    Глубокая\мелкая копия.

    В предыдущем примере, код,  где мы писали 'Dim As foo b = a', можно назвать мелкой копией. Были скопированы данные полей, однако иногда это не желательно. Предположите, что один из элементов - указатель, и что произойдет при копировании? Конечно адрес, на который указывает указатель, будет скопирован, и в результате оба объекта укажут на ту же самую память. Пример этого:

    Type foo
      x As Integer Ptr
    End Type

    Dim As foo a

    a.x = Allocate(SizeOf(Integer))
    *a.x = 42

    Dim As foo b = a

    Print *a.x, *b.x

    *a.x = 420

    Print *a.x, *b.x

    Deallocate(a.x)


    Вы можете видеть, что оба указывают на ту же самую память, то есть изменяя один, затрагивается и другой. Как объяснено в предыдущей записи про конструктор копирования, FreeBASIC создает код, делая мелкие копии по умолчанию. Это также верно, если мы делаем присваивание так:

    Dim As foo a, b

    b = a


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

    Type foo
      Declare Constructor()
      Declare Constructor(ByRef obj As foo)
      Declare Destructor()
      Declare Operator Let(ByRef obj As foo)
      x As Integer Ptr
    End Type

    Constructor foo()
      Print "Default ctor"
      x = CAllocate(SizeOf(Integer))
    End Constructor

    Constructor foo(ByRef obj As foo)
      Print "Copy ctor"
      x = CAllocate(SizeOf(Integer))
      *x = *obj.x
    End Constructor

    Destructor foo()
      Print "dtor"
      Deallocate(x)
    End Destructor

    Operator foo.Let(ByRef obj As foo)
      Print "Let"
      *x = *obj.x
    End Operator

    Dim As foo a

    *a.x = 42

    Dim As foo b = a 'Использование конструктора копирования

    Print *a.x, *b.x

    *a.x = 420

    Print *a.x, *b.x


    Как Вы видите к конструктору копирования относится линия  'Dim As foo b = a' и на сей раз, мы выделяем некоторую память и копируем данные в новом конструкторе копирования, для того, чтобы мы могли использовать x в одном объекте без затрагивания другого. Если мы изменим код следующим образом:

    Dim As foo a, b

    *a.x = 42
    b = a    'Используется оператор присваивания (Let).

    Print *a.x, *b.x

    *a.x = 420

    Print *a.x, *b.x


    То здесь уже используется оператор присваивания. Обратите внимание, что в коде под оператор присваивания не надо выделять память, потому что она уже была распределена в конструкторе по умолчанию, нам нужно просто скопировать данные. Линия '*x = *obj.x' выполняет эту копию. Если у нас было бы что-то более продвинутое, например массив динамической памяти, то необходимо было бы перераспределить память правильно, сообразно размеру копируемых данных. Вот более продвинутая версия примера.

    Type foo
      Declare Constructor(ByVal num_elements As Integer)
      Declare Constructor(ByRef obj As foo)
      Declare Destructor()
      Declare Operator Let(ByRef obj As foo)
      x As Integer Ptr
      size As Integer
    End Type

    Constructor foo(ByVal num_elements As Integer)
      Print "Default ctor"
      x = CAllocate(SizeOf(Integer) * num_elements)
      size = num_elements
    End Constructor

    Constructor foo(ByRef obj As foo)
      Print "Copy ctor"
      x = CAllocate(SizeOf(Integer) * obj.size)
      size = obj.size
      For i As Integer = 0 To size - 1
        x[i] = obj.x[i]
      Next i
    End Constructor

    Destructor foo()
      Print "dtor"
      Deallocate(x)
    End Destructor

    Operator foo.Let(ByRef obj As foo)
      Print "Let"
      x = Reallocate(x, SizeOf(Integer) * obj.size)
      size = obj.size
      For i As Integer = 0 To size - 1
        x[i] = obj.x[i]
      Next i
    End Operator

    Dim As foo a = foo(5)

    a.x[0] = 42
    a.x[1] = 420

    Dim As foo b = a 'Используется конструктор копирования

    Print a.x[0], a.x[1], b.x[0], b.x[1]

    b.x[0] = 10
    b.x[1] = 20

    Print a.x[0], a.x[1], b.x[0], b.x[1]

    b = a ' А сейчас используется оператор присваивания LET

    Print a.x[0], a.x[1], b.x[0], b.x[1]


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

    Передача объектов в функции по ByVal

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

    New/Delete

    New и Delete - это специальные операторы для динамического выделения памяти и последующего освобождения ее. Они используется для динамической памяти с указателями. Во всех примерах до сих пор мы просто использовали Dim для создания наших объектов, а это в свою очередь будет создавать их в стеке. Но с помощью New мы можем создавать их динамически, и это дает большую гибкость, так же, как с помощью Allocate/DeAllocate с нормальной памятью. Еще одна характерная вещь для New:  вам не нужно проверять указатели на равенство нулю (NULL) после вызова New. Если New даст сбой,  то это вызовет исключение, которое завершит программу. В более поздних версиях FreeBASIC вполне вероятно будет создан механизм, позволяющий лучшую обработку исключений, но по состоянию на момент написания, это еще не реализовано.

    Существует два различных варианта new/delete. Первый тип, создает только один элемент или объект, например:

    Dim As Integer Ptr foo = New Integer

    *foo = 1
    Print *foo

    Delete foo


    New будет создавать новый Integer, а затем уничтожит его, когда мы вызовем delete. Помните, что я использовал ptr, потому что это динамическая память. Для простых типов данных можно также указать значение по умолчанию, поместив его в скобках после типа данных, то есть:

    Dim As Integer Ptr foo = New Integer(42)

    Print *foo

    Delete foo


    Это также работает для определяемого пользователем типа (UDT) с простыми полями данных:

    Type foo
      x As Integer
      y As Integer
    End Type

    Dim As foo Ptr bar = New foo(1, 2)
      
    Print bar->x, bar->y

    Delete bar


    Эта инициализация не будет работать для более сложных типов с участием конструкторов , деструкторов и т.д., однако полезной особенностью является то, что при использовании New\Delete с объектами, также вызываются конструктор и деструктор, попробуйте следующий пример:

    Type foo
      Declare Constructor()
      Declare Destructor()
      x As Integer
      y As Integer
    End Type

    Constructor foo()
      Print "ctor"
    End Constructor

    Destructor foo()
      Print "dtor"
    End Destructor

    Dim As foo Ptr bar = New foo

    Delete bar


    Вы увидите, что вызываются конструктор и деструктор для объекта.

    Второй тип new/delete предназначен для создания массивов. На этот раз количество элементов помещается после типа данных в квадратных скобках '[]'. При использовании версии массива, необходимо также использовать 'delete []' вместо 'delete', для того, чтобы FreeBASIC знал, что удаляет массив. Вот простой пример использования с типом Integer:

    Dim As Integer Ptr foo = New Integer[20]

    foo[1] = 1
    Print foo[1]

    Delete[] foo


    Это позволит создать динамический массив, с 20 элементами Integer. Следует отметить, что это отличается от Allocate, который принимает количество байт в качестве аргумента; с использованием New, вы должны указать количество элементов. Метод с массивом работает точно так же для объектов:

    Type foo
      Declare Constructor()
      Declare Destructor()
      x As Integer
      y As Integer
    End Type

    Constructor foo()
      Print "ctor"
    End Constructor

    Destructor foo()
      Print "dtor"
    End Destructor

    Dim As foo Ptr bar = New foo[3]

    Delete[] bar


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

    Вы должны помнить, чтобы необходимо вызывать Delete, или Delete [] для любой памяти, выделенной с New, или это приведет к утечке памяти. Все так же , как DeAllocate вызывается когда нужно освободить память, выделенную с помощью Allocate.

    Искажение имени

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

    Sub foo Overload ()

    End Sub

    Sub foo(ByVal i As Integer)

    End Sub


    Если у нас бы не было искажения имен, то оба объекта будут известны на более низком уровне как FOO, и это приведет к столкновению имен. Поэтому они должны быть оформлены соответствующим образом, чтобы знать, какой из них должен быть вызван для использования. Для первого sub компилятор фактически создает sub под названием _Z3FOOv, а для второго он создаёт sub под названием _Z3FOOi. Компилятор запоминает это и выбирает соответствующий sub при вызове, в зависимости от того, как идет вызов. К примеру 'foo()' на самом деле будет вызывать _Z3FOOv, и 'foo(1)' на самом деле будет вызывать _Z3FOOi.  «v» означает VOID (без аргументов), и «i» расшифровывается как integer. Все детали искажения имени являются довольно сложными и варьируются в зависимости от компиляторов. Компиляторы Microsoft используют другие схемы , чем GNU компиляторы, так же как компиляторы других фирм используют свои различные схемы искажения имен. Главное, что нам нужно знать, это то, что FreeBASIC следует за GCC 3.x, ABI (двоичный интерфейс приложений), что означает, что любые перегруженные функции, или сложные типы будут совместимы только с компиляторами, по той же схеме. Это конечно ограничение, но это не совсем проблема FreeBasic, это общая проблема всех компиляторов.

    Неявный this

    Опять же нет необходимости знать о внутреннем устройстве THIS и как все происходит на более низком уровне. При вызове функции-элемента объекта, в нее передается первым скрытый параметр, в результате функция знает какой экземпляр объекта в настоящее время используется. Это также верно для элементов property/constructor/destructor/operator. Если мы рассмотрим очень простой пример:

    Type foo
      Declare Sub bar(ByVal n As Integer)
      x As Integer
    End Type

    Sub foo.bar(ByVal n As Integer)
      x = n
    End Sub

    Dim baz As foo
    baz.bar(5)


    То по существу верхний пример эквивалентен этому:

    Type foo
      x As Integer
    End Type

    Sub foo_bar(ByRef _this As foo, ByVal n As Integer)
      _this.x = n
    End Sub

    Dim baz As foo
    foo_bar(baz, 5)


    Этот метод с использованием явного «this» часто используется в языках, которые не имеют возможностей сделать проще. ООП - это просто набор понятий, которые могут кодироваться на практически любом языке. Некоторые вещи являются более трудными для осуществления, например конструкторы, нужно будет явным образом вызывать методы 'create', или 'init' функциями. А некоторые вещи, такие как private/public очень трудны в реализации или даже невозможны, потому что компилятор не знает как обеспечить их выполнение. Причиной для добавления функций ООП в языки — скрыть много лишнего и добавить "синтаксический сахар" для более простого использования. Таким образом, мы можем использовать свойства, как если бы они были элементами обычных данных, а не функции, которые является тем, чем они в действительности являются.

    Подсказки для отладки/профилирования

    При использовании GDB или других отладчиков и средства профилирования gprof, информация находится в синтаксисе C++, и все ваши имена переменных и другие символы отображаются в верхнем регистре. Здесь только очень короткий обзор, чтобы помочь вам понять:

    Вот пример типа:

    Type bar
      Declare Constructor()
      Declare Constructor(ByRef obj As bar)
      Declare Constructor(ByVal n As Integer)
      Declare Destructor()
      Declare Operator Cast() As Any Ptr
      Declare Operator Let(ByVal n As Integer)
      Declare Property foo(ByVal n As Integer)
      Declare Property foo() As Integer
      member As Any Ptr
    End Type


    При использовании GDB, они будут показаны следующим образом (Обратите внимание на C++, они используют :: где мы используем . (точку),  '::' известен как оператор разрешения области):

    BAR::BAR() - Конструктор по умолчанию
    BAR::BAR(BAR&) - Конструктор копирования (& в C++ означает ссылку, как byref)
    BAR::BAR(int) - Конструктор принимает аргумент Integer (обратите внимание, что нет специального символа для обозначения ByVal, так как это метод прохождения по умолчанию в C / C++)
    BAR::~BAR() - Деструктор
    BAR::operator void*() - Приведение к Any ptr (void симулирует Any, * означает указатель)
    BAR::operator=(int) - Оператор присваивания (Let), обозначается '=', в C/C++ '=' присваивание, '==' проверка на равенство.
    BAR::FOO(int) - Свойство foo установка, принимающий аргумент Integer
    BAR::FOO() - Свойство foo получение

    Элементы Sub/Function показываются так же, как свойства, индексированные свойства то же самое, только с дополнительным аргументом для индекса.

    Вот как типы данных FB будут показаны:

    Any ptr - void *
    ZString ptr - char *
    String - FBSTRING
    byte - signed char
    ubyte - bool
    short - short
    ushort - unsigned short
    integer - int
    uinteger - unsigned int
    longint - long long
    ulongint - unsigned long long

    Я надеюсь, что это поможет вам начать работу в GDB/GProf, но немного экспериментов не будет лишним.

    Дополнительная информация

    http://www.freebasic.net/wiki/wikka.php?wakka=KeyPgOpNew
    http://www.freebasic.net/wiki/wikka.php?wakka=KeyPgOpDelete
    http://en.wikipedia.org/wiki/Copy_constructor
    http://en.wikipedia.org/wiki/Object_copy
    http://en.wikipedia.org/wiki/Name_mangling