Модель памяти и структура программы. Классы памяти. Ссылки
Поможем в ✍️ написании учебной работы
Поможем с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой

От создателей первой части

Вопросы к зимнему экзамену по ООП Part 2

Вопросы по C++

 

НГУ 2018

Абстракцию забыли =\

 

1. Модель памяти и структура программы. Классы памяти. Ссылки.

2. Средства абстракции C++. Структура класса. Статические члены и их инициализация

3. Средства инкапсуляции C++. Инкапсуляция и наследование. Друзья.

4. Модульность, раздельная компиляция, пространства имен, using директива.

5. Представление иерархических отношений. Наследование.

6. Представление иерархических отношений. Агрегация. Зависимость по времени жизни.

7. Правила преобразования типов в C++. Параметрический и виртуальный полиморфизм.

8. C++: средства реализации состояния объектов; реализация поведения.

9. Перегрузка операторов.

10. Жизненный цикл объекта. Инициализация массивов. Конструкторы и деструкторы. Порядок вызова конструкторов и деструкторов при наследовании.

11. Варианты реализации отношения клиент-сервер. Объекты при передаче параметров и возврате из методов.

12. Исключения в C++. Обработка исключений. Умные указатели.

13. Шаблоны классов и шаблоны функций. Специализация.

14. Основы STL. Структура и назначение. Контейнеры.

15. Основы STL. Аллокаторы и итераторы.

 

 

Модель памяти и структура программы. Классы памяти. Ссылки.

Типы памяти в С++:

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

· а втоматическая (аргументы функций и локальные переменные, которые автоматически создаются и уничтожаются); автоматической памятью управляет компилятор

· свободная (динамическая) – куча. Свободной памятью управляет программист в коде программы с помощью new, delete, malloc, free

Создание автоматических объектов

class A

{

int k, double d;

public:

A ( ) = default;

A (const A&) = default;

A& operator= (const A&) = default;

A (int i, double x) : k(i), d{x} {}

};

void foo ()

{

A a; // k==0, d==0.0 – конструктор по умолчанию

A a2 (10, 7.0); //конструктор

A a3 {5, 6.0}; // унифицированная инициализация, тоже что и а2

A a4 = a2; // k==10, d ==7 - конструктор копии!

A a5 = {11, 2.0}; //тоже что и a3

}

 

Выделение памяти под массивы

· new[] выделяет чуть больше памяти чем нужно для размещения всех элементов массива – дополнительно хранится информация о размере массива

int a[] = new int[10];

delete a;    //Неправильно! Удалится только a[0]

delete[] a; //Правильно!

 

· безопасно вызывать delete на нулевом указателе

int *p = 0;

delete p; //Ok!

delete[] p; //Ok!

 

· При создании массива вызываются конструкторы по умолчанию для всех элементов массива.

· При удалении массива вызываются деструкторы для всех элементов массива

 

Ч то происходит при вызове MyClass *p = new MyClass()

1. вызывается operator new (sizeof(MyClass))

2. вызывается конструктор MyClass()

 

При вызо ве delete (p)

1. вызывается деструктор ~MyClass()

2. вызывается operator delete (p)

 

! Вызываемые операторы operator new и operator delete можно перегрузить в классе

 

Ссылки

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

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

· T& - ссылка на T; const T& - константная ссылка на T

· ссылку нельзя объявить, ее можно только определить; инициализатором для T& должно быть lvalue типа T

· инициализатор для const T& не обязан быть lvalue и даже иметь тип T!!!

 

Ссылки в С++ служат двум целям:

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

· Обеспечивают возможность возврата из функции lvalue.

 

Значение ссылки нельзя изменить после инициализации, она всегда ссылается на объект, которым была проинициализирована.

 

Программы на C++

Используется классическая схема:

· Исходные тексты программ хранятся в текстовых файлах на диске (*.cpp, *.cc, *.h, *.hpp)

· Единицей компиляции является файл .cpp или .cc

