Динамический полиморфизм c что это
Перейти к содержимому

Динамический полиморфизм c что это

  • автор:

Полиморфизм. Виртуальные функции. Общие понятия. Спецификаторы virtual и override . Примеры

Виртуальные функции реализуют так называемый полиморфизм. Термин «полиморфизм» происходит от греческих слов poly (много) и morphos (форма). Полиморфизм – это свойство программного кода изменять свое поведение в зависимости от ситуации, возникающей при выполнении программы. В контексте реализации полиморфизм – это технология вызова виртуальных функций, реализуемых в иерархически связанных классах. Иерархия классов формируется на базе механизма наследования.

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

Механизм виртуальных функций реализует основополагающий принцип полиморфизма: «один интерфейс, несколько реализаций» или «один интерфейс, несколько методов».

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

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

Для реализации позднего связывания требуется следующее:

  • классы обязаны образовывать иерархию с помощью механизма наследования;
  • в иерархии классов были определены функции, имеющие одинаковое имя и список параметров;
  • функции с одинаковым именем и параметрами должны быть отмечены как виртуальные (с ключевым словом virtual ).

Относительно парадигмы классов и объектов для полиморфизма можно выделить следующие характерные особенности:

  • полиморфизм не характеризует объект;
  • реализацию полиморфизма определяет архитектура класса;
  • полиморфизм является характеристикой функций-членов класса;
  • весь класс не может быть полиморфным, полиморфными могут быть только функции члены класса.
2. Виды полиморфизма. Динамический полиморфизм. Виртуальная функция. Организация цепочки виртуальных функций. Спецификаторы virtual и override

В языке C++ есть возможность реализовывать два вида полиморфизма:

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

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

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

В наиболее упрощенном виде объявление виртуальной функции в классе может быть следующим:

  • FuncNameVirtual() – имя виртуальной функции;
  • return_type – тип, возвращаемый функцией;
  • list_of_parameters – список параметров, которые получает функция.

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

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

  • функция с такой же сигнатурой как в базовом классе, но объявленная как константная ( const );
  • функция, которой передаются аргументы типа, совместимого с аргументами функции базового класса.

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

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

Если для функции, объявленной со спецификатором override в производном классе DerivedClass , нет подходящей виртуальной функции в базовом классе BaseClass , компилятор сгенерирует ошибку.

3. Случаи реализации полиморфизма

Вызов виртуальной функции из клиентского кода такой же, как и вызов невиртуальной функции. Основным здесь является правильная организация вызова виртуальной функции.

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

  • при объявлении указателя ( * ) на базовый класс и вызове виртуальной функции соответствующего экземпляра класса, являющегося частью иерархии. Как известно, в этом случае указатель на базовый класс может быть установлен в значение экземпляров производных классов. После этого вызывается соответствующая виртуальная функция;
  • при передаче указателя ( * ) на базовый класс в некоторую функцию, вызывающую виртуальную функцию базового класса с помощью оператора -> (доступ по указателю);
  • при передаче ссылки ( & ) на базовый класс в некую функцию, вызывающую виртуальную функцию базового класса с помощью оператора ‘ . (точка, доступ по ссылке).
4. Примеры реализации полиморфизма
4.1. Пример полиморфизма для двух классов. Вызов виртуальной функции по указателю ( -> ). Анализ кода

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

Результат выполнения программы

Проанализируем вышеприведенный код.

В примере объявляются два класса Base и Derived , образующие иерархию с помощью механизма наследования.

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

Теперь указателю p можно присваивать адрес любого экземпляра классов Base и Derived . Сначала присваивается адрес экземпляра obj1 типа Base и вызывается метод PrintInfo()

Вывод будет прогнозированным – слово «Base».

Затем указателю p присваивается адрес экземпляра obj2 типа Base и вызывается метод PrintInfo()

Вывод будет «Derived». То есть будет вызван метод PrintInfo() производного класса, что нам и нужно. Вызов этого метода обеспечивает ключевое слово virtual в объявлении функции PrintInfo() базового класса Base .

Если в классе Base перед объявление функции PrintInfo() убрать ключевое слово virtual , то в следующем коде

будет вызван метод PrintInfo() класса Base , а не класса Derived . Это означает, что полиморфизм не будет поддерживаться, а всегда будет вызываться функция базового класса. В результате программа выведет

Таким образом, функция PrintInfo() класса Derived для указателя p на базовый класс Base будет недоступна.

Вывод. Полиморфизм реализует правило «один интерфейс, много реализаций». В нашем случае интерфейс один – это объявление и вызов функции PrintInfo()

Но в зависимости от того, на какой объект указывает указатель p , будет вызван соответствующий метод PrintInfo() – это и есть много реализаций.

4.2. Пример, объясняющий полиморфизм для трех классов. Передача в функцию указателя ( * ) на базовый класс

Приводится пример 3-х классов CalcLength , CalcArea , CalcVolume , в которых в виртуальной функции Calc() возвращается соответственно длина окружности, площадь круга и объем шара.

Для демонстрации создается некоторая функция ShowResult() , в которую передается указатель на базовый класс. Функция вызывает виртуальную функцию Calc() по указателю. В теле функции ShowResult() неизвестно, экземпляр какого класса будет ей передан. Экземпляр будет сформирован во время выполнения.

4.3. Пример полиморфизма для трех классов. Вызов виртуальной функции в методе. Передача в метод ссылки ( & ) на базовый класс

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

Объявляется 3 класса с именами A , B , C . Класс A является базовым для класса B . Класс B является базовым для класса C . Классы содержат только один метод, выводящий название класса. С помощью механизма полиморфизма в функцию DemoPolymorphism() передается ссылка на один из экземпляров. В соответствии с переданным экземпляром вызывается требуемый метод.

После запуска на выполнение программа выдаст следующий результат

Если при объявлении метода Show() в классах A , B , C убрать спецификаторы virtual и override , то полиморфизм поддерживаться не будет. А это значит, что при вызове метода DemoPolymorphism() все передаваемые ссылки на экземпляры objA , objB , objC будут конвертироваться в ссылку на базовый класс A& . Как следствие, 3 раза будет вызван метод Show() класса A , и программа выдаст следующий результат

sergeysablin99 / C++ basics.md

Полиморфизм бывает статическим и динамическим:

Статический полиморфизм обрабатывается на этапе компиляции.

Из примера следует, что тип T должен обладать функциями prepare() и work() , а их реализации будут выявлены во время компиляции, основываясь на типе T .

Динамический полиморфизм обрабатывается во время исполнения.

Глядя на следующий код, мы можем сказать следующее: переданный в функцию process() указатель, должен указывать на объект, реализующий интерфейс (наследующий) base , и выбор реализаций функций prepare() и work() будет осуществлен во время выполнения программы в зависимости от того на объект какого именно производного от base типа указывает b.

CRTP (Curiously Recurring Template Pattern) — это идиома проектирования, заключающаяся в том, что класс наследует от базового шаблонного класса с самим собой в качестве параметра шаблона базового класса. Звучит запутано, но в коде выглядит довольно просто.

