Объектная модель C++

Очень много времени у меня ушло на написание данной статьи… Но получилось неплохо. Критерии описания объектной модели этого языка я описал в этой заметке. Итак, перейдем к делу.

Язык C++ является одним из наиболее распространенных языков программирования, поддерживающих объектно-ориентированный подход к программированию. На языке C++ (как и почти на любом другом объектном языке программирования) можно писать программы соответствующие другим парадигмам программирования. В частности, C++ поддерживает процедурный стиль программирования на основе функций, параметрическое программирование — с помощью механизма шаблонов.

В какой-то степени C++ — это компромисс между строго объектно-ориентированным и процедурным подходом. Может быть, благодаря этому (и благодаря тому, что C++ вырос из другого популярного языка C), он и получил такое широкое распространение. Для многих программистов и для целых организаций переход к C++ от C произошел эволюционно. Ранее написанное на C программное обеспечение не требовало переделки и легко интегрировалось к новым возможностям языка. Несмотря на то что C++ имеет богатую историю развития, основные принципы объектной модели, заложенные его автором Бьерном Страуструпом в конце 70-х годов, остались практически неизменными. К настоящему времени завершена процедура стандартизации языка. Международный стандарт ISO языка C++ — ISO/IEC 14882:2003.

Система типов в объектной модели C++

Язык C++ — строго типизированный язык. Это означает, что любая величина, с которой оперирует программа, будь то константа или переменная, относится к определенному типу. Тип величины задается во время ее создания и не может быть изменен во время выполнения программы. Все типы языка разделяются на две группы: встроенные типы и классы, определяемые программистом.

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

Встроенные типы языка C++

Встроенные типы языка C++

Классы языка C++ — это типы определенные программистом. Классы задают атрибуты и методы объектов данного класса. Атрибуты определяют состояние объекта, они принимают значения других типов (встроенных или производных). Методы определяют интерфейс класса, те сообщения, которые объекты данного класса могут обрабатывать.

Все атрибуты и методы класса разделяются на три группы по степени доступности (на три модификатора доступа). Внешние (public) методы и атрибуты определяют внешний интерфейс класса, и к ним могу обращаться произвольные объекты и функции. Защищенные (protected) методы и атрибуты доступны только объектам данного класса и классам, выведенным из него. Внутренняя (private) часть атрибутов и методов доступна только объектам этого класса. Внутренние и защищенные методы задают реализацию интерфейсам класса. Внутренние и защищенные атрибуты задают внутреннее состояние объекта. Ключевое слово void используется для обозначения того факта, что у функции (метода) нет аргументов или что эта функция не возвращает значение.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Shape
{
public:
    Shape();  // Конструктор
    void SetCoords(int x, int y); // Установить координаты
    int GetXCoord(void) const; // Сообщить координату X
    int GetYCoord(void) const; // Сообщить координату Y
    virtual void Draw(void); // Нарисовать фигуру
protected:
    void RepaintBackground(void);  // Закрасить фон
private:
    int x;    // Координата x
    int y;    // Координата y
};

В приведенном примере определение класса Shape (произвольная фигура, изображаемая на экране терминала или на бумаге) задается так, что объекты данного класса могут выполнять операции установки координат, сообщения координат и рисовать самих себя. Кроме них к внешней части класса относится конструктор — метод, выполняемый при создании объекта. В защищенной части класса находится метод перерисовки фона. Этим методом могут воспользоваться выведенные из класса Shape классы. Во внутреннюю часть класса мы поместили два атрибута: координаты X и Y объекта (на экране или бумаге), задаваемые целыми числами.

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

Shape ashape; // Создать объект класса Shape
ashape.Draw(); // Нарисовать объект ashape
то, если определена операция, например сложения, ее можно записать например как
Complex x; // Объект класса Complex
Complex y; // Объект класса Complex
x + y; // Вызвать метод-операцию сложения

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

Complex w, x, y, z;
w = (x + y) * 3 — z;

Промежуточное положение занимает особый тип данных — набор перечислимых значений enum. Этот тип определяет (перечисляет) все допустимые значения для нового типа, например тип Months перечисляет все названия месяцев:

enum Monhts { Jan, Feb, Mar, Apr, Jun, Jul,
Aug, Sep, Oct, Nov, Dec};