· Файлы .h и .hpp включаются препроцессором перед компиляцией файлов .cpp и .cc

Результатом компиляции являются объектные модули .o или .obj

· Редактор связей (линковщик) используется для компоновки объектных модулей в исполняемые файлы (.exe) либо библиотеки (статические .lib, либо динамические .dll, .ld.so).

· Возможна компоновка модулей на разных языках

 

2. Средства абстракции C++. Структура класса. Статические члены и их инициализация.

"Абстракция данных" это техника программирования. В С++ это по сути возможность создавать пользовательские классы.


Структура класса

Класс задается своим типом (именем класса), информацией о суперклассе и реализуемых интерфейсах, а также своими членами:

· Полями и методами экземпляра (реализуют свойства объекта)

· Статическими полями и методами класса (реализуют собственно свойства и поведение самого класса)

· Внутренними абстракциями (вложенные и внутренние классы и интерфейсы, как правило, не используемые извне класса)

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

Средства обобщенного программирования – шаблоны классов (родовые компоненты) позволяют порождать родственные классы.

Инстанцирование объектов – создание объекта на основе класса

Инстанцирование шаблонов – создание класса на основе шаблона класса

 

    Статические переменные и их инициализация

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

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

class counter {
static int count;  //static int count = 0; - ОШИБКА!
public:
void setcount(int i) {count = i;};
void showcount () {cout << count << " "; }
};
int counter::count; // определение count







Статические методы

У статических методов есть две интересные особенности. Во-первых, поскольку статические методы не привязаны к объекту, то они не имеют скрытого указателя this ! Здесь есть смысл, так как указатель this всегда указывает на объект, с которым работает метод. Статические методы могут не работать через объект, поэтому и указатель this не нужен.

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

Статические методы можно определять вне тела класса. Это работает так же, как и с обычными методами.

 

class IDGenerator

{

private:

     static int s_nextID; // объявление статической переменной-члена

 

public:

static int getNextID(); // объявление статического метода

};

int IDGenerator::s_nextID = 1;

  int IDGenerator::getNextID() { return s_nextID++; }

 

int main()

{

      for (int count=0; count < 4; ++count)

        std::cout << "The next ID is: " << IDGenerator::getNextID() << '\n';

 

    return 0;

}

 

 

3. Средства инкапсуляции C++. Инкапсуляция и наследование. Друзья.

Классы и инкапсуляция

Существуют следующие уровни доступа к членам класса:

· Открытый (public) – члены с данным уровнем доступа видимы всем клиентам класса

· Защищенный (protected) – члены этого уровня видимы самому классу и его наследникам

· Закрытый (private) – члены данного уровня видны только изнутри самого класса

 

Инкапсуляция и наследование:

Концепции:

· Уточнение (переопределение поведения - перегрузка)

· Расширение (интерфейса абстракции)

· Переиспользование (использование ранее написанного кода, иногда, в данном случае, лучше воспользоваться агрегацией)

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

class A{

public: int a;

protected: int b;

private: int c;

};

 

class B : public A{};

 

Методы производного класса имеют доступ к членам базового класса, если они имеют спецификатор доступа public или protected. К членам, объявленным как private, доступа нет.

 

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

· public - Права доступа к public и protected членам, наследуемые от базового класса, в производном не меняются.

class D: public B {...}

o Открытые ( public ) и защищенные ( protected ) члены класса B могут использоваться функциями-членами и друзьями класса D, а также могут использоваться функциями-членами и друзьями классов –наследников D.

o Открытые члены B доступны через переменные (ссылки и указатели) типа D

o Всем разрешено осуществлять преобразование типа D в B, D* в B*, D& в B&

 

· protected - Если класс наследовался как protected, то все public и protected члены базового класса становятся protected членами производного.

class D : protected B {...}