Что это может нам дать? Такая конструкция делает возможным обращение к производному классу из базового.

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

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

Ключевое слово virtual

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

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

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

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

Компилятор не выдает ошибки при использовании этого кода.

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

Шаблоны в C++ представляют семейство классов.

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

Реализация шаблонов должна быть определена в заголовочном файле, чтобы компилятор смог выполнить инстанцирование шаблона для любых аргументов, но можно скрыть реализацию, используя явное инстанцирование. В заголовочном файле при этом остаётся только объявление шаблонов, а реализация помещается в отдельный файл. Для всех возможных вариантов применения шаблонов в этом модуле выполняется явное инстанцирование. Плюсы: компиляция выполняется быстрее, сохраняется know-how, минус: использование шаблонов с непредусмотренными аргументами приведёт к ошибке при компоновке программы. Заголовочный файл my.h:

Реализация модуля my.cpp:

Шаблоны класса можно частично специализировать, при этом получившийся класс по-прежнему будет шаблоном. Частичная специализация позволяет частично настроить код шаблона для определенных типов, например:

  • Шаблон имеет несколько типов, и только некоторые из них требуют специализации. Результат для остальных типов параметризован шаблоном.
  • Шаблон имеет только один тип, но специализация необходима для типов указателя, ссылки, указателя на член или указателя на функцию. Специализация сама по себе по-прежнему является шаблоном с типом, на который задан указатель или ссылка.

Если у вас есть шаблон класса коллекции, который принимает любой тип T , можно создать частичную специализацию, которая принимает любой тип указателя T* . В следующем коде показан шаблон класса коллекции Bag и частичная специализация для типов указателей, в которой коллекция разыменовывает типы указателей перед их копированием в массив. Затем коллекция сохраняет значения, на которые указывают указатели. В исходном шаблоне в коллекции сохранялись бы только сами указатели, а данные оставались бы уязвимы в отношении удаления или изменения. В этой версии коллекции, специально предназначенной для указателей, добавлен код для проверки наличия пустых указателей null в методе add .

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

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

Существует шесть (начиная со стандарта C++17) типов итераторов:

  • Копирование, присваивание.
  • Операции сравнения на равенство == и != .
  • Инкремент: ++iter и iter++ .
  • Разыменование для чтения: *iter и iter->m , при этом запрещена запись: *iter = value; .

Пример входного итератора — это итератор чтения из потока: std::istream_iterator .

  • Копирование, присваивание.
  • Инкремент: ++iter и iter++ .
  • Разыменование для записи: *iter = value и *iter++ = value , при этом запрещено чтение.

Пример выходного итератора — это итератор записи в поток: std::ostream_iterator .

  • Все операции InputIterator и OutputIterator .
  • Требование многопроходности: если i1 == i2 , то ++i1 == ++i2 , т.е. итератор можно копировать, и обходить им последовательность много раз.

Прямые итераторы — это итераторы односвязных списков, например std::forward_list .

  • Все операции ForwardIterator .
  • Декремент: —iter , iter— , *iter— .

Пример: итераторы двусвязных списков ( std::list ), деревьев ( std::set ).

RandomAccessIterator (произвольного доступа)

  • Все операции BidirectionalIterator .
  • Операции сравнения: i1 < i2 , i1 > i2 , i1 <= i2 , i1 >= i2 .
  • Сложение/вычитание с числом: iter + n , iter += n , iter — n , iter -= n .
  • Разность итераторов: i2 — i1 .
  • Индексирование: iter[n] .

Пример: итераторы массивов ( std::vector ), указатели.

Итератор, логически смежные элементы которого смежны и физически:

The type It satisfies LegacyContiguousIterator if

  • The type It satisfies LegacyRandomAccessIterator
  • a , a dereferenceable iterator of type It
  • n , an integral value
  • a + n is a valid and dereferenceable iterator value
  • *(a + n) is equivalent to *(std::addressof(*a) + n)

Ключевое слово auto

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

Стоить отметить, что возвращаемое значение не может быть auto . Однако, вы можете использовать auto вместо типа возвращаемого значения функции. В таком случае, auto не говорит компилятору, что он должен определить тип, он только дает ему команду искать возвращаемый тип в конце функции. В примере ниже, возвращаемый тип функции compose — это возвращаемый тип оператора +, который суммирует значения типа T и E .

Рекомендуется использоваться ключевое слово auto всегда, если не требуется преобразование типов. Это предоставляет следующие удобства:

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

Ключевое слово decltype

Определяет тип объекта или возвращаемое значение выражения.

  • decltype(var) — decltype(var)* будет иметь в точности тот тип, с которым объявлена переменная var.
  • decltype(expr), expr — выражение. В этом случае типом decltype(expr) будет тип, которое могло бы вернуть это выражение, с той оговоркой, что decltype(expr) будет иметь тип T& (const T&), если expr возвращает lvalue, T, если expr возвращает rvalue типа Т (const T) и T&& (const T&&), если expr возвращает xvalue (rvalue reference)

Что значит “могло бы вернуть”? Это значит то, что decltype не вычисляет переданное ему в качестве аргумента выражение. Несколько поясняющих примеров:

В том случае, если мы не знаем lvalue нам вернет выражение, rvalue или xvalue, а тип использовать хочется, можно воспользоваться стандартным шаблоном std::remove_reference, чтобы “очистить” тип от ссылок.

Decltype(auto)

Это новая “фишка” языка, которая войдет в С++14. Она нужна для сохранения семантики decltype при объявлении auto переменных и будет использоваться в тех случаях, когда нас не будет устраивать то, что auto отбрасывает ссылки и cv квалификаторы и, возможно, в связке с новой возможностью С++14 — выводом типа возвращаемого функцией значения.

В последнем случае мы могли бы написать decltype(foo()), но представьте, если бы вместо foo() было выражение на 2 строчки, а такие в С++ не редкость.

Range-based for loop

Используется для выполнения for на промежутке.

for (range_declaration : range_expression) < loop_statement >, где

range_declaration — объявление переменной, тип которой с элементом последовательности range_expression или ссылкой на него.

range_expression — выражение, представляющее последовательность (в том числе массив или объект, для которого определены методы begin и end или free ) или braced-init-list.

Ключевое слово nullptr используется для явного обозначения нулевого указателя, является prvalue типа std::nullptr_t . Определено неявное преобразование в значение нулевого указателя любого типа. (There exist implicit conversions from nullptr to null pointer value of any pointer type and any pointer to member type.)

Спецификатор constexpr используется для вычисления операций на этапе компиляции. Выглядит это так:

constexpr возвращаемое_значение имя_функции (параметры) Ключевое слово constexpr , добавленное в C++11, перед функцией означает, что если значения параметров возможно посчитать на этапе компиляции, то возвращаемое значение также должно посчитаться на этапе компиляции. Если значение хотя бы одного параметра будет неизвестно на этапе компиляции, то функция будет запущена в runtime (а не будет выведена ошибка компиляции).

Ограничения