С одной стороны, этот тип определен пользователем, но, с другой, он не является классом. Для набора значений нельзя определить новые методы или операции. Из простых типов (как встроенных типов, так и определенных программистом классов) можно построить более сложные типы применением модификаторов «указатель«, «ссылка«, «массив«. Указатель — это тип, являющийся адресом величины в памяти определенного типа. Ссылка задает еще одно имя переменной определенного типа, фактически являясь также адресом. Массив — это набор элементов одного и того же типа, обращаться к которым можно по индексу.

Наследование в объектной модели C++

Язык C++ позволяет как простое так и множественное наследование. У иерархии классов, которую создает программист, нет общего корня.Иными словами, в языке C++ нет какого-то универсального класса, из которого выведены все остальные классы. Произвольные классы могут наследовать интерфейс своих родителей вместе с их состоянием и реализацией (при внешнем наследовании), либо наследовать только состояние и реализацию методов (при защищенном и внутреннем наследовании).

Полиморфизм реализован с помощью виртуальных методов. Определив метод виртуальным в базовом классе, его определение можно уточнять в произвольных классах. Например, используя класс Shape, в качестве базового для класса Square (квадрат) и Circle (круг), мы можем переопределить метод Draw:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Square : public Shape
{
public:
    virtual void Draw(void);
    …
private:
    double length;
};

class Circle : public Shape
{
public:
    virtual void Draw(void);

private:
    double radius;
}

Тогда, если через указатель на базовый класс вызывается метод Draw, то выполнен будет метод того конкретного класса, к которому принадлежит текущий объект:

Circle* circleObj = new Circle; // Создать объект класса Circle
Shape* pShape = circleObj; // Указатель на базовый класс
pShape->Draw(); // Вызывается метод Draw класса Circle

С помощью виртуальных методов в языке C++ можно определять необходимый интерфейс выводимых классов. Если объявить метод чисто виртуальным, то все порожденные классы обязаны определить этот метод (иначе невозможно создать объект данного класса). У чисто виртуального метода нет реализации, т.е. он задает лишь формат метода, но не способ, каким этот метод выполняется. Например, если в описании класса Shape определение метода Draw будет записано в виде

class Shape
{
public:
virtual void Draw (void) = 0;
};

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

Как уже было отмечено, язык C++ поддерживает множественное наследование. У одного класса может быть несколько базовых классов. В таком случае интерфейс и состояние объектов этого класса — это сумма интерфейсов и состояний всех базовых классов (естественно, плюс атрибуты и методы, определенные в самом классе).

Создание и уничтожение объектов  в объектной модели C++

Прежде всего язык C++ — это язык программирования предназначенный для написания традиционных программ.  Это значит, что программа, написанная на C++, должна быть запущена в соответствующей операционной системе. До того, как программа запущена, ни один из объектов не существует. После того как программа прекратила работать, опять-таки ни один из объектов не существует. Поэтому по необходимости время жизни объектов по меньшей мере ограничено временем исполнения программы.

Программа выполняется в оперативной памяти. Завершение программы (либо плановое, либо выключение компьютера) уничтожает все созданные этой программой объекты. Объекты в языке C++ создаются либо при объявлении переменной соответствующего типа, либо явно с помощью вызова операции new, либо временно как промежуточные результаты вычисления выражений.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Generator globalGen;  // Статический объект класса
// Generator, создается вначале
// выполнения программы,
// и уничтожается по ее завершении

void foo(void)
{
    Square(s); // Создание объекта класса Square
    // происходит только тогда, когда
    // программа начнет выполнять функцию foo
    …
    return; // Завершение функции и уничтожение
              // объекта s
}

Явное создание объектов с помощью операции new может быть выполнено в любой момент выполнения программы, и созданный объект существует до тех пор, пока он не будет явно уничтожен с помощью операции delete. В качестве ссылки на созданный объект операция new возвращает его адрес. Даже если все ссылки на созданный с помощью new объект утеряны, объект продолжает существовать и занимать отведенную под него память (это одна из часто встречающихся ошибок при программировании на C++, приводящая к неэффективному использованию памяти, а иногда и к сбоям программ).

void bar(void)
{
Circle* ptr1 = new Circle;
Circle* ptr2 = new Circle;

delete ptr1;
return;
}

В приведенном примере в начале функции bar создается два объекта Circle.При выходе из функции один из них уничтожается, а другой — нет. Ссылка на второй объект, хранящаяся в автоматической переменной ptr2, теряется, обратиться к ней после выхода из функции никак нельзя, но тем не менее, объект не уничтожается и память не освобождается.

Идентификация объектов в объектной модели C++

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

Фактически, сравнивая два объекта на равенство, мы сравниваем состояние объектов, точнее в C++ значения объектов. Значения объекта — это значения всех его атрибутов.  Для того чтобы корректно сравнивать объекты какого-либо класса, для этого класса следует определить операцию «равно» (==). Операция «равно» — это не более чем один из методов класса, т.е. программист может определить его так как считает нужным для данного конкретного класса. Быть может, для определения равенства объектов не существенно равенство каких-либо его атрибутов или равенство означает «приблизительное равенство». Например, легко представить ситуацию, когда равенство слов определено без учета регистра.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Circle : public Shape
{
public:
    …
    bool operator==(const Circle& c) const
      {
        if ( radius == c.radius
               && GetXCoord() == c.GetXCoord()
               && GetYCoord() == c.GetYCoord() )
                  return true;
        else
                  return false;
        }
};

В приведенном примере, операция «равно» сравнивает длину радиусов двух окружностей и их координаты. Ключевое слово operator используется для задания метода-операции «равно», т.е. метода, который можно вызвать в виде x==y.

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

Однако у способа идентификации объекта по адресу имеются и определенные недостатки. Например, даже если операционная система и предусматривает механизмы копирования памяти, копирование объектов таким образом создает новый экземпляр класса, а не тот же самый объект.

Другой проблемой является сравнение объекта производного класса с объектом базового класса. Объект класса Circle одновременно является объектом класса Shape, поскольку класс Circle выведен из класса Shape. Однако обязательно ли преобразование адреса объекта класса Circle к адресу его базового класса (операция, вполне допустимая в языке C++) не изменяет ли это значение указателя (адреса)? Проблему демонстрирует нижеприведенный рисунок со схемой структуры объекта в памяти.

Структура объекта в памяти

Структура объекта в памяти

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

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

Метаданные в объектной модели C++

Метаданные призваны описать систему типов конкретной программы. Метаданные языка C++ крайне ограничены.

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

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

1
2
3
4
5
6
7
8
9
10
class type_info
{
public:
    virtual ~type_info();
    int operator==(const type_info& t) const;
    int operator!=(const type_info& t) const;
    int before(const type_info& t) const;
    const char* name() const;
    const char* raw_name() const;
};

Получить информацию о типе (объект класса type_info) можно с помощью операции typeid. Объекты класса type_info можно сравнивать на равенство и неравенство. Методы name и raw_name выдают имена типов. Причем оператор raw_name выдает внутреннее представление имени в том виде, в каком оно хранится в программе. Метод name выдает имя в «нормальном» удобно читаемом виде.

Очевидно, что класс type_info не описывает систему типов. Он не описывает отношения наследования, методы и атрибуты классов. Он задает упорядочивание типов с помощью метода before, но язык не определяет какова именно эта упорядоченность.

В языке C++ не существует специальных метаклассов. При определении класса определяется конструктор и деструктор — методы, которые выполняются при создании и уничтожении объекта.

Модули или компоненты в объектной модели C++

Возможность объединять объекты в более крупные компоненты по функциональности или по какому-нибудь иному признаку — существенная характеристика объектной модели объектно-ориентированного языка. Такая возможность прежде всего важна при создании многократно используемых модулей и библиотек.

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

Библиотеки функций не поддерживают объектно-ориентированный подход к программированию. Библиотеки классов, несомненно, являются объектно-ориентированными, поскольку не только состоят из классов, но и допускают приспособления к нуждам конкретной программы объектно-ориентированным путем. Например, какой-либо класс из библиотеки можно использовать в качестве базового и в производном классе «скорректировать» его поведение в соответствии с конкретной задачей. Классы из библиотеки можно использовать в качестве атрибутов других классов и т.д.

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

Единственный средством языка C++, поддерживающим более или менее создание отдельных компонентов, является возможность определения контекста имен с помощью оператора namespace. Контексты позволяют избежать конфликта имен, т.е. позволяют использовать одно и тоже имя для разных целей и в разных контекстах.

Оператор namespace делит программы на блоки видимости имен. Глобальные имена должны быть уникальны только внутри блока имен. Чтобы обратиться к имени класса, функции или переменной из другого контекста, необходимо указать имя контекста и после двух двоеточий — имя необходимого объекта. Например, Math::pi.

Определение динамики поведения объектов в объектной модели C++

В объектно-ориентированной программе действия  выполняются путем создания объектов и вызова методов объектов. Однако какое-то действие должно начать этот процесс. В языке C++ программа начинает выполняться со специальной функции main. Эта функция может вызывать другие функции и создавать какие-либо объекты, которым она может передавать сообщения.

При определении поведения программы важную роль играет тот факт, что язык C++ поддерживает не только объектно-ориентированный, но и процедурный стиль программирования. Действия, в том числе и методы классов, — это функции или процедуры.

Ранее, описывая классы, были приведены примеры реализации классов банковской системы без описания их реализации. Далее будут приведен полный код этих классов.

Ниже приведенный фрагмент программы реализует два вида счетов — расчетный счет (класс CheckingAccount) и депозитный счет (класс DepositAccount) — с помощью наследования из базового класса Account.

Класс Account — это абстрактный класс с чисто виртуальным методом GetAccountType. Кроме того, он задает для всех порожденных из него классов методы — Deposit, Withdraw и Balance. Вызывая эти методы, программы, работающие с базовым классом, будут использовать ограниченный полиморфизм.

Атрибут amount — остаток на счету — помещен в защищенную часть класса для того, чтобы порожденные классы могли с ним работать. Атрибут accountNumber — номер счета — помещен во внутреннюю часть класса, поскольку базовый класс полностью контролирует создание уникальных номеров счетов.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//
// Класс Account — базовый класс для всех типов счетов
//
class Account
{
// Внешняя часть класса, его интерфейс
public:
    // Перечисление всех возможных типов счетов
    enum AccountType { CHECKING, DEPOSIT };

    Account() {amount = 0; }; // Создание пустого счета
    Account(long _accountNumber); // Создание объекта для имеющегося счета

    virtual ~Account();

    // Хотя класс Account абстрактный, он проводит
    // стандартные реализации методов "положить на счет" Deposit,
    // "снять со счета" Withdraw, и "остаток на счете" Balance,
    // хотя они могут быть переопределены в порожденных классах
    virtual void Deposit(float _amount); // Положить на счет
    virtual bool Withdraw(float _amount); // Снять со счета
    virtual float Balance(void) const; // Остаток на счете

    // Метод GetAccountType возвращает тип конкретного счета
    // и определен только в порожденных классах
    virtual AccountType GetAccountType(void) const = 0;
    // Узнать номер счета
    long GetAccountNumber(void) const { return accountNumber; };

// Защищенная часть класса доступна порожденным классам
protected:
    float amount; // Атрибут — сумма на счете
// Внутренняя часть класса
private:
    long accountNumber; // Атрибут — номер счета
};

Класс DatabaseObject — абстрактный класс, который задет интерфейс для сохранения и восстановления объектов в базе данных. Порожденные из него классы обязаны определить два метода: Save — для сохранения самого себя в базе данных, и Restore — для восстановления состояния из базы данных.

В данном классе показано что в абстрактном классе могут задаваться конкретные методы (MarkModifed, IsModifed, GetDatabaseConnection). Если первые два метода относятся к интерфейсу класса, то третий — это защищенный метод, необходимый для реализации Save и Restore в порожденных классах.

Идея состоит из предположения, что операции с базой данных требуют установки соединения с ней через объект типа DBConnection, который и возвращает метод GetDatabaseConnection.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//
// Базовый класс для всех объектов,
// которые могут храниться в базе данных
//
class DatabaseObject
{
public:
    DatabaseObject() : modifiedFlag(false) {}
    virtual ~DatabaseObject();

    virtual bool Save(void) = 0;
    virtual bool Restore(void) = 0;

    // Два дополнительных метода устанавливают или проверяют
    // признак того, что объект был модифицирован и должен
    // быть сохранен в базе данных
    void MarkModified(void);
    bool IsModified(void) const;

protected:
    DBConnection* GetDatabaseConnection (void);
private:
    bool modifiedFlag;
};

