- Оглавление
- Ключевые слова/операторы
- Наследование
- Умные указатели
- STL
- Исключения/обработка ошибок
- Выделение памяти
- Классы/Структуры
- Шаблоны
- Переменные/аргументы функций
- Компилятор
- Всякое
- Инициализация
- ABI
- RTTI
- Slicing
- PIMPL
- variadic templates
- Precompiled headers
- Ключевые слова/операторы:
- Static в с++:
- Для статических элементов память выделяется только один раз и хранятся они в отдельном сегменте памяти. Например, статическая переменная созданная внутри функции будет сохранять свое значение между вызовами функции.
- При объявлении статической функции/переменной в .cpp, мы говорим компилятору, что доступ к этой функции ограничен файлом, в котором она объявлена. (Пришло из Си).
- При объявлении статической функции в .h, появляется возможность у каждого .cpp файла, который сделает инклюд этого .h, иметь собственную реализацию этой функции.
- Статические методы класса, используются без создания экземпляра класса, через оператор ::, могут обращаться только к статическим членам класса и статическим методам класса.
- Const в с++:
- Константные классы:
- Константные экземпляры класса могут явно вызывать только константные методы класса. Проверяется на этапе компиляции.
- const в методе класса, говорит о том, что этот метод не будет влиять на внутренее состояние класса. Проверяется на этапе компиляции.
- const int *ptr1 = &value; ptr1 указывает на "const int", поэтому это указатель на константное значение
- int *const ptr2 = &value; ptr2 указывает на "int", поэтому это константный указатель на неконстантное значение
- const int *const ptr3 = &value; ptr3 указывает на "const int", поэтому это константный указатель на константное значение
- Константные классы:
- Mutable:
- Используется для переменных константного объекта класса. Например, для захвата мьютекса, внешне объект остается неизменным, но внутри нужно поменять его состояние.
- Для изменения переменных захваченных лямбда функциями по значению.
- constexpr: #TODO
- auto и decltype:
- auto -
- decltype -
- typeid: #TODO
- sizeof: #TODO
- alignof: #TODO
- explicit: запрещает неявные преобразования. Например, есть класс с конструктором с одним параметром принимающим int, и функция принимающая экземпляр этого класса. Если передать в функцию переменную типа int, то компилятор может использовать конструктор класса для преобразования к нужному типу, explicit запрещает такие преобразования.
- Static в с++:
- Наследование:
- Таблицы виртуальных функций: Хранят в себе массив указателей на наиболее дочерние методы для данного класса. Для каждого класса содержащего виртуальные методы, создается своя таблица виртуальных функций. Базовый класс содержит указатель на таблицу. При наследовании, так же наследуется указатель на VTABLE, который будет указывать на таблицу наследника. Через указатель с типом базового класса, который указывает на экземпляр наследника, нельзя обратиться к специфичным методам наследника, но можно обратиться к переопределенным методам базового класса, используя указатель на VTABLE, поскольку он будет указывать на таблицу наследника и следовательно будут вызываться методы наследника. Абстрактные классы имеют таблицу, но указатели в ней будут нулевыми или ссылаться на функцию, которая выводит ошибку (__purecall?)
- Типы наследования:
- public - public и protected данные наследуются без изменения уровня доступа к ним
- protected — данные protected и public базового класса становятся protected
- private - данные protected и public базового класса доступны из методов производного класса, но недоступны извне, то есть они становятся private.
- Множественное наследование:
- Ромбовидное наследование
- Виртуальное наследование: : предотвращает появление множественных объектов базового класса в иерархии наследования
- Виртуальный деструктор: : если деструктор не объявлен как виртуальный, то при удалении объекта наследника через указатель на базовый класс, будет вызван только деструктор базового класса
- Умные указатели:
- RAII: Захват ресурса есть инициализация, инициализируем ресурс в конструкторе, освобождаем в деструкторе.
- unique_ptr:
- shared_ptr:
- Control block - содержит счетчик ссылок на shared и weak ptr, удаляется, когда оба счетчика достигнут 0.
- Почему "std::make_shared()" лучше чем "std::shared_ptr(new A())" - потому что во втором случае, сначала конструируется объект, и только затем конструируется умный указатель и создается control block. В первом случае они создаются одновременно
- Потокобезопасноть -
- Цикличные ссылки - когда первый объект ссылается на второй, а второй ссылается на первый
- weak_ptr:
- STL:
- Многопоточность STL:
- Контейнеры STL:
- reserve vs resize -
- Последовательные:
- Vector - Занимает больше места, чем статические массивы, поскольку заранее выделяет память "про запас". Это спасает от дорогостоящей операции релокации массива. Доступ - константная сложность, вставка, поиска и удаление - линейная.
- Array - Массив фиксированного размера.
- Deque - Двунаправленная очередь, реализуется с помощью набора массивов фиксированной длины, если нужно вставить элемент в начало, просто создается еще один массив и элемент добавляется в его конец. Доступ - константная сложность, вставка и удаление в начало и конец - константная сложность, вставка, поиск и удаление - линейная.
- List - Двусвязный список, представляет из себя структуру с пейлоадом и ссылками на предыдущий и следующий элемент списка. Вставка и удаление - константная сложность, поиск и доступ - линейная.
- Forward_list - Тоже самое, что и список, но однонаправленный. Т.е. есть ссылка только на следующий элемент.
- Ассоциативные:
- Map:
- Элементы упорядочены по ключу.
- Базируется на базе двоичного дерева, в частности красно-черного дерева.
- Алгоритмическая сложность - логарифмическая, в худшем случае линейная, при балансировке дерева.
- Set - #TODO
- Unordered_map:
- Элементы не отсортированы.
- На основе хеш-функции, генерируется индекс, по которому будет сохранен новый элемент.
- Алгоритмическая сложность - константная. При коллизии индексов, сложность возрастает до линейной.
- Коллизию можно решить введением списка в место пересечения и добавлением элементов с одинаковым индексом в него.
- Unordered_set -
- Map:
- Адаптеры:
- Queue - Реализует структуру данных first in first out. #TODO
- Priority_queue - #TODO
- Stack - Реализует структуру данных last in first out. #TODO
- Исключения/обработка ошибок:
- Исключения vs return codes:
- Исключения позволяют четко разделить обработку кода и обработку ошибок.
- Необработанное исключение аварийно завершит программу.
- Код ошибки не позволяет возвращать другой результат функции напрямую.
- Нельзя возвращать код ошибки из операторов, конструкторов.
- Обработка исключений более ресурсозатрано. В момент выброса исключения происходит раскрутка стека. #TODO
- Раскрутка стека: При выбросе исключения, программа последовательно спускается на уровень ниже, к вызывающей функции, до тех пор, пока не найдет соответствующий обработчик исключения. При переходе на блок catch, вызываются деструкторы у всех объектов созданных на стеке, в обратном порядке их конструирования.
- Исключения выброшенное в конструкторе:
- При выбросе исключения в конструкторе, не будет вызван деструктор текущего объекта.
- Можно бросать исключения, если в конструкторе не инициализировалось никаких ресурсов, которые в дальнейшем будут освобождены в деструкторе.
- Исключения выброшенное в деструкторе:
- Если в деструкторе будет блок try catch, который обработает это исключение, то все хорошо.
- Если попали в деструктор во время раскрутки стека при уже выброшенном исключении и если в этот момент выбросить еще одно исключение, то будет вызвано std::terminate()
- Исключения vs return codes:
- Выделение памяти:
- Placement new: Используется для инициализации (вызова конструктора) объекта в уже выделенной памяти.
- new() vs malloc():
- new() - оператор, malloc() - функция.
- new() инициализирует память в 0, malloc() оставляет значение неопределенным.
- new() вызывает конструктор объекта, malloc() просто выделяет память
- Классы структуры:
- Упакованные структуры: #TODO
- Размер пустого класса: Равен одному байту, это сделано для того, чтобы у каждого объекта был свой уникальный адрес.
- Интерфейс vs абстрактный класс:
- Абстрактный класс - класс, в котором реализована часть методов и в котором определена хотя бы одна чисто виртуальная функция (без реализации). Что запрещает создавать экземпляр этого класса.
- Интерфейс - класс, в котором все функции - чисто виртуальные функции (так же не имеет членов класса (переменных)).
- Правило трех/пяти: Если один из методов (конструктор, конструктор копирования, конструктор перемещения, оператор копирования, оператор перемещения, деструктор) определен программистом, то и остальные методы должны быть определены программистом.
- Конструктор копирования vs оператор присваивания:
- Конструктор копирования используется для инициализации ранее неинициализированного объекта с помощью данных другого объекта.
- Оператор присваивания используется для замены данных ранее инициализированного объекта с помощью данных другого объекта.
- Порядок инициализации конструкторов/деструкторов:
- Конструкторы инициализируются, сначала базовые, затем наследники
- Перед конструктором инициализируются все члены-класса
- Деструкторы в обратном порядке, сначала наследники, затем базовые
- Если деструктор не помечен как virtual, то при уничтожении объекта наследника, который хранится в указателе базового класса, будет вызван только дестркутор базового класса
- Шаблоны:
- Инстанцирование шаблона: Генерация кода шаблона с конкретными параметрами.
- Частичная специализация шаблонов: Реализация шаблона с частичным указанием типов параметров.
- Полная специализация шаблона: Реализация шаблона с явным указанием всех типов параметров шаблона. Рассматривается компилятором, как отдельный и независимый класс.
- SFINAE: Неудачная подстановка типов в шаблон, не является ошибкой, такие шаблоны отбрасываются и компилятор ищет дальше нужную перегрузку. Как пример, для одного типа данных используется вывод через cout, а для второго через print, можно сделать специализацию шаблона для каждого типа, но если потом будут добавляться новые типы данных, то нужно добавлять новые специализации. Тут на помощь приходит SFINAE, при определенном условии, для части типов будет выбираться нужная перегрузка. (enable_if)
- Переменные/аргументы функций:
- Способы передачи аргументов в функцию:
- T - передача по значению, создается копия объекта, которая и будет изменяться внутри функции, не затрагивая изначальнй объект.
- T& - передача по ссылке, внутри функции будет использоваться изначальный объект.
- const T& - тоже самое, но только read-only доступ.
- T&& - передача по rvalue-ссылке, объект "перемещается" внутрь функции и перестает существовать снаружи.
- Ссылки vs указатели:
- Ссылка должна быть проинициализирована при создании.
- Указатель имеет свой адрес, который можно получить через оператор &.
- С указателями работает арифметика указателей.
- Указатель занимает память в зависимости от архитектуры платформы, ссылка не занимает память, так как не является объектом. Если значение ссылки нельзя узнать на этапе компиляции, то она реализуется как замаскированный указатель.
- Ссылка как аргумент функции - замаскированный указатель.
- Сколько места занимает ссылка:
- В большинстве случаев - ничего не занимает, поскольку просто является псевдонимом другого объекта.
- При передаче как параметр - #TODO
- rvalue/lvalue: #TODO
- Move семантика:
- std::move - преобразует lvalue в rvalue
- std::forward - #TODO https://habr.com/ru/post/8.42639/
- Способы передачи аргументов в функцию:
- Компилятор:
- Ранее и позднее связывание:
- Ранне связывание - означает, что вся необходимая информация для того, чтобы определить, какая именно функция будет вызвана, известна на этапе компиляции программы. В с++ - это стандартные вызовы функций.
- Позднее связывание - означает, что объект связывается с функцией на этапе выполнения программы. В с++ достигается с помощью виртуальных функций.
- ADL: #TODO
- Этапы компиляции:
- Препроцессинг - пропроцессор добавляет хэдеры в код, заменяет макросы их значениями, выбирает условия в #if #ifdef #ifndef.
- Компиляция - превращает полученный код в ассемблерный код.
- Ассемблирование - превращает полученный код в машинный код, сохраненный в объектный файл.
- Линковка - связывает все объектные файлы и библиотеки в исполняемый файл. Таблица символов — это структура данных, создаваемая самим компилятором и хранящаяся в самих объектных файлах. Таблица символов хранит имена переменных, функций, классов, объектов и т.д., где каждому идентификатору (символу) соотносится его тип, область видимости. Также таблица символов хранит адреса ссылок на данные и процедуры в других объектных файлах.
- Ранее и позднее связывание:
- Всякое:
- C++ style cast vs C style cast:
- С++ style касты проще искать в коде, они безопаснее, потому что проверяют возможность преобразования типов.
- C style каст последовательно перебирает другие преобразования и применяет первый подходящий, что может привести к ошибкам.
const_cast static_cast static_cast followed by const_cast reinterpret_cast reinterpret_cast followed by const_cast
- static_cast:
- Для преобразований простых типов, enum to int, int to float.
- Для преобразований указателей и ссылок разных типов по иерархии наследования классов или преобразование к указателю на void.
- Нелья преобразовывать значение к указателю или наоборот.
- Проверка происходит на этапе компиляции.
- const_cast - для добавления/удаления const и volatile квалификаторов
- dynamic_cast - для преобразования указателя/ссылки базового класса к указателю наследника, с проверкой во время выполнения. Поскольку указатель может хранить как адрес наследника, так и базового класса (Понижающее приведение, не работает если наследование типа private или protected.).
- reinterpret_cast - для преобразования указателя к целому, указателя к указателю. Не снимает cv квалификаторы.
- Функторы/Лямбда-функции: #TODO
- Функтор - это объект, который можно использовать подобно вызову функции, в с++ это достигается путем перегрузки оператора ().
- Лямбда - синтаксический сахар для функторов? Позволяет объявить анонимную функцию
- C++ style cast vs C style cast:
- ABI: Application Binary Interface. Стандарт языка описывает, общее поведение, а ABI конкретную реализацию (как в памяти распалогаются классы, как распалагаются виртуальные таблицы и т.п.). Две библиотеки собранные с разным ABI нельзя слинковать вместе.
- RTTI: (run-time type identification - RTTI) Используется для определения типа объекта, во время выполнения программы. Для этого используется функция typeid(объект).
- Slicing: #TODO
- PIMPL: #TODO https://habr.com/ru/post/9.21602/
- variadic templates: https://habr.com/ru/post/8.48897/
- Precompiled headers: https://habr.com/ru/company/pvs-studio/blog/227521/