o Открытые ( public ) и защищенные ( protected ) члены класса B могут использоваться только функциями-членами и друзьями класса D, а также могут использоваться функциями-членами и друзьями классов – наследников D. Доступ к ним извне через переменные (ссылки, указатели) типа D запрещен

o Только друзья и члены D, а также члены и друзья классов-наследников D могут преобразовывать D* в B* и D& в B& и D в B

 

· private - Если класс наследовался как private, то public и protected члены, наследованные от базового класса, становятся private членами производного класса. В любом случае, private члены базового класса становятся private членами произвольного, причем доступ к этим членам закрыт в производном классе. Чтобы его открыть, производный класс должен быть объявлен другом базового (friend).

       class D : private B {...}:

o Открытые ( public ) и защищенные ( protected ) члены класса B могут использоваться только функциями-членами и друзьями класса D. Доступ к ним извне через переменные (ссылки, указатели) типа D запрещен

o Только друзья и члены D могут преобразовывать D* в B* и D& в B& и D в B

o Наследники класса D “не будут знать” о его базе B

 

Friend функции

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

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

 

class Auto;

 

class Person

{

public:

Person(std::string n)

{

name = n;

}

void drive(Auto &a);

void setPrice(Auto &a, int price);

 

private:

std::string name;

};

 

class Auto

{

friend void Person::drive(Auto &); //определение друга внутри другого класса

friend void Person::setPrice(Auto &, int price);

public:

Auto(std::string autoName, int autoPrice)

{

name = autoName;

price = autoPrice;

}

std::string getName() { return name; }

int getPrice() { return price; }

 

private:

std::string name; // название автомобиля

int price; // цена автомобиля

};

 

Директива Using

Для объявления имени из некоторого пространства имен используется объявление using, которое может находиться в любой области видимости, включая область видимости любого пространства имен. Синтаксис:

u sing имя_пр_имен имя_объекта;

 

 

Объявление using может использоваться для вложенных пространств имен.

Для объявления сразу всех имен из некоторого пространства имен используется директива using вида:

using namespace имя _ пр _ имен ;

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

namespace псевдоним = имя_пр_имен ;

(например: using FactoryMap = std::map<Key, AbstractCreator<BaseType, Args...>*>;)

  Раздельная компиляция

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

1) Препроцессинг

Самая первая стадия компиляции программы.

Препроцессор — это макро процессор, который преобразовывает вашу программу для дальнейшего компилирования. На данной стадии происходит происходит работа с препроцессорными директивами. Например, препроцессор добавляет хэдеры в код (#include), убирает комментирования, заменяет макросы (#define) их значениями, выбирает нужные куски кода в соответствии с условиями #if, #ifdef и #ifndef.

 

2) Компиляция

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

 

Ассемблерный код — это доступное для понимания человеком представление машинного кода.

 

 

3) Ассемблирование

Далее, необходимо перевести ассемблерный код в машинный с помощью ассемблера.
Ассемблер преобразовывает ассемблерный код в машинный код, сохраняя его в объектном файле.

 

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

 

 

4) Компоновка

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

 

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

 

5) Загрузка

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

 




Заголовочные файлы

o используются для включения общих частей кода (обычно прототипов) для многократного использования

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

o могут содержать:

§ именованные пространства имен

§ объявления типов, шаблонов, функций, данных, перечислений

§ определения шаблонов, inline функций, констант

§ директивы включения, макроопределения, директивы условной компиляции, комментарии

o не должны (а то Ефремов пиздить будет):

§ определения обычных функций, данных, агрегатов (массивов)

§ неименованные пространства имён, экспортируемые определения шаблонов

 

Инициализация переменных при запуске:

o Исполнение начинается с main

o Все нелокальные переменные инициализируются до его вызова

o Нет фиксированного порядка инициализации между единицами трансляции

o Внутри единицы трансляции - в порядке объявления

Разрешение перегрузки

1. Точное соответствие типов; в т.ч. соответствие, достигаемое тривиальным преобразованием типов (имя массива и указатель, имя функции и указатель на функцию, тип T и const T)

2. Соответствие, достигаемое «продвижением» интегральных типов (bool в int, char в int, short в int, в unsigned аналоги), float в double и double в long double

3. Соответствие, достигаемое путем стандартных преобразований ( int в double, double в int, указатели на производные типы в указатели на базовые, любые указатели в void*, T в unsigned T)

4. Соответствие, достигаемое путем преобразований, определяемых пользователем

class A {

public: A(int) {...}

};

 

class B {

public: operator A() {...}

……………..

  };

       Можно запретить неявное приведение типов если сделать конструктор explicit

  5. Соответствие за счет «...» в объявлении функции

int print(const char* format ...);

 

int main() {

A a;

B b;

int i = 10;

print(“ %A ”,a);

print(“ %B ”,b);

print(“ %d ”,i);

}

 

   Перегрузка и область видимости

Функции, объявленные в различных областях видимости (не пространствах имен) не являются перегруженными

 

Инстанцирование шаблонов

Тоже вид параметрического полиморфизма. Тут подробно рассказывать не буду. Главное, что надо сказать, шаблон определяет семейство классов (метакласс, ыыыы) или функций => результат инстанцирования с указанием параметров шаблона – функция или класс. Создаются только те классы и функции, которые потом используются в программе(что логично).

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

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

 

final - С++ 11 также добавляет возможность предотвращения наследования от классов или просто предотвращения переопределения методов в производных классах. Это делается с помощью специального идентификатора final. Например:

 

       struct Base1 final { };

    

struct Derived1 : Base1 { }; // ill-formed because the class Base1

                        // has been marked final

 

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

struct Base2 {

virtual void f() final;

};

 

struct Derived2 : Base2 {

void f();   // ill-formed because the virtual function Base2::f has

            // been marked final

};

8. C++: средства реализации состояния объектов; реализация поведения.

Состояние

Состояние объекта характеризуется перечнем (обычно статическим) всех свойств данного объекта и текущими (обычно динамическими) значениями каждого из этих свойств.

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

 

 

Переменные-члены

Переменные-члены класса могут объявляться как:

• статические (static)

Тогда будет существовать только одна копия этой переменной, независимо от того, сколько объектов класса создается.

 

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

 

Статическая переменная-член класса создается еще до того, как создан объект этого класса; по сути, это просто глобальная переменная, область видимости которой ограничена классом, в котором она объявлена.

 

Все стататические переменнные-члены по умолчанию инициализируются нулем, однако можно задать отличное от этого значение.

• обычные

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

 

Функции-члены

Помимо переменных-членов статическими можно объявлять и функции-члены.

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

 

У стат. ф.-члена нет указателя this, они не могут быть виртуальными, объявляться как const и volatile. Стат. ф.-члены класса могут вызываться любым объектом этого класса, а также через имя класса и оператор расширения области видимости без связи с объектом этого класса.

 

Ф.-члены класса могут объявляться постоянными (const). Если ф-ция объявлена постоянной, она не может изменить вызывающий ее объект (только поля, помеченные mutable). Кроме того, постоянный объект не может вызвать непостоянную ф-цию член.

Однако постоянная ф.-член может вызываться как постоянными, так и непостоянными объектами.

 

Перегрузка операторов.

В С++ операторы рассматриваются как функции, которые имеют прототип:

тип operator имя_оператора (список_параметров );

       Перегрузка операторов

Перегрузка операторов – средство для короткой записи предопределенного поведения

Связь операторов и функций:

Выражение Функция – член Свободная функция
@a (a).operator@() operator@(a)
a @ b (a).operator@(b) operator@(a,b)
a @= b (a).operator@=(b)  
a[b] (a).operator[](b)  
a(x,y) (a).operator()(x,y)  
a-> (a).operator->()  
a@ (a).operator@(int) operator@(a,int)

Конструктор по умолчанию

-автоматически генерится компилятором

-вызывает конструкторы базовых классов по порядку

-не генерится:

- если есть ссылка или константа без инициализатора

- есть член без конструктора по умолчанию

- нет конструктора по умолчанию или деструктора у базового класса

Деструктор

-автоматически генерируется компилятором, если нет явного

-не может кидать исключений

-не генерится:

-если имеет нестатический член без деструктора

- базовый класс без конструктора

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

- автоматически генерируется компилятором, если нет явного

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

-не генерируется, если:

- нет конструктора копирования базового класса

- нет конструктора копирования одного из нестатических членов

- если есть член –rvalue ссылка (нельзя копировать, только перемещать)

- есть явный перемещающий конструктор или оператор присваивания

Варианты реализации отношения клиент-сервер. Объекты при передаче параметров и возврате из методов.

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

 

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

• Сервер имеет глобальную видимость по отношению к клиенту

• Сервер передан клиенту в качестве параметра операции (метода)

• Сервер является частью клиента

• Сервер локально порождается клиентом в ходе выполнения какой-либо операции

 

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

 

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

 

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

 

Когда работа функции, которой был передан объект, завершается, то копия аргумента удаляется.

 

12. Исключения в C++. Обработка исключений. Умные указатели.

Исключения в Си плас плас

Концепция

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

 

o Исключение может быть вызвано явно (throw) из какой-либо функции, либо в результате нарушения семантики языка (dynamic_cast, typeid)

o Чтобы быть перехваченным исключение должно выбрасываться изнутри try блока или функции вызванной из try блока который имеет подходящий по типу catch блок перехвата

o Исключения следует наследовать от std::exception

       Когда бросать?

Выброс исключения используется для сигнализирования об ошибках при вызове функции при следующих обстоятельствах:

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

o Невозможно выполнить «предусловия» другой функции, которая должна быть вызвана из текущей

o Для функций-членов, не являющихся private: невозможно установить или соблюсти «инвариант» объекта(???)

 

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

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

Перехват исключений

Исключения, находящиеся внизу иерархии наследования должны перехватываться первыми

void f() {

try {

//...

} catch (overflow_error) {

//...

} catch (runtime_error) {

//...

} catch (bad_alloc){

//...

} catch (exception){

//...

} catch (...) {

//...

}

Обработка исключений

Для перехвата исключения с возможностью работы с ним, необходимо в блоке catch использовать переменную – формальный параметр:

try {

...

} catch (exception &dafaq) {

cout<<dafaq.what();

}

 

С целью повторной генерации исключения необходимо использовать оператор throw без параметров:

                                                                                                                                                                              

try {

...

} catch (exception &dafaq) {

cout<<dafaq.what();

throw; //повторный выброс исключения

}

 

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

 

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

       Функция terminate()

❑ Вызывается при разрушении стека:

❑ если выбрасывается исключение во время обратной раскрутки стека при уже выброшенном исключении

❑ если вызывается throw; вне блока перехвата исключения (повторная генерация если выброса исключения не было)

❑ если произошло исключение но не было перехвачено

❑ если конструктор глобального статического объекта завершается аварийно с выбросом исключения

❑ По умолчанию terminate() вызывает abort()*

* Функция abort() вызывает немедленное прекращение программы.

 

Умные указатели:

o unique_ptr<T> - эксклюзивное владение указателем на объект типа T с возможностью передачи владения другому unique_ptr, поддерживает определенный пользователем «уничтожитель» - Deleter (опция)

o shared_ptr<T> - общее владение указателем на объект с подсчетом ссылок, семантикой копирования, возможностью преобразования

o weak_ptr<T> - позволяет работать с указателем, управляемым shared_ptr без «владения»

 

UNIQUE

 

std::unique_ptr – умный указатель, который:

§ получает единоличное владение объектом через его указатель, и

§ разрушает объект через его указатель, когда unique_ptr выходит из области видимости.

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

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

Существует две версии std::unique_ptr:

1) управляет временем жизни одного объекта, например, созданного с помощью оператора new

2) управляет временем жизни массива, с длиной, определенной во время выполнения, созданного с помощью new[]

Типичные случаи применения std::unique_ptr включают:

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

§ передача владения динамически созданным объектом в функции

§ получение владения динамически созданным объектом из функций

§ в качестве типа элемента в контейнерах, поддерживающих семантику перемещения, таких как std::vector, которые хранят указатели на динамически выделенные объекты (например, если желательно полиморфное поведение)

Для массива:

 

std::unique_ptr<int[]> ptr(new int[10]);

  for (int i = 0; i < 10; i++)

        ptr[i] = 2 * i;

 

 

Пример реализации своего deleter для смарт указателей:

class TextureDeleter

{

public:

TextureDeleter()

{}

 

void operator()(SDL_Texture *ptr) const

{

           SDL_DestroyTexture(ptr);

}      

~TextureDeleter()

{}

};

 

void foo()

{

...................

std::unique_ptr<SDL_Texture, TextureDeleter> a(IMG_LoadTexture(canvas, name.str().c_str()), TextureDeleter());

textures["walls"].push_back(std::move(a));

   ...................

}

 

 

Shared

std::shared_ptr – умный указатель, с разделяемым владением объектом через его указатель. Несколько указателей shared_ptr могут владеть одним и тем же объектом; объект будет уничтожен, когда последний shared_ptr, указывающий на него, будет уничтожен или сброшен. Объект уничтожается с использованием delete-expression или с использованием пользовательской функции удаления объекта, переданной в конструктор shared_ptr.

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

 

std::shared_ptr<int> sptr(new int);

  {

        std::shared_ptr<int> p2 = sptr;    //умрет после выхода из блока

        std::cout << sptr.unique() << std::endl;

  }

std::cout << sptr.unique() << std::endl;

 

Выведет: 0

        1

      

Weak

std::weak_ptr – умный указатель, который содержит "слабую" ссылку на объект, управляемый указателем std::shared_ptr. Чтобы получить доступ к управляемому объекту, указатель необходимо привести к типу std::shared_ptr, .

std::weak_ptr моделирует временное владение: когда объект должен быть доступен только если он существует и может быть удален в любой момент кем-то другим, std::weak_ptr используется для отслеживания объекта, и преобразуется в std::shared_ptr для принятия временного владения. Если исходный std::shared_ptr будет уничтожен в процессе работы, время жизни объекта продлевается до того момента, пока не будет разрушен временный std::shared_ptr.

Помимо этого, std::weak_ptr используется для устранения циклических ссылок std::shared_ptr.

void f()

{

if (auto p = wp.lock())

      std::cout << *p << std::endl;

   else

           std::cout << "Shit, it died!" << std::endl;

}

 

int main()

{

       

   {

      std::shared_ptr<int> sp(new int);

      *sp = 5;

           wp = sp;

           f();

        }

f();

    return 0;

}

Выведет : 5

Shit, it died!

 

Шаблоны функций

 

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

 

Специализация

 

-По умолчанию шаблон – единое определение типа для всевозможных значений аргументов

 

-Однако иногда для некоторого подмножества наборов аргументов необходимо определенное поведение (например, специализация vector<bool> отличается от просто вектора от T)

 

Правила специализации:

 

-Общий шаблон должен быть объявлен до специализации

-Специализация должна быть видима везде, где она должна использоваться, вместе с основным шаблоном

-Специализации в том же пространстве имен, что и сам шаблон

 

-Одна специализация – специализация для другой специализации, если всякое множество параметров, удовлетворяющих одной специализации удовлетворяет и другой специализации

 

-Для функций частичной специализации – нет!!! (решается через структуры)

Наследование и шаблоны:

 

-Шаблон – метакласс, поэтому можно наследовать только конкретную его реализацию

 

Variadic шаблоны (sizeof…(Args))

-Самый яркий представитель – std::tuple

-Полезно для операций над кучей аргументов

 

Организация исходников для шаблонов:

-Объявления и определения шаблонов все в заголовочных файлов (вместе с специализациями)

 

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

-Линковщик удаляет лишние дубли

 

- template class MyClass <int, double>; - явное инстанцирование шаблонного класса MyClass в единице трансляции

 

 

Пример работы с вариадиками и “специализацией” функций через костыли                               

namespace tpl

{

  template <typename Stream, typename Type, size_t _Cur, size_t _Last>

  class tuple_printer

  {

  public:

        static void print(Stream& os, const Type &t)

        {

               os << std::get<_Cur>(t) << ", ";

               tuple_printer<Stream, Type, _Cur + 1, _Last>::print(os, t);

        }

  };

 

  template <typename Stream, typename Type, size_t _Last>

  class tuple_printer<Stream, Type, _Last, _Last>

  {

  public:

        static void print(Stream& os, const Type &t)

        {

               os << std::get<_Last>(t);

        }

  };    

}

 

template <typename Ch, typename Tr, typename... Args>

auto operator<<(std::basic_ostream<Ch, Tr> &os, const std::tuple<Args...> &t)

->std::basic_ostream<Ch, Tr>&

{

  os << '(';

tpl::tuple_printer<std::basic_ostream<Ch, Tr>, std::tuple<Args...>, 0, sizeof...(Args) - 1>::print(os, t);

  os << ')' << std::endl;

  return os;

}

 

Простейший пример специализации шаблонов классов:

template<class T>

class A {};

 

template<>

class A<int> {}

 

SDL. Аллокаторы / Итераторы

(Надо смотреть в лекциях!!!)

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

- для выделения памяти под n объектов

- освобождения памяти по указателю, выделенной под n объектов

-создания экземпляра объекта в заданном месте памяти

-удаления экземпляра объекта в данном месте памяти

(Вынесли этот функционал из вектора по принципу единственности ответственности + возможность специализировать аллокатор для нужного нам типа по своему)

 

Итератор – класс, обеспечивающий доступ к элементам, содержащимся в контейнере в определенном порядке (должен поддерживать разыменование, оба инкремента и проверку на равенство)

Возможности итераторов:

-Все:

-оператор и конструктор копирования

-движение вперед

-Для чтения

-однократное чтение (корректность повторного разыменования итератора не гарантируется)

-сравнение итераторов

- (*iter).m ó iter->m

-Для записи:

-однократная запись (аналогично см. чтение)

-для однопроходных алгоритмов (файл открыли, прошли, всё, назад низя)

-Прямой:

-гарантируется корректность повторного чтения и записи

-Двунаправленный:

-все возможности прямого

-движение назад

-реализован в map, list, set

-Произвольного доступа:

-все возможности двунаправленного

-поддерживает +=, -= числа, +, -, [] (как указатели)

-вычитание (если они над одной последовательностью), возвращает расстояние

-сравнение

-реализован в vector, deque

 

 (Всякая ерунда)

Модули в C++

С целью оптимизации процесса разработки и борьбы со сложностью системы абстракций, программы на C++ разбиваются на модули (исходные файлы – единицы компиляции “.cc” и “.cpp”)

Каждый файл может содержать объявления и определения элементов программы (переменных, функций, структур и классов, шаблонов, определений типа typedef, пространств имен и т.д.)

Каждый элемент имеет свой тип связи (linkage):

· no linkage (для элементов на стеке)

· internal linkage (область видимости – файл)

· external linkage (область видимости – проект)

 

Типы данных

❑ Целочисленные:

· int d = 42; //десятичные

· int o = 052; //восьмеричные

· int x = 0x2a; //шестнадцатеричные

· int X = 0X2A; // шестнадцатеричные

· int b = 0b101010; // двоичные

 

Область определения и видимости

· Локальные переменные скрывают глобальные и определенные вне блока

· для доступа к глобальным можно использовать оператор разрешения видимости ::

· доступ к скрытым локальным переменным невозможен

◼ Глобальные переменные инициализируются значением по умолчанию (0 для большинства типов: char, int, float, double, указателей)

◼ Локальные переменные не инициализируются по умолчанию и должны быть проинициализированы

 

 

Rvalue и lvalue

Допускается выделять память и «использовать

переменные», у которых нет имен, а также присваивать значения странно выглядящим выражениям: *p[a+10] = 10

 

· Вводится понятие «что-то в памяти» - это и есть lvalue – то, что может стоять слева от знака присваивания. Т.е. lvalue – это выражение, ссылающееся на объект в памяти или функцию.

· rvalue – это выражение, которое имеет значение и может стоять справа от знака присваивания. Lvalue может быть автоматически преобразовано в rvalue

· присвоить значение lvalue ссылающемуся на константу – нельзя (lvalue, которое не было объявлено константой называют модифицируемым lvalue).

 

CV квалификаторы

Переменные могут быть в своем объявлении содержать CV квалификаторы (const, volatile)

 

Существуют следующие варианты CV квалификации переменной типа T (кроме void):

· const T var; // константа

· volatile T var; // volatile объект

· const volatile T var; // константный volatile объект

❑ const объект не может быть изменен

❑ volatile служит для запрещения оптимизации)

❑ для снятия CV используется const_cast<T>

 

Typedef & using:

typedef unsigned int uint;

using uint = unsigned int;

 

 

Существуютм четыре категории преобразования типов:

❑ static_cast<T>() – тоже что и (T)

❑ dynamic_cast<T>() – используется для указателей и ссылок на полиморфные типы

❑ const_cast<T> – снятие CV

❑ reinterpret_cast<T> – реинтерпретация участка памяти

 

Члены классов

· объявления статических полей

· определения нестатических полей и констант

· объявление статических и нестатических функций-членов

· определения статических и нестатических функций-членов

· объявление вложенных типов

· определение вложенных типов

· декларации дружественности

· декларации использования

 

Объявления и определения

· Объявление – объявляется только тип и имя сущности

· Определение – определяется устройство/реализация или размещение – определяется сущность

 

           

От создателей первой части

Вопросы к зимнему экзамену по ООП Part 2

Вопросы по C++

 

НГУ 2018

Абстракцию забыли =\

 

1. Модель памяти и структура программы. Классы памяти. Ссылки.

2. Средства абстракции C++. Структура класса. Статические члены и их инициализация

3. Средства инкапсуляции C++. Инкапсуляция и наследование. Друзья.

4. Модульность, раздельная компиляция, пространства имен, using директива.

5. Представление иерархических отношений. Наследование.

6. Представление иерархических отношений. Агрегация. Зависимость по времени жизни.

7. Правила преобразования типов в C++. Параметрический и виртуальный полиморфизм.

8. C++: средства реализации состояния объектов; реализация поведения.

9. Перегрузка операторов.

10. Жизненный цикл объекта. Инициализация массивов. Конструкторы и деструкторы. Порядок вызова конструкторов и деструкторов при наследовании.

11. Варианты реализации отношения клиент-сервер. Объекты при передаче параметров и возврате из методов.

12. Исключения в C++. Обработка исключений. Умные указатели.

13. Шаблоны классов и шаблоны функций. Специализация.

14. Основы STL. Структура и назначение. Контейнеры.

15. Основы STL. Аллокаторы и итераторы.

 

 

Модель памяти и структура программы. Классы памяти. Ссылки.

Типы памяти в С++:

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

· а втоматическая (аргументы функций и локальные переменные, которые автоматически создаются и уничтожаются); автоматической памятью управляет компилятор

· свободная (динамическая) – куча. Свободной памятью управляет программист в коде программы с помощью new, delete, malloc, free

Дата: 2019-02-19, просмотров: 636.