Два следующих класса: CheckAccount и Deposit — это конкретные классы, реализующие два типа счетов — расчетный счет и депозит. Они используют множественное наследование.

В качестве базовых классов выступают классы Account и DatabaseObject. Класс Account создают интерфейс и стандартную реализацию свойств счета, а класс DatabaseObject — необходимый интерфейс для хранения объектов в базе данных.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//
// Класс Расчетный счет — это конкретный счет, который
// может храниться в базе данных
//
class CheckingAccount : public Account, DatabaseObject
{
public:
    CheckingAccount() {};
    CheckingAccount(long _accountNumber) :
         Account(_accountNumber) {};
         // При конструировании нового объекта данного
         // класса, мы вначале конструируем его
         // базовую часть
    virtual ~CheckingAccount();

    // Класс определяет метод Снятия со счета, потому
    // что имеется требование минимального остатка на счете,
    // оставляя неизменными методы "положить на счет"
    // и "проверить остаток"
    virtual bool Withdraw(float _amount);

    // Метод GetAccountType определен и возвращает тип
    // счета — расчетный счет
    virtual AccountType GetAccountType(void) const
             { return CHECKING; };

    // Поскольку объекты данного класса можно хранить
    // в базе данных — класс выведен из DatabaseObject, —
    // в нем определяются методы Save и Restore для
    // сохранения и восстановления из базы данных
    virtual bool Save(void);
    virtual Restore(void);

private:
    // Класс определяет один дополнительный атрибут —
    // минимальный остаток на счете
    float minBalance;
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//
// Класс счет-депозит, аналогичный предыдущему классу
// расчетного счета
//
class DepositAccount : public Account, DatabaseObject
{
public:
    DepositAccount() {};
    DepositAccount(long _accountNumber) :
        Account(_accountNumber) {};
    virtual ~DepositAccount();
    // Класс предопределяет метод снятия со счета и метод
    // "положить на счет", потому что имеется ограничение,
    // в какие сроки их можно производить,
    // метод "проверить остаток", также переопределен,
    // поскольку на сумму начисляется процент
    virtual void Deposit(float _amount);
    virtual bool Withdraw(float _amount);
    virtual float Balance(void) const;

    // Метод GetAccountType определен и возвращает тип
    // счета — депозит
    virtual AccountType GetAccountType(void) const
              { return DEPOSIT; };

    // Поскольку объекты данного класса можно хранить
    // в базе данных — класс выведен из DatabaseObject, —
    // в нем определяются методы Save и Restore для
    // сохранения и восстановления из базы данных
    virtual bool Save(void);
    virtual Restore(void);

    // Два дополнительных метода определяют прошел ли срок
    // депозита и какой процент на этот вклад
    bool IsMature(void) const;
    float GetInterestRate(void) const;

private:
    // Два дополнительных атрибута хранят срок
    // депозита и процент на этот вклад
    Date maturityteDate;
    float interestRate;
};

Класс AccountFactory — это пример использования понятия фабрики объектов в языке C++. Фабрика объектов — это не более чем класс, результатом вызова объекта которого являются новые объекты другого класса. Хотя непосредственно новые объекты создаются с помощью оператора new, фабрика объектов позволяет инкапсулировать создание объектов разных конкретных классов в одном статическом методе CreateAccount.

Кроме того, этот класс иллюстрирует статические (static) методы класса. Эти методы не требуют объекта для своего выполнения, они выполняются от имени класса. Таким образом, класс AccountFactory эмулирует метакласс, поскольку «настоящие» метакласс отсутствуют в C++.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//
// Класс AccountFactory используется для создания новых счетов
//
class AccountFactory
{
public:
    // Метод CreateAccount создает новый счет определенного
    // типа с начальным вкладом _initialAmount, процентом
    // по вкладу _interestRate и сроком по вкладу _maturityDate
    // Последние два аргумента необязательны и
    // не задаются для расчетных счетов
    static Account* CreateAccount(Account::AccountType _t,
                                               float _initialAmount,
                                               float _interestRate = 0,
                                               Date _maturityDate = "");

    // Глобальный метод GetNewAccountNumber
    // генерирует новые, уникальные номера счета
    static long GetNewAccountNumber(void);
};

Объектная модель в ООП



pc71.ru