Тело constexpr -функции может содержать любые конструкции, кроме:

  • Ассемблерных вставок
  • Ключевого слова goto
  • Определения переменных нелитерального типа или static и thread_safe -переменных. Все переменные должны инициализироваться при определении.

А тело constexpr -конструктора теперь должно удовлетворять более лояльным условиям:

  • Он должен соответствовать всем условиям constexpr -функции
  • Все его нестатические члены должны иметь литеральный тип
  • Аналогичное условие про то, что все нестатические члены класса должны каким-либо способом инициализироваться
  • Появилась возможность использовать union ‘ы, но с некоторыми ограничениями

constexpr тип = expression; Ключевое слово в данном случае означает создание константы. Причем expression должно быть известно на этапе компиляции.

Рассмотрим такой пример:

constexpr -переменная является константой ( const ), но константа не является constexpr-переменной.

Ограничения

Тип constexpr-переменной должен быть литеральным типом, то есть одним из следующих:

  • Скалярный тип
  • Указатель
  • Массив скалярных типов
  • Класс, который удовлетворяет следующим условиям:
    • Имеет деструктор по умолчанию
    • Все нестатические члены класса должны быть литеральными типами
    • Класс должен иметь хотя бы один constexpr -конструктор (но не конструктор копирования и перемещения) или не иметь конструкторов вовсе

    constexpr -переменная должна удовлетворять следующим условиям:

    • Ее тип должен быть литеральным
    • Ей должно быть сразу присвоено значение или вызван constexpr -конструктор
    • Параметры конструктора или присвоенное значение могут содержать только литералы или constexpr -переменные и constexpr -функции

    Такая модификация enum решает две проблемы:

    • область видимости констант enum class — это сам enum class, т.е. снаружи вместо Enum e = EnumValue1 вам придётся писать Enum e = Enum::Value1 , что гораздо нагляднее
    • enum конвертируется в целое число без ограничений, а в enum class для этого потребуется static cast: const auto value = static_cast(Enum::Value1)

    Кроме того, для enum и scoped enum появилась возможность явно выбрать тип, используемый для представления перечисления в сгенерированном компилятором коде:

    Для проверки условия на этапе компиляции используется if constexpr.

    Услышав об этом в первый раз, возможно вы спросите, зачем нужен статический if и эти сложные шаблонные выражения… Разве нормальный if не будет работать?

    is_same даёт true для используемого типа (string), и мы можем просто вернуть t без преобразований… но что пошло не так?

    Главная причина в этом: компилятор попытался разобрать обе условные ветви и нашёл ошибку в случае else . Он не может отбросить «неправильный» код в нашем частном случае конкретизации шаблона.

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

    static_assert проверяет утверждение во время компиляции. Если утверждение — истина, то ничего не происходит. Если — ложь, то компилятор выводит указанное сообщение об ошибке.

    В следующем примере, функция add , как предполагается, работает только с целочисленными типами.

    Однако, при компиляции не возникнет ошибки, если написать следующее:

    Программа просто выведет «4.14» и «е». Используя static_assert , эти две строки вызовут ошибку во время компиляции.

    Ключевое слово default

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

    • конструктор по умолчанию (default constructor);
    • конструктор копирования (copy constructor);
    • конструктор перемещения (move constructor);
    • оператор присваивания копированием (copy assignment operator);
    • оператор присваивания перемещением (move assignment operator);
    • деструктор (destructor).

    USING vs TYPEDEF

    using создает синоним типа

    typedef создаёт синоним имени типа

    Using позволяет работать с шаблонами:

    В случае typedef приходится идти длинным окружным путем:

    Я бы сказал так — главное преимущество using перед typedef — при работе с шаблонами. шаблоны. В частности, объявления псевдонимов могут быть шаблонизированы (и в этом случае они называются шаблонами псевдонимов), в то время как typedef — нет. Это дает программистам на C++11 простой механизм для выражения того, что в C++98 можно было выразить только хакерскими способами, с помощью typedef , вложенных в шаблонные struct . Рассмотрим, например, определение синонима для связанного списка, который использует пользовательский распределитель памяти MyAlloc . В случае шаблонов псевдонимов это просто:

    В случае typedef приходится идти длинным окружным путем:

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

    Здесь MyAllocList::type ссылается на тип, который зависит от параметра типа шаблона ( T ). Тем самым MyAllocList::type является зависимым типом, а одно из правил C++ требует, чтобы имена зависимых типов предварялись ключевым словом typename . Если MyAllocList определен как шаблон псевдонима, это требование использования ключевого слова typename убирается (как и громоздкий суффикс “ ::type ”):

    Конструкторы с std::initializer_list

    В C++11 ввели возможность инициализировать объекты с помощью фигурных скобок. Это решило most vexing parse, позволило определять контейнеры из заранее известных элементов, запретило сужающее преобразование (narrowing conversion).

    Для составления конструкторов с переменным числом параметров придумали std::initializer_list, который умеет конструироваться из braced-init-list-а.

    Зачем же он нужен? Главным образом, чтобы пользовательские классы могли сказать: «я хочу конструироваться от braced-init-list-а элементов такого-то типа», и им не требовался бы для этого шаблонный конструктор.

    Рассмотрим такую запись:

    Что имелось в виду, v(5) или v(<5>) ? Другими словами, хотим ли мы сконструировать вектор из 5 элементов, или из одного элемента со значением 5 ?

    Для решения этого конфликта разрешение перегрузок (overload resolution, выбор нужной функции по переданным аргументам) в случае list-initialization происходит в два этапа:

    1. Сначала рассматриваются только конструкторы с единственным параметром типа std::initializer_list (это один из главных моментов, когда компилятор таки генерирует std::initializer_list по содержимому фигурных скобочек). Разрешение перегрузок происходит между ними.
    2. Если ни один конструктор не подходит, то дальше все как обычно — разворачиваем braced-init-list в список аргументов и проводим разрешение перегрузок среди всех доступных конструкторов.

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

    Под пункт 1 наш конструктор не подходит — <<<>>> не похож на std::initializer_list , потому что int нельзя проинициализировать с помощью <<>> . Однако <> — вполне себе zero-initialization, поэтому конструктор принимается на втором шаге.

    Almost Always Auto Как же braced-init-list-ы ведут себя в сочетании с выводом типов? Что будет, если я напишу auto x = <0>; auto y = <1, 2>; ? Можно придумать несколько разумных стратегий:

    1. Запретить такую инициализацию вообще (в самом деле, что программист хочет этим сказать?)
    2. Вывести тип первой переменной как int , а второй вариант запретить
    3. Сделать так, чтобы и x, и y имели тип std::initializer_lits

    Последний вариант нравится мне меньше всего (мало кому в реальной жизни заводить локальные переменные типа std::initializer_list ), но в стандарт С++11 попал именно он. Постепенно стало выясняться, что это вызывает проблемы у программистов (кто бы мог подумать), поэтому в стандарт добавили патч http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3922.html, который реализует поведение №2… только в случае direct-list-initialization ( auto x <5>), а в случае copy-list-initialization ( auto x = <5>) оставляет все по-старому.

    Класс std::optional управляет возможно хранимым значением, т.е. значение может и не храниться. Обычно используется как возвращаемое значение функции, которая может завершиться ошибкой.

    В каждый момент времени объект std::optional<T> одновременно хранит и не хранит значение. Хранимое значение хранится в памяти как часть объекта optional, т.е. нет места динамическому распределению памяти.

    The optional object contains a value in the following conditions:

    • The object is initialized with/assigned from a value of type T or another optional that contains a value.

    The object does not contain a value in the following conditions:

    The object is default-initialized.

    The object is initialized with/assigned from a value of type std::nullopt_t or an optional object that does not contain a value.

    The member function reset() is called.

    Не бывает ссылок на std::optional объект, однако, он может храниться в std::reference_wrapper<T>

    Helper classes

    std::hash(C++17) specializes the std::hash algorithm (class template specialization)
    nullopt_t(C++17) indicator of optional type with uninitialized state (class)
    bad_optional_access(C++17) exception indicating checked access to an optional that doesn’t contain a value (class)
    Helpers
    nullopt(C++17) an object of type nullopt_t (constant)

    Представляет собой типобезопасный union . В каждый момент времени std::variant содержит значение одного из типов альтернатив или не содержит вовсе (например, в случае ошибки).

    Расположение элемента std::variant хранится там же, где и сам объект std::variant , поэтому запрещено использовать динамически выделенную память.

    Member functions

    (constructor) constructs the variant object (public member function)
    (destructor) destroys the variant, along with its contained value (public member function)
    operator= assigns a variant (public member function)
    Observers
    index returns the zero-based index of the alternative held by the variant (public member function)
    valueless_by_exception checks if the variant is in the invalid state (public member function)
    Modifiers
    emplace constructs a value in the variant, in place (public member function)
    swap swaps with another variant (public member function)

    Non-member functions

    visit(C++17) calls the provided functor with the arguments held by one or more variants (function template)
    holds_alternative(C++17) checks if a variant currently holds a given type (function template)
    std::get(std::variant)(C++17) reads the value of the variant given the index or the type (if the type is unique), throws on error (function template)
    get_if(C++17) obtains a pointer to the value of a pointed-to variant given the index or the type (if unique), returns null on error (function template)
    operator== (C++17)
    operator!= (C++17)
    operator< (C++17)
    operator>= (C++17)
    operator<=>(C++20)
    compares variant objects as their contained values (function template)
    std::swap(std::variant)(C++17) specializes the std::swap algorithm (function)

    Helper classes

    monostate(C++17) placeholder type for use as the first alternative in a variant of non-default-constructible types (class)
    bad_variant_access(C++17) exception thrown on invalid accesses to the value of a variant (class)
    variant_sizevariant_size_v(C++17) obtains the size of the variant’s list of alternatives at compile time (class template) (variable template)
    variant_alternativevariant_alternative_t(C++17) obtains the type of the alternative specified by its index, at compile time (class template) (alias template)
    std::hash(C++17) specializes the std::hash algorithm (class template specialization)
    Helper objects
    variant_npos(C++17) index of the variant in the invalid state (constant)

    Описывает типобезопасный контейнер для объектов любого типа.

    Member functions

    (constructor) constructs an any object (public member function)
    operator= assigns an any object (public member function)
    (destructor) destroys an any object (public member function)
    Modifiers
    emplace change the contained object, constructing the new object directly (public member function)
    reset destroys contained object (public member function)
    swap swaps two any objects (public member function)
    Observers
    has_value checks if object holds a value (public member function)
    type returns the typeid of the contained value (public member function)

    Non-member functions

    std::swap(std::any)(C++17) specializes the std::swap algorithm (function)
    any_cast(C++17) type-safe access to the contained object (function template)
    make_any(C++17) creates an any object (function template)
    Helper classes
    bad_any_cast(C++17) exception thrown by the value-returning forms of any_cast on a type mismatch (class)

    Шаблон класса std::basic_string_view описывает объекты, которые могут ссылаться на постоянную ( const ) конечную последовательность char -подобных объектов, позиция первого элемента которого равна 0. Обычно для реализации неоьходимо для элемента: указатель на const CharT и размер.

    (constructor) constructs a basic_string_view (public member function)
    operator= assigns a view (public member function)
    Iterators
    begincbegin returns an iterator to the beginning (public member function)
    endcend returns an iterator to the end (public member function)
    rbegincrbegin returns a reverse iterator to the beginning (public member function)
    rendcrend returns a reverse iterator to the end (public member function)
    Element access
    [operator] accesses the specified character (public member function)
    at accesses the specified character with bounds checking (public member function)
    front accesses the first character (public member function)
    back accesses the last character (public member function)
    data returns a pointer to the first character of a view (public member function)
    Capacity
    sizelength returns the number of characters (public member function)
    max_size returns the maximum number of characters (public member function)
    empty checks whether the view is empty (public member function)
    Modifiers
    remove_prefix shrinks the view by moving its start forward (public member function)
    remove_suffix shrinks the view by moving its end backward (public member function)
    swap swaps the contents (public member function)
    Operations
    copy copies characters (public member function)
    substr returns a substring (public member function)
    compare compares two views (public member function)
    starts_with(C++20) checks if the string view starts with the given prefix (public member function)
    ends_with(C++20) checks if the string view ends with the given suffix (public member function)
    find find characters in the view (public member function)
    rfind find the last occurrence of a substring (public member function)
    find_first_of find first occurrence of characters (public member function)
    find_last_of find last occurrence of characters (public member function)
    find_first_not_of find first absence of characters (public member function)
    find_last_not_of find last absence of characters (public member function)
    Constants
    npos[static] special value. The exact meaning depends on the context (public static member constant)
    operator==operator!=operatoroperator<=operator>= lexicographically compares two string views (function template)
    Input/output
    operator<< performs stream output on string views (function template)
    Defined in inline namespace std::literals::string_view_literals
    operator»»sv(C++17) Creates a string view of a character array literal (function)

    В C++11 ввели возможность использовать (безымянные) лямбда-функции — это функции, которые определяются на месте. Например,

    Полный синтаксис таков:

    Declaration of a const lambda: the objects captured by copy are const in the lambda body.

    Omitted trailing-return-type: the return type of the closure’s operator() is deduced from return statements as if for a function whose return type is declared auto.

    Omitted parameter list: function takes no arguments, as if the parameter list was **()** . This form can only be used if none of constexpr, mutable, exception specification, attributes, or trailing return type is used.

    Функторы в C++ являются сокращением от «функциональные объекты«. Функциональный объект является экземпляром класса С++, в котором определён operator() . Если вы определите **operator()** для C++ класса, то вы получите объект, который действует как функция, но может также хранить состояние. Например,

    Рассмотрим три выражения:

    • [](double x)
    • class foo >
    • double bar(double x)

    Они имеют общую сигнатуру вызова double (double) , однако представляют собой разные сущности.

    Для описания сигнатуры вызова был введён std::function .

    Синтаксис: std::function <return-type (params)> object-name

    std::mem_fn

    Представляет собой обёртку на функцию-член класса.

    std::reference_wrapper

    Представляет собой копируемую и присваемую обёртку для ссылки.

    Member functions

    (constructor) stores a reference in a new std::reference_wrapper object (public member function)
    operator= rebinds a std::reference_wrapper (public member function)
    getoperator T& accesses the stored reference (public member function)
    operator() calls the stored function (public member function)

    std::bind

    Представляет собой обёртку для функции и списка параметром для будущего использования.

    _1, _2, .

    Представляют собой плейсхолдеры для std::bind , т.е. эти аргументы будут дополнены позднее.

    Rvalue ссылка ведет себя точно так же, как и lvalue ссылка, за исключением того, что она может быть связана с временным объектом, тогда как lvalue связать с временным (не константным) объектом нельзя.

    std::move

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

    std::forward

    Применяется для идеальной передачи аргументов в функцию.

    Теперь давайте вернемся к нашей описанной выше шаблонной функции wrapper. Вот как она должна быть реализована с использованием С++11:

    А вот как реализован forward [3]:

    Рассмотрим следующий вызов:

    Рассмотрим первый аргумент (второй аналогичен): ii является lvalue, таким образом T1 становится int& в соответствии с правилом особого вывода типа. Получается вызов func(forward<int&>(e1), …). Таким образом, шаблон forward инстанцирован типом int& и получаем следующую версию этой функции:

    Время применить правило сжатия ссылок:

    Другими словами, аргумент передан по ссылке в func, как и требуется для lvalue. Следующий пример:

    Здесь аргументы являются rvalue, таким образом T1 становится int. Получаем вызов func(forward(e1), . ). Таким образом, шаблонная функция forward инстанцирована типом int и получаем следующую версию функции:

    Аргумент, полученный по ссылке, приводится к rvalue-ссылке, которую и требуется получить от forward.

    Make-функции

    Конструируют объект определенного типа. Например, std::make_pair принимает два объекта и создает объект типа std::pair<T> , тип T определяется автоматически.

    count ( InputIt first, InputIt last, const T &value ) — подсчитывает число элементов, равных value.

    count ( ExecutionPolicy&& policy, ForwardIt first, ForwardIt last, const T &value ) — аналогично count, но выполнение согласно policy.

    count_if ( InputIt first, InputIt last, UnaryPredicate p ) — подсчитывает число элементов, для которых унарный предикат p возвращает true.

    count_if ( ExecutionPolicy&& policy, ForwardIt first, ForwardIt last, UnaryPredicate p ) — аналогично count, но выполнение согласно policy.

    Сложность: last — first операций сравнения/выполнения предикат.

    Возможные execution policy:

    • std::execution::seq — запрещает параллельное исполнение. Элементы вызываются упорядоченно.
    • std::execution::par — разрешает параллельное исполнение. Элементы из одинаковых потоков вызываются упорядоченно.
    • std::execution::par_unseq — разрешает параллельное исполнение. Элементы из одинаковых потоков вызываются неупорядоченно внутри потока и между потоками.
    • std::execution::unseq — запрещает параллельное исполнение. Элементы вызываются неупорядоченно внутри потока и между потоками.

    find ( InputIt first, InputIt last, cons T& value ) — ищет элемент, равный value.

    find_if ( InputIt first, InputIt last, UnaryPredicate p ) — ищет элемент, для которого унарный предикат возвращает true.

    find_if_not ( InputIt first, InputIt last, UnaryPredicate p ) — ищет элемент, для которого унарный предикат возвращает false.

    Так же существуют аналогичные count перегрузки с использованием execution policy.

    Сложность: last — first операций предиката.

    transform (унарный) ( InputIt first1, InputIt last1, OutputIt d_first, UnaryOperation unary_op ) — применяет унарный оператор unary_op для элементов промежутка [first1;last1).

    transform (бинарный) ( InputIt1 first1, InputIt1 last1, InputIt2 first2, OutputIt d_first, BinaryOperation binary_op ) — применяет бинарный оператор для пар из двух промежутков: один [first1, last1), второй начинается с first2.

    Так же существуют аналогичные count перегрузки с использованием execution policy.

    Сложность: std::distance(first1, last1).

    sort ( RandomIt first, RandomIt last ) — сортирует элементы при помощи operator <

    sort ( RandomIt first, RandomIt last, Compare comp ) — сортирует элементы с определенной бинарной функцией сравнение comp.

    Так же существуют аналогичные count перегрузки с использованием execution policy.

    Сложность: O(n * log(n) ).

    any_of ( InputIt first, InputIt last, UnaryPredicate p ) — Проверяет, возвращает ли предикат true хотя бы для одного элемента.

    all_of ( InputIt first, InputIt last, UnaryPredicate p ) — Проверяет, возвращает ли предикат true для каждого элемента.

    none_of ( InputIt first, InputIt last, UnaryPredicate p ) — Проверяет, возвращает ли предикат true ни для каждого элемента.

    Так же существуют аналогичные count перегрузки с использованием execution policy.

    Сложность: last — first операций предиката.

    remove ( ForwardIt first, ForwardIt last, const T& value ) — удаляет элемент, равный (operator ==) value.

    remove_if ( ForwardIt first, ForwardIt last, UnaryPredicate p ) — удаляет элемент, унарный предикат которого возвращает true.

    Так же существуют аналогичные count перегрузки с использованием execution policy.

    Сложность: last — first операций предиката.

    replace ( ForwardIt first, ForwardIt last, const T& old_value, const T& new_value ) — заменяет все элементы, равные old_value.

    replace_if ( ForwardIt first, ForwardIt last, UnaryPredicate p, const T& new_value )- заменяет все элементы, унарный предикат которых возвращает true.

    Так же существуют аналогичные count перегрузки с использованием execution policy.

    Сложность: last — first операций предиката.

    merge ( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, OutputIt d_first ) — смешивает два отсортированных промежутка в один [d_first, d_last): d_last = d_first + (last1 — first1) + (last2 — first2), для сравнения использует operator <

    merge merge( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, OutputIt d_first, Compare comp ) — аналогично, для сравнения использует функцию comp

    Так же существуют аналогичные count перегрузки с использованием execution policy.

    Сложность: 1,3) At most std::distance(first1, last1) + std::distance(first2, last2) — 1 comparisons.

    T& **max ** ( const T& a, const T& b ) — возвращает большее из значений. Использует operator <.

    T& max ( const T& a, const T& b, Compare comp ) — возвращает большее из значений. Использует comp.

    T **max **( std::initializer_list ilist, Compare comp ) — возвращает большее из значений из ilist.

    T max ( std::initializer_list ilist, Compare comp )- возвращает большее из значений из ilist. Использует comp.

    Сложность: 1-2) Exactly one comparison

    3-4) Exactly ilist.size() — 1 comparisons

    template< class ForwardIt >
    ForwardIt max_element( ForwardIt first, ForwardIt last );
    (until C++17)
    template< class ForwardIt > constexpr
    ForwardIt max_element( ForwardIt first, ForwardIt last );
    (since C++17)
    template< class ExecutionPolicy, class ForwardIt >
    ForwardIt max_element( ExecutionPolicy&& policy, ForwardIt first, ForwardIt last );
    (2) (since C++17)
    template< class ForwardIt, class Compare >
    ForwardIt max_element( ForwardIt first, ForwardIt last, Compare comp );
    (until C++17)
    template< class ForwardIt, class Compare >
    constexpr ForwardIt max_element( ForwardIt first, ForwardIt last, Compare comp );
    (since C++17)
    template< class ExecutionPolicy, class ForwardIt, class Compare >
    ForwardIt max_element( ExecutionPolicy&& policy, ForwardIt first, ForwardIt last, Compare comp );
    (4) (since C++17)

    Finds the greatest element in the range [first, last) .

    Elements are compared using operator< .

    Elements are compared using the given binary comparison function comp .

    2,4) Same as (1,3), but executed according to policy . These overloads do not participate in overload resolution unless std::is_execution_policy_v<std::decay_t> is true

    template< class ForwardIt >
    bool is_sorted( ForwardIt first, ForwardIt last );
    (since C++11) (until C++20)
    template< class ForwardIt >
    constexpr bool is_sorted( ForwardIt first, ForwardIt last );
    (since C++20)
    template< class ExecutionPolicy, class ForwardIt >
    bool is_sorted( ExecutionPolicy&& policy, ForwardIt first, ForwardIt last );
    (2) (since C++17)
    template< class ForwardIt, class Compare > bool is_sorted( ForwardIt first, ForwardIt last, Compare comp ); (since C++11) (until C++20)
    template< class ForwardIt, class Compare >
    constexpr bool is_sorted( ForwardIt first, ForwardIt last, Compare comp );
    (since C++20)
    template< class ExecutionPolicy, class ForwardIt, class Compare >
    bool is_sorted( ExecutionPolicy&& policy, ForwardIt first, ForwardIt last, Compare comp );
    (4) (since C++17)

    Checks if the elements in range [first, last) are sorted in non-descending order.

    A sequence is sorted with respect to a comparator comp if for any iterator it pointing to the sequence and any non-negative integer n such that it + n is a valid iterator pointing to an element of the sequence, comp(*(it + n), *it) evaluates to false .

    Elements are compared using operator< .

    Elements are compared using the given binary comparison function comp .

    2,4) Same as (1,3), but executed according to policy . These overloads do not participate in overload resolution unless std::is_execution_policy_v<std::decay_t> is true.

    Сложность: линейная на дистанции между first и last

    template< class ForwardIt, class T >
    ForwardIt lower_bound( ForwardIt first, ForwardIt last, const T& value );
    (until C++20)
    template< class ForwardIt, class T >
    constexpr ForwardIt lower_bound( ForwardIt first, ForwardIt last, const T& value );
    (since C++20)
    template< class ForwardIt, class T, class Compare >
    ForwardIt lower_bound( ForwardIt first, ForwardIt last, const T& value, Compare comp );
    (until C++20)
    template< class ForwardIt, class T, class Compare >
    constexpr ForwardIt lower_bound( ForwardIt first, ForwardIt last, const T& value, Compare comp );
    (since C++20)

    Returns an iterator pointing to the first element in the range [first, last) that is not less than (i.e. greater or equal to) value , or last if no such element is found.

    The range [first, last) must be partitioned with respect to the expression element < value or comp(element, value), i.e., all elements for which the expression is true must precede all elements for which the expression is false. A fully-sorted range meets this criterion.

    The first version uses operator< to compare the elements, the second version uses the given comparison function comp .

    Complexity

    The number of comparisons performed is logarithmic in the distance between first and last (At most log 2(last — first) + O(1) comparisons). However, for non-LegacyRandomAccessIterators, the number of iterator increments is linear.

    template< class InputIt, class T > T accumulate( InputIt first, InputIt last, T init ); (until C++20)
    template< class InputIt, class T > constexpr T accumulate( InputIt first, InputIt last, T init ); (since C++20)
    template< class InputIt, class T, class BinaryOperation > T accumulate( InputIt first, InputIt last, T init, BinaryOperation op ); (until C++20)
    template< class InputIt, class T, class BinaryOperation > constexpr T accumulate( InputIt first, InputIt last, T init, BinaryOperation op ); (since C++20)

    Computes the sum of the given value init and the elements in the range [first, last) . The first version uses operator+ to sum up the elements, the second version uses the given binary function op , both applying std::move to their operands on the left hand side (since C++20).

    op must not have side effects. (until C++11)
    op must not invalidate any iterators, including the end iterators, nor modify any elements of the range involved, and also *last. (since C++11)

    Шаблон проектирования «Фабрика»

    Фабрика — это объект для создания других объектов.

    Добавление классов в фабрику:

    Создание объекта с помощью фабрики:

    Пример реализации от Скотта Майерса:

    Идиома PImpl означает pointer-to-implementation — указатель на определение. Это позволяет писать подобный код:

    Преимущества использования Pimpl существенны. Инкапсулируя все детали реализации, получаем тонкий и долгосрочный стабильный интерфейс (заголовочный файл). Под первым подразумевается легко читаемое описание класса; под вторым — поддержка бинарной совместимости даже после значительных изменений в реализации. Berkley sockets Сокеты Беркли — интерфейс программирования приложений (API), представляющий собой библиотеку для разработки приложений на языке C с поддержкой межпроцессного взаимодействия (IPC), часто применяемый в компьютерных сетях.

    Библиотека сокетов располагается в заголовочном файле <sys/socket.h>.

    <netinetin.h> — Семейства адресов/протоколов PF_INET и PF_INET6. Широко используются в сети Интернет, включают в себя IP-адреса, а также номера портов TCP и UDP.

    <sys/un.h> — Семейство адресов PF_UNIX/PF_LOCAL. Используется для локального взаимодействия между программами, запущенными на одном компьютере. В компьютерных сетях не применяется.

    <arpa/inet.h> — Функции для работы с числовыми IP-адресами.

    <netdb.h> — Функции для преобразования протокольных имен и имен хостов в числовые адреса. Используются локальные данные аналогично DNS.

    Структуры sockaddr — обобщённая структура адреса, к которой, в зависимости от используемого семейства протоколов, приводится соответствующая структура, например:

    struct sockaddr_in stSockAddr; . bind(SocketFD,(const struct sockaddr *)&stSockAddr, sizeof(struct sockaddr_in)); sockaddr_in

    Функции #include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol); socket() создаёт конечную точку соединения и возвращает дескриптор. socket() принимает три аргумента:

    domain, указывающий семейство протоколов создаваемого сокета. Этот параметр задает правила использования именования и формат адреса. Например:

    PF_INET для сетевого протокола IPv4 или

    PF_INET6 для IPv6.

    PF_UNIX для локальных сокетов (используя файл).

    type (тип) один из:

    SOCK_STREAM надёжная потокоориентированная служба (TCP) (сервис) или потоковый сокет

    SOCK_DGRAM служба датаграмм (UDP) или датаграммный сокет

    SOCK_SEQPACKET надёжная служба последовательных пакетов

    SOCK_RAW Сырой сокет — сырой протокол поверх сетевого уровня.

    protocol определяет используемый транспортный протокол. Самые распространённые — это IPPROTO_TCP, IPPROTO_SCTP, IPPROTO_UDP, IPPROTO_DCCP. Эти протоколы указаны в <netinet/in.h>. Значение «0» может быть использовано для выбора протокола по умолчанию из указанного семейства (domain) и типа (type).

    Функция возвращает −1 в случае ошибки. Иначе, она возвращает целое число, представляющее присвоенный дескриптор.

    struct hostent *gethostbyname(const char *name); struct hostent *gethostbyaddr(const void *addr, int len, int type); Функции gethostbyname() и gethostbyaddr() возвращают указатель на объект типа struct hostent, описывающий интернет-узел по имени или по адресу, соответственно. Эта структура содержит или информацию, полученную от сервера имен или произвольные поля из строки в /etc/hosts. Если локальный сервер имен не запущен, то эти подпрограммы просматривают /etc/hosts. Функции принимают следующие аргументы:

    name, определяющий имя хоста. Например: www.wikipedia.org

    addr, определяющий указатель на struct in_addr, содержащую адрес хоста.

    len, определяющий длину в байтах addr.

    type, определяющий тип области адресов хоста. Например: PF_INET

    Функции возвращают NULL-указатель в случае ошибки.

    #include <sys/types.h> #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen); connect() Устанавливает соединение с сервером. Возвращает целое число, представляющее код ошибки: 0 означает успешное выполнение, а −1 свидетельствует об ошибке.

    #include <sys/types.h> #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen); bind() связывает сокет с конкретным адресом. Когда сокет создается при помощи socket(), он ассоциируется с некоторым семейством адресов, но не с конкретным адресом. До того как сокет сможет принять входящие соединения, он должен быть связан с адресом. bind() принимает три аргумента:

    sockfd — дескриптор, представляющий сокет при привязке

    serv_addr — указатель на структуру sockaddr, представляющую адрес, к которому привязываем.

    addrlen — поле socklen_t, представляющее длину структуры sockaddr.

    Возвращает 0 при успехе и −1 при возникновении ошибки

    #include <sys/socket.h> int listen(int sockfd, int backlog); listen() подготавливает привязываемый сокет к принятию входящих соединений (так называемое «прослушивание»). Данная функция применима только к типам сокетов SOCK_STREAM и SOCK_SEQPACKET. Принимает два аргумента:

    sockfd — корректный дескриптор сокета.

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

    После принятия соединения оно выводится из очереди. В случае успеха возвращается 0, в случае возникновения ошибки возвращается −1.

    #include <sys/types.h> #include <sys/socket.h> int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); accept() используется для принятия запроса на установление соединения от удаленного хоста. Принимает следующие аргументы:

    sockfd — дескриптор слушающего сокета на принятие соединения.

    cliaddr — указатель на структуру sockaddr, для принятия информации об адресе клиента.

    addrlen — указатель на socklen_t, определяющее размер структуры, содержащей клиентский адрес и переданной в accept(). Когда accept() возвращает некоторое значение, socklen_t указывает сколько байт структуры cliaddr использовано в данный момент.

    Функция возвращает дескриптор сокета, связанный с принятым соединением, или −1 в случае возникновения ошибки.

    sendmsg ( struct proc*p, struct sendmsg_args *uap, int retval); struct sendmsg_args <
    int s; caddr_t msg; int flags; >; Параметры системного вызова send следующие:

    s – дескриптор сокета.

    msg – указатель на структуру msghdr.

    flags – управляющая информация.

    recvmsg(struct proc *p, struct recvmsg_args *uap , int *retval); struct recvmsg_args < int s, struct msghdr *msg, int flags, >; Параметры системного вызова receive следующие:

    s – дескриптор сокета.

    msg – указатель на структуру msghdr.

    flags – управляющая информация.

    Boost::Asio Представляет собой кросс-платформенная С++ библиотека для программирования сетей и некоторых других низкоуровневых программ ввода/вывода.

    Boost.Asio успешно абстрагирует понятия input и output, которые работают не только для работы в сети, но и для последовательных COM-портов, файлов и так далее. Кроме этого вы можете делать input или output программирование синхронным или асинхронным:

    read(stream, buffer [, extra options]) async_read(stream, buffer [, extra options], handler) write(stream, buffer [, extra options]) async_write(stream, buffer [, extra options], handler) Socket Boost::asio:ip::tcp::socket представляет собой асинхронный и потокоориентированный блокирующий сокет.

    basic_stream_socket Construct a basic_stream_socket without opening it.
    Construct and open a basic_stream_socket.
    Construct a basic_stream_socket, opening it and binding it to the given local endpoint.
    Construct a basic_stream_socket on an existing native socket.
    Move-construct a basic_stream_socket from another. bind Bind the socket to the given local endpoint. async_connect Start an asynchronous connect. async_read_some Start an asynchronous read. async_send Start an asynchronous send. connect Connect the socket to the specified endpoint. read_some Read some data from the socket. send Send some data on the socket. write_some Write some data to the socket. Все операции поддерживают отлаживание ошибок с записью в boost::system::error_code.

    Endpoint Представляет собой точку доступа, которую можно ассоциировать с

    basic_endpoint Default constructor.
    Construct an endpoint using a port number, specified in the host’s byte order. The IP address will be the any address (i.e. INADDR_ANY or in6addr_any). This constructor would typically be used for accepting new connections.
    Construct an endpoint using a port number and an IP address. This constructor may be used for accepting connections on a specific interface or for making a connection to a remote endpoint.
    Copy constructor. address Get the IP address associated with the endpoint.Set the IP address associated with the endpoint. port Get the port associated with the endpoint. The port number is always in the host’s byte order.Set the port associated with the endpoint. The port number is always in the host’s byte order. protocol The protocol associated with the endpoint. Acceptor Класс для приёма новых соединений.

    basic_socket_acceptor Construct an acceptor without opening it.
    Construct an open acceptor.
    Construct an acceptor opened on the given endpoint.
    Construct a basic_socket_acceptor on an existing native acceptor.
    Move-construct a basic_socket_acceptor from another. bind Bind the acceptor to the given local endpoint. accept Accept a new connection.Accept a new connection and obtain the endpoint of the peer. async_accept Start an asynchronous accept. listen Place the acceptor into the state where it will listen for new connections. io_service The io_service class provides the core I/O functionality for users of the asynchronous I/O objects, including:

    Управление потоками Состояние гонки (англ. race condition), также конкуренция[1] — ошибка проектирования многопоточной системы или приложения, при которой работа системы или приложения зависит от того, в каком порядке выполняются части кода.

    Рассмотрим пример кода (на Java).

    volatile int x; // Поток 1: while (!stop) < x++; … >// Поток 2: while (!stop) < if (x%2 == 0) System.out.println("x auto">Оператор if в потоке 2 проверяет x на чётность.

    Оператор «x++» в потоке 1 увеличивает x на единицу.

    Оператор вывода в потоке 2 выводит «x=1», хотя, казалось бы, переменная проверена на чётность

    std::future Представляет шаблон касса для получения доступа к результату асинхронных операций:

    State valid — checks if the future has a shared state (public member function)

    wait — waits for the result to become available (public member function)

    wait_for — waits for the result, returns if it is not available for the specified timeout duration (public member function)

    wait_until — waits for the result, returns if it is not available until specified time point has been reached (public member function)

    std::async async( std::launch policy, Function&& f, Args&&. args );

    The template function async runs the function f asynchronously (potentially in a separate thread which may be part of a thread pool) and returns a std::future that will eventually hold the result of that function call.

    std::unique_lock The class unique_lock is a general-purpose mutex ownership wrapper allowing deferred locking, time-constrained attempts at locking, recursive locking, transfer of lock ownership, and use with condition variables.

    std::lock Функция блокирования используется для избежание deadlock’ов.

    void lock( Lockable1& lock1, Lockable2& lock2, LockableN&. lockn ); (since C++11) Locks the given Lockable objects lock1, lock2, . lockn using a deadlock avoidance algorithm to avoid deadlock.

    The objects are locked by an unspecified series of calls to lock, try_lock, and unlock. If a call to lock or unlock results in an exception, unlock is called for any locked objects before rethrowing.

    Std::conditional_variable The condition_variable class is a synchronization primitive that can be used to block a thread, or multiple threads at the same time, until another thread both modifies a shared variable (the condition), and notifies the condition_variable.

    The thread that intends to modify the variable has to

    acquire a std::mutex (typically via std::lock_guard)

    perform the modification while the lock is held

    execute notify_one or notify_all on the std::condition_variable (the lock does not need to be held for notification)

    Even if the shared variable is atomic, it must be modified under the mutex in order to correctly publish the modification to the waiting thread.

    Any thread that intends to wait on std::condition_variable has to

    acquire a std::unique_lockstd::mutex, on the same mutex as used to protect the shared variable

    execute wait, wait_for, or wait_until. The wait operations atomically release the mutex and suspend the execution of the thread.

    When the condition variable is notified, a timeout expires, or a spurious wakeup occurs, the thread is awakened, and the mutex is atomically reacquired. The thread should then check the condition and resume waiting if the wake up was spurious.

    std::condition_variable works only with std::unique_lockstd::mutex; this restriction allows for maximal efficiency on some platforms. std::condition_variable_any provides a condition variable that works with any BasicLockable object, such as std::shared_lock.

    Condition variables permit concurrent invocation of the wait, wait_for, wait_until, notify_one and notify_all member functions.

    The class std::condition_variable is a StandardLayoutType. It is not CopyConstructible, MoveConstructible, CopyAssignable, or MoveAssignable.

    #include #include #include #include #include <condition_variable>

    std::mutex m; std::condition_variable cv; std::string data; bool ready = false; bool processed = false;

    Everything About Dynamic Polymorphism in C++

    A detailed overview of implementing dynamic polymorphism efficiently

    Polymorphism is an important concept of Object oriented programming paradigm. It comprise of two words — poly which means multiple and morph which means states. It can be classified in two ways on the basis of time at which the procedure call is resolved.

    • Static Polymorphism
    • Dynamic Polymorphism

    Static Polymorphism implies that the invocation (call) to a function is resolved at compile time. It can be implemented using Overloading. Further details of static polymorphism are out of the scope of this article but you can read more about it on javatpoint blog about c++ overloading.

    Dynamic Polymorphism implies the runtime resolution of function call. It is implies via Overriding which in turn is followed by inheritance in c++.

    Here are the examples showing the implementation of static as well as dynamic polymorphism.

    Dynamic polymorphism is not so simple as it appears in the syntax. Compiler has to resolve the overloaded function call at runtime. Our aim is to perform this resolution of correct function call efficiently.

    Incase of a simple non casted pointer for an object , we can use this approach and it is also known as early binding as the call is bound to the object pointer at compile time.

    But incase of casting , early binding would not help. During upcasting, if we need to invoke the polymorphed function then we have to implement the base function as a virtual function. Virtual functions ensure that the correct function is called for an object, regardless of the type of reference (or pointer) used for function call. This is also called late binding.

    This is a general way of implementing dynamic polymorphism in C++. Compiler will resolve the call to polymorphed function using virtual table.

    ADVANTAGE

    Virtual table is useful in resolving the call to the child class method. Every instance of a class holds a pointer to virtual table VPTR which in turn maintains different versions of polymorphed function. It provides a mechanism to resolve the call to the right version of function in a inheritance tree

    DISADVANTAGE

    Despite of this advantage, there are some drawbacks of this approach. Every time a virtual function is invoked, compiler makes a hit to the Virtual table VTABLE which makes the call to virtual functions a little expensive.

    Hence, another approach can be used to implement dynamic polymorphism. Templates can be used to implement a generics in a base class. This template can hold a base version of function which can cast the this pointer to the desired object and can call the function related to that object type.

    This approach comes under the label of TMP ie. Template Meta Programming

    ADVANTAGE

    It mitigate the cost of using a virtual table and also eliminate the use of pointer to the virtual table ( VPTR ). This approach will make the call to the polymorph function just like a normal function as the object resolution mechanism is provided explicitly by the code.

    It also allows the developer to avoid the error of forgetting to define base function as virtual.

    DISADVANTAGE

    There is nothing like free lunch in this world. Just like the previous approach, this approach also comes with a cost. Templates generate a new copy of base class for each initialisation on compile time. Therefore, it increase the size of object file and hence the binaries. Using a lot of TMP, will unnecessarily bulk up your compiled code.

    CONCLUSION

    Dynamic polymorphism can be introduced in code via overriding. Overriding in turn, can be implemented in two ways :

    • Virtual functions/methods
    • Template meta programming

    Both of the approaches have their pros and cons. Hence, a developer has to decide how to design the code in order to harness the power of dynamic polymorphism.

    “ Polymorphism is such a powerful concept that if it is implemented in an efficient and clean manner to a feasible limit (not overused) then it can enhance the software structure significantly. “

    Раннее и позднее связывание. Динамический полиморфизм

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

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

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

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

    Для каждого полиморфного типа данных компилятор создает таблицу виртуальных функций и встраивает в каждый объект такого класса скрытый указатель на эту таблицу. Она содержит адреса виртуальных функций соответствующего объекта. Имя указателя на таблицу виртуальных функций и название таблицы зависят от реализации в конкретном компиляторе. Например, в Visual C++ 6.0 этот указатель имеет имя vfptr , а таблица называется vftable (от английского Virtual Function Table). Компилятор автоматически встраивает в начало конструктора полиморфного класса фрагмент кода, который инициализирует указатель на таблицу виртуальных функций. Если вызывается виртуальная функция, код, сгенерированный компилятором, находит указатель на таблицу виртуальных функций, затем просматривает эту таблицу и извлекает из нее адрес соответствующей функции. После этого производится переход на указанный адрес и вызов функции.

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

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

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

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *