<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>@mrnimda</title><author><name>@mrnimda</name></author><id>https://teletype.in/atom/mrnimda</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/mrnimda?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@mrnimda?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=mrnimda"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/mrnimda?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-04-07T07:26:17.343Z</updated><entry><id>mrnimda:CW4E5JWAO</id><link rel="alternate" type="text/html" href="https://teletype.in/@mrnimda/CW4E5JWAO?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=mrnimda"></link><title>Из гуманитария в программисты программисты после 25.</title><published>2020-05-16T11:34:05.044Z</published><updated>2020-05-16T11:34:05.044Z</updated><summary type="html">Однажды утром, когда мне было 25, я проснулся с сильным желанием самореализации. Спустя несколько месяцев раздумий было принято решение кардинально изменить свою жизнь. Потом ещё несколько месяцев заняло осознание того, что же во мне вызывает неподдельный интерес. За всё это время, в моей голове рождались только айтишные проекты и идеи. Для реализации любой из них нужно много денег или быть специалистом в своём деле. За неимением первого, я решил для начала научиться программировать. Так начался мой путь из гуманитария в программисты. Первые полгода всё свободное время я уделял книгам, курсам на Stepik и огромному количеству роликов на Ютуб. Это был разбросанный в знаниях и очень хаотичный период времени. За те полгода я успел пройти...</summary><content type="html">
  &lt;p&gt;Однажды утром, когда мне было 25, я проснулся с сильным желанием самореализации. Спустя несколько месяцев раздумий было принято решение кардинально изменить свою жизнь. Потом ещё несколько месяцев заняло осознание того, что же во мне вызывает неподдельный интерес. За всё это время, в моей голове рождались только айтишные проекты и идеи. Для реализации любой из них нужно много денег или быть специалистом в своём деле. За неимением первого, я  решил для начала научиться программировать.&lt;br /&gt;&lt;br /&gt;Так начался мой путь из гуманитария в программисты. Первые полгода всё свободное время я уделял книгам, курсам на Stepik и огромному количеству роликов на Ютуб. Это был разбросанный в знаниях и очень хаотичный период времени. За те полгода я успел пройти 2 курса по Python, освоить основы HTML и CSS. &lt;/p&gt;
  &lt;p&gt;Далее пришло время практики. Месяц проб и ошибок, не дал мне никаких весомых результатов, лишь только осознание нескольких вещей:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Самостоятельное изучение, мне очень плохо даётся. &lt;/li&gt;
    &lt;li&gt;Получение желаемых результатов без сторонней помощи, может затянуться на долгие годы.&lt;/li&gt;
    &lt;li&gt;Программирование меня реально прёт, потому помимо реализации своих идей, я очень хочу работать программистом.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;Было принято решение начать искать работу программистом. Живу я в маленьком городе, на тот момент на весь город была 1 вакансия на программиста. Написал письмо на корпоративный ящик работодателя, в котором рассказал, какие знания смог получить за время самостоятельного обучения, что у меня нет опыта работы, но зато есть огромное желание и стремление грызть гранит науки. Так по воле случая меня пригласили на собеседование, где я сразу согласился на бесплатную стажировку на вакансию С++ разработчик. Так я сделал свой окончательный выбор и посвятил всего себя изучению программирования. &lt;br /&gt;&lt;br /&gt;Последующие две недели после собеседования, я целыми днями изучал теорию по С++ и пытался понять данный язык. За это время мне подготовили рабочее место и я вышел на стажировку. Считаю, что мне очень повезло в этом плане, т.к не каждый работодатель будет так запариваться ради стажёра, за что я очень благодарен.)&lt;/p&gt;
  &lt;p&gt;Тестовым заданием была задача написать парсер логов, с возможностью ведения базы знаний в бд. В итоге весь процесс растянулся на 4 месяца. За это время были:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Бесконечные вопросы будущим коллегам. Спасибо им, что помогали и научили пользоваться гуглом 🤣.&lt;/li&gt;
    &lt;li&gt;Бессонные ночи изучения документаций и  Stack Overflow.&lt;/li&gt;
    &lt;li&gt;Выходные с репетитором, который тоже отвечал на безумное количество моих вопросов. Ведь на работе, уже было стыдно спрашивать 🤣🤣🤣. &lt;br /&gt;Спасибо ему то же за терпение и то, что объяснял одно и то же по тысяче раз).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;После написания утилиты меня взяли на работу с испытательным сроком, а спустя еще 3 месяца приняли в основной штат.&lt;/p&gt;
  &lt;p&gt;Если подытожить тот период времени, для себя я усвоил:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;По книжкам  можно научиться основам программирования и найти стажировку. Она крайне необходима, для получения опыта.&lt;/li&gt;
    &lt;li&gt;Для получения структурированных знаний в начале пути, лучше найти ментора или репетитора. &lt;/li&gt;
    &lt;li&gt;Онлайн курсы дают результат, когда уже есть  багаж знаний и нужно только восполнять пробелы или поднимать свой скил.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;На данный момент:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;В этом году решил поступить в универ на заочку, для получения профильного образования.&lt;/li&gt;
    &lt;li&gt;Получил перевод в другой филиал.&lt;/li&gt;
    &lt;li&gt;Изучаю в в свободное время cv и нейронки.&lt;/li&gt;
    &lt;li&gt;Пробую писать свои первые пет проекты.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;Надеюсь Вам понравилась моя история, и в будущем буду Вас также радовать интересными рассказами из жизни)&lt;/p&gt;

</content></entry><entry><id>mrnimda:B1QqWjrlI</id><link rel="alternate" type="text/html" href="https://teletype.in/@mrnimda/B1QqWjrlI?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=mrnimda"></link><title>BOOST :: MPL в естественной среде обитания.</title><published>2020-01-10T07:02:34.835Z</published><updated>2020-01-17T06:35:19.580Z</updated><summary type="html">Метапрограммирование обычно определяется как создание программ, которые генерируют другие программы. Сейчас для нас метапрограммирование это удобный механизм шаблонов, который предоставляет широкие возможности для вычислений во время компиляции.</summary><content type="html">
  &lt;h3&gt;&lt;strong&gt;Немного теории:&lt;/strong&gt;&lt;/h3&gt;
  &lt;p&gt;&lt;strong&gt;Метапрограммирование&lt;/strong&gt; обычно определяется как создание программ, которые генерируют другие программы. Сейчас для нас метапрограммирование это удобный механизм шаблонов, который предоставляет широкие возможности для вычислений во время компиляции.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;boost::mpl&lt;/strong&gt; - это библиотека метапрограммирования,которая удовлетворяет потребность в наиболее востребованных и часто используемых шаблонах кодирования. Так же, как и STL дала нам концепцию итераторов, библиотека ускоренного метапрограммирования предоставляет итераторы типов и протокол классов мета-функций.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Метафункция &lt;/strong&gt;- центральная абстракция MPL. Это шаблонный класс, все параметры являются типами. В открытом доступе находится вложенный элемент «::type/::value», в отличии от свойств может выполнять действия.&lt;/p&gt;
  &lt;pre&gt;Имя_метафункции&amp;lt;аргументы&amp;gt;::type/::value;&lt;/pre&gt;
  &lt;p&gt;&lt;strong&gt;Главное правило метафункций:&lt;/strong&gt; все, что можно разделить и сделать более независимым должно быть сделано таковым. Такие решения способствуют более эффективному конструированию, обеспечивающее большую гибкость и свободу действий, чем одно монолитное решение, которое не поддается изменениям. &lt;/p&gt;
  &lt;hr /&gt;
  &lt;h3&gt;&lt;strong&gt;Стандартные примеры:&lt;/strong&gt;&lt;/h3&gt;
  &lt;p&gt;Одна из самых сильных сторон MPL, это концептуальное и синтаксическое сходство с STL. Алгоритмы и идиомы, которые программисты уже знают из STL, могут быть снова применены во время компиляции.&lt;/p&gt;
  &lt;p&gt;Большинство алгоритмов в &lt;strong&gt;mpl&lt;/strong&gt; работают с последовательностями.&lt;br /&gt;Например, поиск типа в списке выглядит следующим образом:&lt;/p&gt;
  &lt;pre&gt;typedef mpl :: list &amp;lt;char, short, int, long, float, double&amp;gt; types;
typedef mpl :: find &amp;lt;types, long&amp;gt; :: type iter;&lt;/pre&gt;
  &lt;p&gt;Здесь &lt;strong&gt;find&lt;/strong&gt; принимает два параметра - последовательность для поиска ( types ) и  значение ( long ) - и возвращает итератор iter, указывающий на первый элемент последовательности, так что iter :: type идентичен long .Если такого элемента не существует, iter идентичен end &amp;lt;types&amp;gt; :: type . По сути, именно так можно искать значение в std :: list или std :: vector , за исключением того, что mpl :: find принимает последовательность как один параметр, а std :: find занимает два итератора. &lt;/p&gt;
  &lt;hr /&gt;
  &lt;p&gt;Есть возможность вызывать класс метафункций или лямбда-выражение с аргументами A1, ... An.&lt;/p&gt;
  &lt;pre&gt;typedef mpl::apply&amp;lt; mpl::find&amp;lt;f&amp;gt;, list,long &amp;gt;::type iter;
typedef mpl::apply&amp;lt; lambda&amp;lt;f&amp;gt; , a1, ... an &amp;gt;::type lmd;&lt;/pre&gt;
  &lt;hr /&gt;
  &lt;p&gt;Даже если не использовать мета функцию lambda в MPL. Библиотека всё равно предоставляет средство использования лямбда-выражений во время компиляции.&lt;br /&gt;Предположим, что мы хотим иметь лямбда-выражение, которое при вызове возвращает foo&amp;lt;Arg1,Arg2&amp;gt;. Такое лямбда-выражение может быть реализовано с помощью MPL Placeholder Expression - шаблон класса, созданный с одним или несколькими заполнителями:&lt;/p&gt;
  &lt;pre&gt;typedef  foo &amp;lt; boost :: mpl :: _1 , boost :: mpl :: _2 &amp;gt;  foo_specifier ;&lt;/pre&gt;
  &lt;p&gt;В данном случае &lt;strong&gt;foo_specifier &lt;/strong&gt;это конкретный тип, такой же, как int или std::set&amp;lt;std::string&amp;gt;. Внутренние механизмы MPL способны обнаруживать, что этот тип был получен при создании экземпляра шаблона класса с заполнителями, boost::mpl::_1 и boost::mpl::_2, для первого и второго аргументов при вызове foo_specifier. Выраженные заполнители избавляют нас от необходимости писать типовые классы мета-функций.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h3&gt;&lt;strong&gt;Примеры из реальной жизни:&lt;/strong&gt;&lt;/h3&gt;
  &lt;p&gt;Пример ассоциативной последовательности, хранящей типы mpl::pair.&lt;/p&gt;
  &lt;pre&gt;typedef boost::mpl::map&amp;lt;
	boost::mpl::pair&amp;lt;ITV8::GDRV::IVideoSource,		VideoSourceFactory&amp;gt;,
	boost::mpl::pair&amp;lt;ITV8::GDRV::IAudioSource,		AudioSourceFactory&amp;gt;,
	boost::mpl::pair&amp;lt;ITV8::GDRV::ITelemetry,		TelemetryFactory&amp;gt;,
	boost::mpl::pair&amp;lt;ITV8::GDRV::IIODevice,			IODeviceFactory&amp;gt;,
	boost::mpl::pair&amp;lt;ITV8::GDRV::IVideoAnalytics,	VideoAnaliticsFactory&amp;gt;,
	boost::mpl::pair&amp;lt;ITV8::GDRV::IAudioDestination,	AudioDestinationFactory&amp;gt;
&amp;gt; PanasonicDriverImplementationMap;&lt;/pre&gt;
  &lt;p&gt;Map - может содержать не более одного элемента для каждого ключа.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p&gt;Пример последовательности произвольного доступа, дополнительно поддерживающей операции back, push_back, pop_back. &lt;/p&gt;
  &lt;pre&gt;typedef boost::mpl::vector&amp;lt;
	ITV8::Utility::MultipartFilter,
	ITV8::Utility::AudioBufferG7xxSendingFilter
&amp;gt; G7xxFilterChain;&lt;/pre&gt;
  &lt;p&gt;Vector - самая простая и во многих случаях самая эффективная последовательность.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p&gt;MPL предоставляет возможность реализовать механизм более гибкой сборки классов из отдельных мелких модулей.&lt;/p&gt;
  &lt;pre&gt;template&amp;lt;typename TFilterMap&amp;gt;
class MultiplexingFilter : public boost::mpl::inherit_linearly&amp;lt;TFilterMap,
	boost::mpl::inherit&amp;lt;boost::mpl::placeholders::_1,
		detail::FilterContainer&amp;lt;
			boost::mpl::first&amp;lt;boost::mpl::placeholders::_2&amp;gt;,
			boost::mpl::second&amp;lt;boost::mpl::placeholders::_2&amp;gt;
		&amp;gt;
	&amp;gt; &amp;gt;::type
{
    // ...
};&lt;/pre&gt;
  &lt;p&gt;&lt;strong&gt;inherit_linearly&lt;/strong&gt; по сути - это for each во время компиляции, который применяет типы из TFilterMap, к типу Node, который применяется к результату предыдущего &amp;quot;вызова&amp;quot;. В данном случае Node - это boost::mpl::inherit, который строит иерархию из переданных ему типов.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;mpl::inherit&lt;/strong&gt; является прямой последовательностью. &lt;br /&gt;Это концепция MPL, представляющая последовательность элементов во время компиляции.Метафункции &lt;strong&gt;mpl::first&lt;/strong&gt; и &lt;strong&gt;mpl::second&lt;/strong&gt; предоставляют итераторы, ограничивающие диапазон элементов последовательности.&lt;/p&gt;
  &lt;p&gt;Заполнитель в форме _n - это просто синоним соответствующей специализации arg &amp;lt;n&amp;gt;. Безымянный заполнитель. Имена заполнителей можно сделать доступными в пространстве имен пользователя с помощью &lt;strong&gt;mpl :: placeholder&lt;/strong&gt;.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Таким образом у нас получается что-то вроде:&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;class MultiplexingFilter 
   : detail::FilterContainer&amp;lt;
            boost::mpl::first&amp;lt;TFilterMap::at&amp;lt;N - 1&amp;gt; &amp;gt;,
            boost::mpl::second&amp;lt;TFilterMap::at&amp;lt;N - 1&amp;gt; &amp;gt;
      &amp;gt; 
{
    // ...
};&lt;/pre&gt;
  &lt;p&gt;Т.е. содержимое первого элемента TFilterMap наследуют в типе FilterContainer от пустого базового класса. Потом, что получилось, становится базовым классом для содержимого второго элемента нашего TFilterMap в типе FilterContainer. Так продолжается, пока элементы в TFilterMap не закончатся.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Это нужно, чтобы на этапе компиляции получить структуру вида:&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;class MultiplexingFilter 
{
     detail::FilterContainer&amp;lt;key_from_map1, value_from_map1&amp;gt; data1;
     detail::FilterContainer&amp;lt;key_from_map2, value_from_map2&amp;gt; data2;
     //.....
     detail::FilterContainer&amp;lt;key_from_mapN, value_from_mapN&amp;gt; dataN;
};&lt;/pre&gt;
  &lt;hr /&gt;
  &lt;h3&gt;&lt;strong&gt;Лямбда выражения:&lt;/strong&gt;&lt;/h3&gt;
  &lt;p&gt;Лямбда выражения состоят из двух основных механизмов: классы метафункций и выражения с использованием заполнителей.&lt;/p&gt;
  &lt;p&gt;Можно сгенерировать класс метафункции из класса boost::add_pointer &amp;lt;_1&amp;gt;, используя лямбды MPL:&lt;/p&gt;
  &lt;pre&gt;template &amp;lt;class X&amp;gt;
struct two_pointers
	: twice&amp;lt;typename mpl::lambda&amp;lt;boost::add_pointer&amp;lt;_1&amp;gt;&amp;gt;::type, X&amp;gt;	
{};

BOOST_STATIC_ASSERT
((
	boost::is_same
	&amp;lt;
		typename two_pointers&amp;lt;int&amp;gt;::type, 
		int**
	&amp;gt;::value
));&lt;/pre&gt;
  &lt;p&gt;Т.е заполняемое выражение сохраняет результат между вызовами различных инстацнирований во внешней лямбда метафункции.&lt;/p&gt;
  &lt;p&gt;Хотя &lt;strong&gt;&lt;u&gt;основная цель использования лямбды состоит в том, чтобы превратить placeholder выражения в классы метафункций&lt;/u&gt;&lt;/strong&gt;, mpl::lambda может принять любое выражение лямбды, даже если это уже - класс метафункции. В этом случае лямбда возвращает свой неизменный аргумент. Алгоритмы MPL, такие как transform вызывают лямбду, &lt;u&gt;прежде&lt;/u&gt;, чем обратиться к результату класса метафункции, это позволяет таким алгоритмам работать одинаково хорошо с любым видом лямбда выражений. &lt;/p&gt;
  &lt;p&gt;Применим эту стратегию к twice, чтобы не передавать в нее лямбду явно:&lt;/p&gt;
  &lt;pre&gt;template &amp;lt;class F, class X&amp;gt;
struct twice : 
apply_helper
&amp;lt;
	typename mpl::lambda&amp;lt;F&amp;gt;::type, 
	typename apply_helper
	&amp;lt;
		typename mpl::lambda&amp;lt;F&amp;gt;::type, 
		X
	&amp;gt;::type
&amp;gt;
{};&lt;/pre&gt;
  &lt;p&gt;Теперь можно использовать twice с классами метафункций и заполнителями:&lt;/p&gt;
  &lt;pre&gt;int* x;

twice&amp;lt;add_pointer_f, int&amp;gt;::type          p = &amp;amp;x;		// передаем класс метафункции
twice&amp;lt;boost::add_pointer&amp;lt;_1&amp;gt;, int&amp;gt;::type q = &amp;amp;x;		// передаем метафункцию с заполнителем&lt;/pre&gt;
  &lt;h4&gt;&lt;strong&gt;Частичное приложение метафункции&lt;/strong&gt;&lt;/h4&gt;
  &lt;p&gt;Лямбды обеспечивают намного больше чем только способность передать метафункцию как аргумент, есть также дополнительные возможности, которые делают лямбды неотъемлемой частью почти каждой метапрограммной задачи.&lt;/p&gt;
  &lt;p&gt;Рассмотрите выражение лямбды mpl::plus&amp;lt;_1, _1&amp;gt;. Одинаковый аргумент передан обоим параметрам plus, таким образом число добавляется к самому себе. Двойная метафункция plus используется, чтобы создать унарное лямбда выражение. Другими словами, мы создали совершенно новое вычисление! Передавая не-заполнитель (non-placeholder) в качестве одного из аргументов, можно построить унарное лямбда выражение, которое добавляет постоянное значение, например, 42 к своему аргументу:&lt;/p&gt;
  &lt;pre&gt;mpl::plus&amp;lt;_1, mpl::int_&amp;lt;42&amp;gt;&amp;gt;&lt;/pre&gt;
  &lt;p&gt;&lt;strong&gt;Композиция метафункций:&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Лямбда выражения могут использоваться для сборки сложных метафункций из ряда простых. Например, следующее выражение, которое умножает результат суммы и разницы двух чисел, различием, является составной метафункцией из трех простых метафункций: умножения, сложения и вычитания:&lt;/p&gt;
  &lt;pre&gt;mpl::multiplies&amp;lt;mpl::plus&amp;lt;_1,_2&amp;gt;, mpl::minus&amp;lt;_1,_2&amp;gt;&amp;gt;&lt;/pre&gt;
  &lt;p&gt;Вычисляя лямбда выражение, MPL проверяет аргументы на предмет того, не являются ли они сами вложенными лямбда выражениями и если да, то они вычисляются в первую очередь, корректно передавая результат во внешнее выражение. Такая очередность работы с лямбда выражениями привносит большую гибкость и строгую логику.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;MPL использует специальное правило, чтобы облегчить объединение обычных шаблонов в метапрограммы:&lt;/strong&gt; &lt;u&gt;после того, как все заполнители были заменены фактическими аргументами, если у получившейся специализации шаблона X нет вложенного «::type», результат лямбды является только X.&lt;/u&gt;&lt;/p&gt;
  &lt;p&gt;Например, mpl::apply&amp;lt;vector&amp;lt;_&amp;gt;, T&amp;gt; является всегда только vector&amp;lt;T&amp;gt;. Если бы не это поведение мы должны были бы создавать тривиальные метафункции для обеспечения обычных специализаций шаблона в лямбда выражениях:&lt;/p&gt;
  &lt;pre&gt;// trivial std::vector generator
template&amp;lt;class U&amp;gt;
struct make_vector { typedef std::vector&amp;lt;U&amp;gt; type; };

typedef mpl::apply&amp;lt;make_vector&amp;lt;_&amp;gt;, T&amp;gt;::type vector_of_t;&lt;/pre&gt;
  &lt;p&gt;Вместо этого достаточно написать лишь 1 строку:&lt;/p&gt;
  &lt;pre&gt;typedef mpl::apply&amp;lt;std::vector&amp;lt;_&amp;gt;, T&amp;gt;::type vector_of_t;&lt;/pre&gt;
  &lt;hr /&gt;

</content></entry><entry><id>mrnimda:rkSYqC60B</id><link rel="alternate" type="text/html" href="https://teletype.in/@mrnimda/rkSYqC60B?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=mrnimda"></link><title>BOOST</title><published>2019-12-23T06:11:08.741Z</published><updated>2019-12-25T05:57:52.959Z</updated><summary type="html">BOOST::THREADS. Многопоточное программирование</summary><content type="html">
  &lt;p&gt;&lt;strong&gt;BOOST::THREADS. Многопоточное программирование&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Создание потока &lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Пример 1 демонстрирует очень простой пример использования класса boost::thread. Создается новый поток, который просто выводит “Hello World” на std::cout, а основной поток ждет его завершения. &lt;/p&gt;
  &lt;pre&gt;// Пример 1 
#include &amp;lt;boost/thread/thread.hpp&amp;gt; 
#include &amp;lt;iostream&amp;gt; 
void hello() 
{ 
	std::cout &amp;lt;&amp;lt; 
		&amp;quot;Hello world, I&amp;#x27;m a thread!&amp;quot; 
		&amp;lt;&amp;lt; std::endl; 
} 
int main(int argc, char* argv[]) 
{ 
	boost::thread thrd(&amp;amp;hello); 
	thrd.join(); 
	return 0; 
} &lt;/pre&gt;
  &lt;p&gt;&lt;strong&gt;Мьютексы&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;В примере 2 показано очень простое использование класса boost::mutex. Создаются два новых потока, каждый из них 10 раз выводит свой id и счетчик цикла, а основной поток дожидается их завершения. Объект std::cout является разделяемым ресурсом, поэтому каждый поток использует глобальный мьютекс, гарантирующий, что только один поток в каждый момент времени пытается осуществлять вывод. &lt;/p&gt;
  &lt;pre&gt;// Пример 2 
#include &amp;lt;boost/thread/thread.hpp&amp;gt; 
#include &amp;lt;boost/thread/mutex.hpp&amp;gt; 
#include &amp;lt;iostream&amp;gt; 
boost::mutex io_mutex; 
struct count 
{ 
	count(int id) : id(id) { } 
	void operator()() 
	{ 
		for (int i = 0; i &amp;lt; 10; ++i) 
		{ 
			boost::mutex::scoped_lock lock(io_mutex); 
			Douglas Schmidt, Michael Stal, Hans Rohnert, and Frank 
				Buschmann. Pattern-Oriented Software Architecture 
				Volume 2 — Patterns for Concurrent and Networked 
				Objects (Wiley, 2000). 
				(Scoped Lock). 
				std::cout &amp;lt;&amp;lt; id &amp;lt;&amp;lt; &amp;quot;: &amp;quot; 
				&amp;lt;&amp;lt; i &amp;lt;&amp;lt; std::endl; 
		} 
	} 
	int id; 
}; 
int main(int argc, char* argv[]) 
{ 
	boost::thread thrd1(count(1)); 
	boost::thread thrd2(count(2)); 
	thrd1.join(); 
	thrd2.join(); 
	return 0; 
}&lt;/pre&gt;
  &lt;p&gt;Передача данных потоку требует ручного кодирования функционального объекта. Хотя этот код и тривиален, писать его всякий раз довольно скучно. Есть и более простое решение. Функциональные библиотеки позволяют создать новые функциональные объекты, связывая (bind) другие функциональные объекты с данными, которые при вызове будут им переданы. В примере 3 показано, как при использовании библиотеки Boost.Bind можно упростить код примера 2, отказавшись от ручного кодирования функционального объекта. &lt;/p&gt;
  &lt;pre&gt;// Пример 3 
// Эта программа идентична программе 
// из примера 2, кроме того, что 
// использует Boost.Bind 
// при создании потока, 
// принимающего параметры. 
#include &amp;lt;boost/thread/thread.hpp&amp;gt; 
#include &amp;lt;boost/thread/mutex.hpp&amp;gt; 
#include &amp;lt;boost/bind.hpp&amp;gt; 
#include &amp;lt;iostream&amp;gt; 
boost::mutex io_mutex; 
void count(int id) 
{ 
	for (int i = 0; i &amp;lt; 10; ++i) 
	{ 
		boost::mutex::scoped_lock lock(io_mutex); 
		std::cout &amp;lt;&amp;lt; id &amp;lt;&amp;lt; &amp;quot;: &amp;quot; &amp;lt;&amp;lt;i &amp;lt;&amp;lt; std::endl; 
	} 
} 
int main(int argc, char* argv[]) 
{ 
	boost::thread thrd1(boost::bind(&amp;amp;count, 1)); 
	boost::thread thrd2(boost::bind(&amp;amp;count, 2)); 
	thrd1.join(); 
	thrd2.join(); 
	return 0; 
}&lt;/pre&gt;
  &lt;p&gt;&lt;strong&gt;Условные переменные &lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Иногда недостаточно просто установить блок и воспользоваться разделяемым ресурсом. Иногда необходимо, чтобы разделяемый ресурс перед использованием находился в некотором специальном состоянии. Например, поток может пытаться извлечь данные из стека, ожидая, когда в нем они появятся, если в настоящий момент стек пуст. Для реализации такого вида синхронизации мьютекса недостаточно. В этом случае можно использовать другой механизм синхронизации, известный как условная переменная. &lt;/p&gt;
  &lt;p&gt;Условная переменная всегда используется в связке с мьютексом и разделяемым ресурсом (или ресурсами). Поток первым делом блокирует мьютекс, а затем проверяет, находится ли ресурс в состоянии, допускающем его безопасное использование требуемым образом. Если он не в нужном состоянии, поток вызывает для условной переменной операцию ожидания. Эта операция приводит к разблокированию мьютекса во время ожидания, так что другой поток получает возможность изменить состояние разделяемого ресурса. Она также гарантирует, что при возвращении потока после ожидания мьютекс окажется заблокированным. Когда другой поток изменяет состояние разделяемого ресурса, он должен уведомить потоки, которые могут ждать условную переменную, позволяя им завершить ожидание. &lt;/p&gt;
  &lt;p&gt;Пример 4 иллюстрирует применение класса boost::condition. Определен класс, реализующий ограниченный буфер — контейнер фиксированного размера с поддержкой ввода/вывода в порядке очереди (FIFO). Этот буфер сделан потокобезопасным благодаря использованию boost::mutex. Операции put и get используют условную переменную, чтобы удостовериться в том, что поток будет ждать, пока буфер не окажется в состоянии, необходимом для завершения операции. Создаются два потока, один помещает в этот буфер сто целых, а другой их же извлекает. Ограниченный буфер в каждый момент времени может содержать только 10 целых, поэтому каждому из потоков приходится периодически ждать другой поток. Для проверки того, что это действительно происходит, операции put и get выводят в std::cout диагностические сообщения. Наконец, основной поток ожидает завершения обоих потоков.&lt;/p&gt;
  &lt;pre&gt;// Пример 4 
#include &amp;lt;boost/thread/thread.hpp&amp;gt; 
#include &amp;lt;boost/thread/mutex.hpp&amp;gt; 
#include &amp;lt;boost/thread/condition.hpp&amp;gt; 
#include &amp;lt;iostream&amp;gt; 
const int BUF_SIZE = 10; 
const int ITERS = 100; 
boost::mutex io_mutex; 
class buffer 
{ 
public: 
	typedef boost::mutex::scoped_lock scoped_lock; 
	buffer() : p(0), c(0), full(0) { } 
	void put(int m) 
	{ 
		scoped_lock lock(mutex); 
		if (full == BUF_SIZE) 
		{ 
			{ 
				boost::mutex::scoped_lock lock(io_mutex); 
				std::cout &amp;lt;&amp;lt;&amp;quot;Buffer is full. Waiting...&amp;quot;&amp;lt;&amp;lt; std::endl; 
			} 
			while (full == BUF_SIZE) 
				cond.wait(lock); 
		} 
		buf[p] = m; 
		p = (p+1) % BUF_SIZE; 
		++full; 
		cond.notify_one(); 
	} 
	int get() 
	{ 
		scoped_lock lk(mutex); 
		if (full == 0) 
		{ 
			{ 
				boost::mutex::scoped_lock lock(io_mutex); 
				std::cout &amp;lt;&amp;lt; &amp;quot;Buffer is empty. Waiting...&amp;quot;
&amp;lt;&amp;lt; std::endl; 
			} 
			while (full == 0) 
				cond.wait(lk); 
		} 
		int i = buf[c]; 
		c = (c+1) % BUF_SIZE; 
		--full; 
		cond.notify_one(); 
		return i; 
	} 
private: 
	boost::mutex mutex; 
	boost::condition cond; 
	unsigned int p, c, full; 
	int buf[BUF_SIZE]; 
}; 
buffer buf; 
void writer() 
{ 
	for (int n = 0; n &amp;lt; ITERS; ++n) 
	{ 
		{ 
			boost::mutex::scoped_lock lock(io_mutex); 
			std::cout &amp;lt;&amp;lt; &amp;quot;sending: &amp;quot; &amp;lt;&amp;lt; n &amp;lt;&amp;lt; std::endl; 
		} 
		buf.put(n); 
	} 
} 
void reader() 
{ 
	for (int x = 0; x &amp;lt; ITERS; ++x) 
	{ 
		int n = buf.get(); 
		{ 
			boost::mutex::scoped_lock lock(io_mutex); 
			std::cout &amp;lt;&amp;lt; &amp;quot;received: &amp;quot; &amp;lt;&amp;lt; n &amp;lt;&amp;lt; std::endl; 
		} 
	} 
} 
int main(int argc, char* argv[]) 
{ 
	boost::thread thrd1(&amp;amp;reader); 
	boost::thread thrd2(&amp;amp;writer); 
	thrd1.join(); 
	thrd2.join(); 
	return 0; 
}&lt;/pre&gt;
  &lt;p&gt;&lt;strong&gt;Локальная память потока &lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Пример 5 иллюстрирует очень простое применение класса boost::thread_specific_ptr. Создаются два потока, в них инициализируется локальная память потока, а затем в цикле 10 раз значение целого, расположенного по адресу «умного» указателя инкрементируется, а результат выводится в std::cout (который синхронизирован с помощью мьютекса, так как является разделяемым ресурсом). Основной поток ожидает завершения этих двух потоков. Вывод в этом примере ясно показывает, что каждый поток оперирует со своим экземпляром данных, хотя оба они используют один и тот же boost::thread_specific_ptr.&lt;/p&gt;
  &lt;pre&gt;// Пример 5 
#include &amp;lt;boost/thread/thread.hpp&amp;gt; 
#include &amp;lt;boost/thread/mutex.hpp&amp;gt; 
#include &amp;lt;boost/thread/tss.hpp&amp;gt; 
#include &amp;lt;iostream&amp;gt; 
boost::mutex io_mutex; 
boost::thread_specific_ptr&amp;lt;int&amp;gt; ptr; 
struct count 
{ 
	count(int id) : id(id) { } 
	void operator()() 
	{ 
		if (ptr.get() == 0) 
			ptr.reset(new int(0)); 
		for (int i = 0; i &amp;lt; 10; ++i) 
		{ 
			(*ptr)++; 
			boost::mutex::scoped_lock lock(io_mutex); 
			std::cout &amp;lt;&amp;lt; id &amp;lt;&amp;lt; &amp;quot;: &amp;quot; &amp;lt;&amp;lt; *ptr &amp;lt;&amp;lt; std::endl; 
		} 
	} 
	int id; 
}; 
int main(int argc, char* argv[]) 
{ 
	boost::thread thrd1(count(1)); 
	boost::thread thrd2(count(2)); 
	thrd1.join(); 
	thrd2.join(); 
	return 0; 
}&lt;/pre&gt;
  &lt;p&gt;&lt;strong&gt;Однократно вызываемые функции&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Остается разобраться с одним вопросом: как сделать функции инициализации (такие как конструкторы) потокобезопасными. Например, когда «глобальный» экземпляр объекта создается как синглетон уровня приложения (единственный существующий в приложении объект данного типа), существует проблема порядка инстанцирования, поэтому используется функция, возвращающая статический экземпляр, которая гарантирует, что при первом к ней обращении этот экземпляр будет создан. Проблема в том, что если несколько потоков одновременно вызовут эту функцию, конструктор для статического объекта также может быть вызван несколько раз, и результаты могут оказаться плачевными. &lt;/p&gt;
  &lt;p&gt;Решение проблемы в так называемых «однократно вызываемых функциях» (once routine). Такая функция вызывается в приложении только один раз. Если несколько потоков попытаются ее вызвать одновременно, только один из них получит такую возможность, а в это время все остальные потоки будут ждать, пока выполнение функции не завершится. Чтобы гаранти ровать однократное выполнение, такая функция вызывается косвенно через другую функцию, которой передается указатель на исходную и ссылка на специальный флаг, сигнализирующий о факте вызова функции. Этот флаг инициализируется статически, что гарантирует инициализацию в период компиляции, а не в период выполнения. И таким образом не представляет проблемы для многопоточной инициализации. Библиотека Boost.Threads предоставляет возможность однократного вызова функции посредством boost::call_once, а также определяет тип для флага boost::once_flag и специальную макроподстановку, используемую для статической инициализации флага, BOOST_ONCE_INIT. &lt;/p&gt;
  &lt;p&gt;Пример 6 показывает очень простой пример использования boost::call_once. Глобальное целое статически инициализируется нулем, а экземпляр boost::once_flag статически инициализируется с помощью BOOST_ONCE_INIT. Основной поток запускает два потока, каждый из которых пытается «инициализировать» глобальное целое, вызывая boost::call_once с указателем на функцию, инкрементирующую целое. Затем основной поток дожидается завершения обоих потоков и выводит конечное значение целого в std::cout. Вывод демонстрирует, что функция действительно была вызвана только однажды, так как значение целого – единица. &lt;/p&gt;
  &lt;pre&gt;// Пример 6 
#include &amp;lt;boost/thread/thread.hpp&amp;gt; 
#include &amp;lt;boost/thread/once.hpp&amp;gt; 
#include &amp;lt;iostream&amp;gt; 
int i = 0; 
boost::once_flag flag = BOOST_ONCE_INIT; 
void init() 
{ 
	++i; 
} 
void thread() 
{ 
	boost::call_once(&amp;amp;init, flag); 
} 
int main(int argc, char* argv[]) 
{ 
	boost::thread thrd1(&amp;amp;thread); 
	boost::thread thrd2(&amp;amp;thread); 
	thrd1.join(); 
	thrd2.join(); 
	std::cout &amp;lt;&amp;lt; i &amp;lt;&amp;lt; std::endl; 
	return 0; 
}&lt;/pre&gt;
  &lt;p&gt;&lt;strong&gt;Инициализация параметров&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Есть ещё классная штука - возможность инициализировать параметры, запустив функцию однократно. Например, есть три потока, которые обращаются к переменной &lt;strong&gt;х&lt;/strong&gt;, но сперва её надо инициализировать, причём только один раз, а не всеми тремя потоками сразу. Для этого предусмотрен специальный механизм:&lt;/p&gt;
  &lt;pre&gt;int i;
boost::once_flag flag = BOOST_ONCE_FLAG;
void init()
{
 i = 0;
}
void thread()
{
 boost::call_once(&amp;amp;init, flag);
 // дальнейший код
}&lt;/pre&gt;
  &lt;p&gt;Так вот, инициализация произойдёт всего один раз, независимо от числа конкурирующих потоков.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Ожидание внутри потоков&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Иногда надо подождать немного в потоке. Для этого есть два разных способа&lt;br /&gt;•	boost::this_thread::sleep(params)&lt;br /&gt;•	boost::this_thread::yield()&lt;br /&gt;Первый (с установленными параметрами) замораживает поток на указанное время. &lt;br /&gt;Второй действует хитрее - он ненадолго замораживает поток, отдавая тем самым ресурсы другим участникам состязания за них. Эдакая джентльменская уступка. &lt;/p&gt;
  &lt;hr /&gt;
  &lt;p&gt;&lt;strong&gt;BOOST::BIND &lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Предположим, что есть функция:&lt;/p&gt;
  &lt;pre&gt;bool compare(int a, int b) {return a &amp;lt; b;}&lt;/pre&gt;
  &lt;p&gt;для того, чтобы использовать эту функцию в, например, алгоритме sort нам необходимо написать:&lt;/p&gt;
  &lt;pre&gt;std::vector&amp;lt;int&amp;gt; vec(…);
std::sort(vec.begin(), vec.end(), compare);&lt;/pre&gt;
  &lt;p&gt;Теперь немного усложним задачу – в зависимости от флага нам необходимо сортировать либо по возрастанию, либо по убыванию. При использовании только STL пришлось бы писать две функции. Накладно. С помощью boost эту задачу решить значительно проще:&lt;/p&gt;
  &lt;pre&gt;bool compare(int a, int b, bool reverseSort) {return reverseSort ? a &amp;gt; b : a &amp;lt; b;}

std::vector&amp;lt;int&amp;gt; vec(…);
std::sort(vec.begin(), vec.end(), boost::bind(compare, _1, _2, order));&lt;/pre&gt;
  &lt;p&gt;где order – булевская переменная, задающая порядок сортировки. Последняя строчка выглядит несколько странновато, но именно в ней и заключается вся суть. В результате ее компиляции будет получен код, реализующий сортировку массива, использующий для сравнения функцию compare, и передающей ей на вход три аргумента – два числа, которые необходимо сравнить, и способ их сравнения. &lt;br /&gt;Рассмотрим этот код подробнее, записав его так:&lt;/p&gt;
  &lt;pre&gt;int a = 1, b = 2;
boost::bind(compare, _1, _2, false)(a, b);&lt;/pre&gt;
  &lt;p&gt;(что-то подобное содержится в недрах функции std::sort). Последняя строчка в этом коде обозначает следующее:&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;boost::bind&lt;/strong&gt; – функция, создающая необходимый нам связыватель. Возвращает экземпляр функционального объекта (функтора), для которого применим оператор вызова функции. (compare, - первый аргумент функции определяет имя/указатель на функцию, которая должна быть вызвана в связывателе. В данном случае это функция compare; _1, _2 – плейсхолдеры, определяющие логику передачи параметров из оператора вызова функции в функцию compare. , false) – значение последнего аргумента, передаваемого в compare. (a, b) – оператор вызова функции, производящий связывание аргументов и вызов функции compare. В итоге будет выполнена следующий код:&lt;/p&gt;
  &lt;pre&gt;compare(a, b, false);&lt;/pre&gt;
  &lt;p&gt;Из приведенного примера можно увидеть, что упомянутые выше плейсхолдеры определяют связь между аргументами у оператора вызова функции связывателя и аргументами вызываемой функцией. Причем, количество аргументов у оператора вызова функции связывателя определяется количеством плейсхолдеров:&lt;/p&gt;
  &lt;pre&gt;boost::bind(compare, _1, 4, false)(a, b); // неправильно. 
boost::bind(compare, _1, 4, false)(a); // правильно
boost::bind(compare, _1, _2, false)(a); // неправильно
boost::bind(compare, _1, _2, false)(a, b); // правильно&lt;/pre&gt;
  &lt;p&gt;Очевидно, что попытка задать в вызове boost::bind количество параметров, несоответствующее количеству обязательных параметров в вызываемой функции приведет к ошибке. Равно как и попытка указания количества параметров большего, чем количество параметров в вызываемой функции. Действуя по аналогии мы можем записать:&lt;/p&gt;
  &lt;pre&gt;std::find_if(vec.begin(), vec.end(), boost::bind(compare, _1, 4, false)); // поиск первого числа, меньшего 4
std::remove_if(vec.begin(), vec.end(), boost::bind(compare, _1, 4, true)); // удаление из последовательности всех элементов, больших 4
// и т. д.&lt;/pre&gt;
  &lt;p&gt;В случае использования чистого STL для каждого случая пришлось бы искать подходящий функтор… Или писать его. Здесь необходимо обратить внимание на то, что в качестве первого параметра функции bind может выступать не только указатель на функцию, но и другой функциональный объект. Например:&lt;/p&gt;
  &lt;pre&gt;std::find_if(vec.begin(), vec.end(), boost::bind(std::equal_to&amp;lt;int&amp;gt;(), _1, 4));&lt;/pre&gt;
  &lt;p&gt;этот строчка найдет в массиве первый элемент, равный 4.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Использование с указателями на функции члены&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Помимо указателей на глобальные функции и функторов, в качестве первого параметра можно указывать указатель на функцию-член какого-то класса. Предположим один из простейших вариантов использования:&lt;/p&gt;
  &lt;pre&gt;class GraphicsObject
{
public:
 void Draw(Canvas* canvas, DrawModes mode);
};
// Объявляем коллекцию объектов этого класса:
std::list&amp;lt;GraphicsObject*&amp;gt; GraphObjects;
// А теперь нам нужно отрисовать все объекты коллекции на заданном канвасе
std::for_each(GraphObjects.begin(), GraphObjects.end(); ……..);&lt;/pre&gt;
  &lt;p&gt;при использовании чистого STL нам бы пришлось писать свой собственный функтор, вызывающий у переданного объекта метод Draw с заданными параметрами. Но можно воспользоваться boost::bind:&lt;/p&gt;
  &lt;pre&gt;std::for_each(GraphObjects.begin(), GraphObjects.end(), 
 boost::bind(&amp;amp;GraphObject::Draw, _1, canvas, mode));&lt;/pre&gt;
  &lt;p&gt;в результате чего мы получаем требуемый результат. У всех объектов коллекции вызывается метод Draw, которому передаются параметры canvas и mode. При использовании указателя на функцию-член класса необходимо помнить о том, что первым параметром ей должен передаваться указатель на объект, для которого эта функция должна вызываться. В приведенном примере мы указываем, что метод должен вызываться у переданного в bind единственного параметра. Но никто не мешает нам в качестве первого передаваемого параметра указать this, или любой другой указатель. Но, как говориться в современных набивших оскомину рекламах, это еще не все.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Использование с указателем на член данных&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Помимо указателей на члены-функции можно использовать указатели на члены данных. Синтаксис подобный и правила использования при этом почти такие же. Возьмем задачу. Есть структура и вектор ее экземпляров:&lt;/p&gt;
  &lt;pre&gt;struct Point
{
 int x;
 int y;
};
std::vector&amp;lt;Point&amp;gt; PointsArray;&lt;/pre&gt;
  &lt;p&gt;Теперь надо удалить из этого массива все элементы, у которых координата x равна нулю. Сделать это просто:&lt;/p&gt;
  &lt;pre&gt;std::remove_if(PointsArray.begin(), PointsArray.end(), 
 boost::bind(std::equal_to&amp;lt;int&amp;gt;(), boost::bind(&amp;amp;Point::x, _1), 0));&lt;/pre&gt;
  &lt;p&gt;Т. е. для каждого элемента массива вызывается вложенный связыватель, который возвращает значение соответствующего элемента структуры, после чего производится его сравнение с нулем. Тут мы видим еще одну возможность связывателей:&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Каскадное использование связывателей&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Связыватели могут вкладываться друг в друга. Один из вариантов такого вкладывания проиллюстрирован предыдущем примером. Единственное, что в этом случае надо «держать в голове» - это то, что плейсхолдеры, вне зависимости от того, в каком bind&amp;#x27;ере (по уровню вложенности) они находятся, «адресуют» параметры самого внешго binder&amp;#x27;а. Усложним предыдущий пример. Нужно выбросить все точки, имеющие нулевые координаты:&lt;/p&gt;
  &lt;pre&gt;std::remove_if( PointsArray.begin(), PointsArray.end(), boost::bind(
 std::logical_and(), 
 boost::bind(std::equal_to&amp;lt;int&amp;gt;(), boost::bind(&amp;amp;Point::x, _1), 0),
 boost::bind(std::equal_to&amp;lt;int&amp;gt;(), boost::bind(&amp;amp;Point::y, _1), 0)
 )
));&lt;/pre&gt;
  &lt;p&gt;Это хотя и работает так, как хочется, но выглядит слишком наворочено. По этому начиная с версии 1.33 в boost::bind появилась новая возможность -&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Перегруженные операторы&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Для упрощения приведенных выше многоэтажных конструкций для boost::bind (начиная с версии 1.33 boost&amp;#x27;а) перегружены следующие операторы: &lt;strong&gt;!&lt;/strong&gt;, &lt;strong&gt;==&lt;/strong&gt;, &lt;strong&gt;!=&lt;/strong&gt;, &lt;strong&gt;&amp;lt;&lt;/strong&gt;, &lt;strong&gt;?&lt;/strong&gt;, &lt;strong&gt;&amp;gt;&lt;/strong&gt; и &lt;strong&gt;&amp;gt;=&lt;/strong&gt;. Таким образом, приведенное выше выражение упрощается до:&lt;/p&gt;
  &lt;pre&gt;std::remove_if( PointsArray.begin(), PointsArray.end(), boost::bind(
 boost::bind(&amp;amp;Point::x, _1) == 0 &amp;amp;&amp;amp; boost::bind(&amp;amp;Point::y, _1) == 0)
 ));&lt;/pre&gt;
  &lt;p&gt;&lt;strong&gt;Использование ссылок&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Одна из неприятностей заключается в том, что если связываемая функция принимает какой-то из своих аргументов по ссылке, то с использованием boost::bind его (напрямую) передать нельзя. Т. е. например, в следующем случае:&lt;/p&gt;
  &lt;pre&gt;void foo(int&amp;amp; a, int&amp;amp; b);
int a = 1, b = 2;
boost::bind(foo, a, b);&lt;/pre&gt;
  &lt;p&gt;аргументы a и b по ссылке переданы не будут. Для того, чтобы действительно передать ссылки, необходимо делать такой вызов:&lt;/p&gt;
  &lt;pre&gt;boost::bind(foo, boost::ref(a), boost::ref(b));&lt;/pre&gt;
  &lt;p&gt;В этом случае foo, вызываемый из связывателя, будет действительно работать со ссылками на соответствующие переменные.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Пример использования.&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;boost/bind.hpp&amp;gt;
#include &amp;lt;boost/function.hpp&amp;gt;
class Test
{
public:
	void f_1()
	{
		std::cout &amp;lt;&amp;lt; &amp;quot;void f()&amp;quot; &amp;lt;&amp;lt; std::endl;
	}
	void f_2(int i)
	{
		std::cout &amp;lt;&amp;lt; &amp;quot;void f_2(): &amp;quot; &amp;lt;&amp;lt; i &amp;lt;&amp;lt; std::endl;
	}
	void f_3(const int &amp;amp;i)
	{
		std::cout &amp;lt;&amp;lt; &amp;quot;void f_2(): &amp;quot; &amp;lt;&amp;lt; i &amp;lt;&amp;lt; std::endl;
	}
	void two_params(int a, int b)
	{
		std::cout &amp;lt;&amp;lt; &amp;quot;void two_params(int a, int b): &amp;quot; &amp;lt;&amp;lt; a &amp;lt;&amp;lt; &amp;quot;, &amp;quot; &amp;lt;&amp;lt; b &amp;lt;&amp;lt; std::endl;
	}
};

class Test2
{
public:
	void do_stuff(const std::vector&amp;lt;int&amp;gt;&amp;amp; v)
	{
		std::copy(v.begin(), v.end(), std::ostream_iterator&amp;lt;int&amp;gt;(std::cout, &amp;quot; &amp;quot;));
	}
};

void test(const std::string &amp;amp;a)
{
	std::cout &amp;lt;&amp;lt; &amp;quot;void test(const std::string &amp;amp;a). a = &amp;quot; &amp;lt;&amp;lt; a &amp;lt;&amp;lt; std::endl;
}

void prn(boost::function&amp;lt;void(int)&amp;gt; fn, int a)
{
	fn(a);
}

int _tmain(int argc, _TCHAR* argv[])
{
	////////////////////// bind //////////////////////
	std::cout &amp;lt;&amp;lt; &amp;quot;boost::bind test&amp;quot; &amp;lt;&amp;lt; std::endl;
	Test a;
	boost::bind(&amp;amp;Test::f_1, &amp;amp;a)();

	boost::bind(&amp;amp;Test::f_2, &amp;amp;a, 1)();
	int test_int(2);
	boost::bind(&amp;amp;Test::f_2, &amp;amp;a, _1)(test_int);

	boost::bind(&amp;amp;Test::f_3, &amp;amp;a, 3)();
	int test_int_2(4);
	boost::bind(&amp;amp;Test::f_3, &amp;amp;a, _1)(test_int_2);

	int one(100), two(200);
	boost::bind(&amp;amp;Test::two_params, &amp;amp;a, _1, _2)(one, two);
	boost::bind(&amp;amp;Test::two_params, &amp;amp;a, _2, _1)(one, two);

	std::string test_str(&amp;quot;Hi there.&amp;quot;);
	boost::bind(&amp;amp;test, test_str)();
	boost::bind(&amp;amp;test, _1)(test_str);
	std::cout &amp;lt;&amp;lt; std::endl;

	////////////////////// function //////////////////////
	std::cout &amp;lt;&amp;lt; &amp;quot;boost::function test&amp;quot; &amp;lt;&amp;lt; std::endl;

	boost::function&amp;lt;void(void)&amp;gt; func;
	func = boost::bind(&amp;amp;Test::f_1, &amp;amp;a);
	func();
	func = boost::bind(&amp;amp;Test::f_2, &amp;amp;a, 201);
	func();
	func = boost::bind(&amp;amp;Test::f_3, &amp;amp;a, 202);
	func();
	func = boost::bind(&amp;amp;Test::two_params, &amp;amp;a, 203, 204);
	func();

	boost::function&amp;lt;void(int)&amp;gt; func_2;
	func_2 = boost::bind(&amp;amp;Test::f_2, &amp;amp;a, _1);
	prn(func_2, 301);
	func_2 = boost::bind(&amp;amp;Test::f_3, &amp;amp;a, _1);
	prn(func_2, 302);

	int i_303(303), i_304(304), i_305(305), i_306(306);
	func_2 = boost::bind(&amp;amp;Test::two_params, &amp;amp;a, i_303, _1);
	prn(func_2, 1);
	func_2 = boost::bind(&amp;amp;Test::two_params, &amp;amp;a, _1, i_304);
	prn(func_2, 1);

	Test2 t;
	std::vector&amp;lt;int&amp;gt; vec;
	vec.resize(20);
	std::generate_n(vec.begin(), 20, rand);
	std::copy(vec.begin(), vec.end(), std::ostream_iterator&amp;lt;int&amp;gt;(std::cout, &amp;quot; &amp;quot;));
	//simple_bind(&amp;amp;Test::do_stuff, t, _1)(vec);
	//boost::bind(&amp;amp;Test2::do_stuff, t, _1)(vec);

	return 0;
}&lt;/pre&gt;
  &lt;hr /&gt;
  &lt;p&gt;&lt;strong&gt;BOOST::ASIO (::IO_SERVICE) &lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;boost::asio на самом деле, хоть и называется библиотекой асинхронной, но может выполнять и синхронные, и асинхронные операции. Вся библиотека при этом завязана на объекты класса boost::asio::io_service – именно asio::io_service предоставляет программе связь с нативными объектами ввода/вывода. Соответственно, что бы ваша программа могла работать с boost::asio, нужно создать хотя бы один объект этого класса и уже после этого “аттачить” все другие объекты к нему, например вот так:&lt;/p&gt;
  &lt;pre&gt;boost::asio::io_service io_service;
boost::asio::ip::tcp::socket socket(io_service);&lt;/pre&gt;
  &lt;p&gt;Подключить только что созданный сокет к серверу тоже не представляет из себя проблемы:&lt;/p&gt;
  &lt;pre&gt;boost::system::error_code ec; // здесь будет записана ошибка, если она была
socket.connect(server_endpoint, ec); // server_endpoint задаёт куда подключаемся&lt;/pre&gt;
  &lt;p&gt;После того как мы подключили объекты ввода-вывода (в данном случае socket) к asio::io_service – этот сервис будет осуществлять взаимодействие с операционной системой и получать/отправлять данные.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;boost::asio асинхронный&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Тот вариант, что мы рассмотрели выше – это синхронное взаимодействие, т.е., в данном случае это значит, что пока происходит подключение – наша программа “зависнет” до того момента, пока подключение не выполнится, либо не произойдёт ошибка. Если же мы хотим что бы работа boost::asio была асинхронной, надо идти немного другим путём, который тоже предусмотрен в библиотеке. Для работы с асинхронными операциями мы должны передать в функцию так называемый completion handler, или, говоря по русски, функцию, которая будет вызвана, когда операция завершится (т.е. либо подключится, либо возникнет ошибка):&lt;/p&gt;
  &lt;pre&gt;void your_completion_handler(const boost::system::error_code&amp;amp; ec)
{
// тут обрабатываем подключение либо ошибку
}

boost::asio::io_service io_service;
boost::asio::ip::tcp::socket socket(io_service);
socket.async_connect(server_endpoint, your_completion_handler);&lt;/pre&gt;
  &lt;p&gt;Все асинхронные функции в boost::asio работают именно по такому принципу. С одной стороны, это довольно удобно для программирования – логика работы каждой из функций отделена от других, с другой стороны это может может показаться Вам немного непривычным.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Использование boost::asio&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Приведу короткий и простой пример использования boost::asio , это код примера из самого boost, лишь перевёл комментарии:&lt;/p&gt;
  &lt;pre&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;istream&amp;gt;
#include &amp;lt;ostream&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;boost/asio.hpp&amp;gt;
 
using boost::asio::ip::tcp;
 
int main(int argc, char* argv[])
{
 try
 {
 if (argc != 3) // если аргументы не заданы или заданы не все
 {
 std::cout &amp;lt;&amp;lt; &amp;quot;Usage: sync_client &amp;lt;server&amp;gt; &amp;lt;path&amp;gt;\n&amp;quot;;
 std::cout &amp;lt;&amp;lt; &amp;quot;Example:\n&amp;quot;;
 std::cout &amp;lt;&amp;lt; &amp;quot; sync_client www.boost.org /LICENSE_1_0.txt\n&amp;quot;;
 return 1;
 }

boost::asio::io_service io_service; // основной класс asio

// Получаем список конечных точек для указанного сервера
 tcp::resolver resolver(io_service);
 tcp::resolver::query query(argv[1], &amp;quot;http&amp;quot;);
 tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
 tcp::resolver::iterator end;

 // Перебираем эти конечные точки и пробуем подключиться
 tcp::socket socket(io_service);
 boost::system::error_code error = boost::asio::error::host_not_found;
 while (error &amp;amp;&amp;amp; endpoint_iterator != end)
 {
 socket.close();
 socket.connect(*endpoint_iterator++, error);
 }
 if (error) // подключиться не удалось
 throw boost::system::system_error(error);

 // Формируем запрос к веб-серверу. Указываем &amp;quot;Connection: close&amp;quot; что бы
 // сервер закрыл соединение как только отправит нам данные. Это
 // позволит нам узнать что отправка завершенна как только возникнет EOF
 boost::asio::streambuf request;
 std::ostream request_stream(&amp;amp;request);
 request_stream &amp;lt;&amp;lt; &amp;quot;GET &amp;quot; &amp;lt;&amp;lt; argv[2] &amp;lt;&amp;lt; &amp;quot; HTTP/1.0\r\n&amp;quot;;
 request_stream &amp;lt;&amp;lt; &amp;quot;Host: &amp;quot; &amp;lt;&amp;lt; argv[1] &amp;lt;&amp;lt; &amp;quot;\r\n&amp;quot;;
 request_stream &amp;lt;&amp;lt; &amp;quot;Accept: */*\r\n&amp;quot;;
 request_stream &amp;lt;&amp;lt; &amp;quot;Connection: close\r\n\r\n&amp;quot;;

 // Отправляем сформированный запрос веб-серверу.
 boost::asio::write(socket, request);

 // Читаем ответ сервер. streambuf response примет все данные
 // он сам будет увеличиваться по мере поступления данных от сервера.
 boost::asio::streambuf response;
 boost::asio::read_until(socket, response, &amp;quot;\r\n&amp;quot;);

 // Проверяем что бы не было ошибок.
 std::istream response_stream(&amp;amp;response);
 std::string http_version;
 response_stream &amp;gt;&amp;gt; http_version;
 unsigned int status_code;
 response_stream &amp;gt;&amp;gt; status_code;
 std::string status_message;
 std::getline(response_stream, status_message);
 if (!response_stream || http_version.substr(0, 5) != &amp;quot;HTTP/&amp;quot;) // ошибка
 {
 std::cout &amp;lt;&amp;lt; &amp;quot;Invalid response\n&amp;quot;;
 return 1;
 }
 if (status_code != 200) // если код ответа не 200, то это тоже ошибка
 {
 std::cout &amp;lt;&amp;lt; &amp;quot;Response returned with status code &amp;quot; &amp;lt;&amp;lt; status_code &amp;lt;&amp;lt; &amp;quot;\n&amp;quot;;
 return 1;
 }

 // Читаем ответ. Он закончится пустой строкой.
 boost::asio::read_until(socket, response, &amp;quot;\r\n\r\n&amp;quot;);

 // Парсим заголовки
 std::string header;
 while (std::getline(response_stream, header) &amp;amp;&amp;amp; header != &amp;quot;\r&amp;quot;)
 std::cout &amp;lt;&amp;lt; header &amp;lt;&amp;lt; &amp;quot;\n&amp;quot;;
 std::cout &amp;lt;&amp;lt; &amp;quot;\n&amp;quot;;

 // Выводим в лог
 if (response.size() &amp;gt; 0)
 std::cout &amp;lt;&amp;lt; &amp;amp;response;

 // Теперь читаем до конца
 while (boost::asio::read(socket, response, 
 boost::asio::transfer_at_least(1), error))
 std::cout &amp;lt;&amp;lt; &amp;amp;response;

 if (error != boost::asio::error::eof) // ошибка
 throw boost::system::system_error(error);
 }
 catch (std::exception&amp;amp; e) // возникло исключение
 {
 std::cout &amp;lt;&amp;lt; &amp;quot;Exception: &amp;quot; &amp;lt;&amp;lt; e.what() &amp;lt;&amp;lt; &amp;quot;\n&amp;quot;;
 }

 return 0;
}&lt;/pre&gt;
  &lt;p&gt;Вот эти 100 строк кода работают как HTTP-клиент и позволяют нам читать страницы с веб-серверов. &lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Асинхронное программирование&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Этот раздел глубоко разбирает некоторые вопросы, с которыми вы столкнетесь при работе с асинхронным программированием. &lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Необходимость работать асинхронно&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;как правило, синхронное программирование гораздо проще, чем асинхронное. Потому что гораздо легче думать линейно (вызываем функцию А, после ее окончания вызываем ее обработчик, вызываем функцию В, после ее окончания вызываем ее обработчик и так далее, так что можно думать в манере событие-обработчик). В последнем случае вы можете иметь, скажем, пять событий и вы никогда не сможете узнать порядок, в котором они выполняются, и вы даже не будете знать выполнятся ли они все! Но даже при том, что асинхронное программирование сложнее вы, скорее всего, предпочтете его, скажем, в написании серверов, которые должны иметь дело с большим количеством клиентов одновременно. Чем больше клиентов у вас есть, тем легче асинхронное программирование по сравнению с синхронным. Скажем, у вас есть приложение, которое одновременно имеет дело с 1000 клиентами, каждое сообщение от клиента серверу и от сервера клиенту заканчивается символом ‘\n’. &lt;strong&gt;Синхронный код, 1 поток:&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;using namespace boost::asio;
struct client 
{
	ip::tcp::socket sock;
	char buff[1024]; // each msg is at maximum this size
	int already_read; // how much have we already read?
};
std::vector&amp;lt;client&amp;gt; clients;
void handle_clients() 
{
	while ( true)
		for ( int i = 0; i &amp;lt; clients.size(); ++i)
			if ( clients[i].sock.available() ) on_read(clients[i]);
}
void on_read(client &amp;amp; c) 
{
	int to_read = std::min( 1024 - c.already_read, c.sock.
		available());
	c.sock.read_some( buffer(c.buff + c.already_read, to_read));
	c.already_read += to_read;
	if ( std::find(c.buff, c.buff + c.already_read, &amp;#x27;\n&amp;#x27;) &amp;lt; c.buff + c.already_read) 
	{
		int pos = std::find(c.buff, c.buff + c.already_read, &amp;#x27;\n&amp;#x27;) - c.buff;
		std::string msg(c.buff, c.buff + pos);
		std::copy(c.buff + pos, c.buff + 1024, c.buff);
		c.already_read -= pos;
		on_read_msg(c, msg);
	}
}
void on_read_msg(client &amp;amp; c, const std::string &amp;amp; msg) 
{
	// analyze message, and write back
	if ( msg == &amp;quot;request_login&amp;quot;)
		c.sock.write( &amp;quot;request_ok\n&amp;quot;);
	else if ...
}&lt;/pre&gt;
  &lt;p&gt;Одна вещь, которую вы хотите избежать при написании серверов (да и в основном любого сетевого приложения) это чтобы код перестал отвечать на запросы. В нашем случае мы хотим, чтобы функция &lt;code&gt;handle_clients()&lt;/code&gt; блокировалась как можно меньше. Если функция заблокируется в какой-либо точке, то все входящие сообщения от клиента будут ждать, когда функция разблокируется и начнет их обработку. Для того чтобы оставаться отзывчивым мы будем читать из сокета только тогда, когда в нем есть данные, то есть &lt;code&gt;if ( clients[i].sock.available() ) on_read(clients[i])&lt;/code&gt;. В on_read мы будем читать только столько, сколько есть в наличии; вызов &lt;code&gt;read_until(c.sock, buffer(...),&amp;#x27;\n&amp;#x27;)&lt;/code&gt; было бы не очень хорошей идеей, так как она блокируется, пока мы не прочитаем сообщение от конкретного клиента до конца (мы никогда не узнаем когда это произойдет). &lt;/p&gt;
  &lt;p&gt;Узким местом здесь является функция &lt;code&gt;on_read_msg()&lt;/code&gt;; все входящие сообщения будут приостановлены, до тех пор, пока выполняется эта функция. Хорошо-написанная функция &lt;code&gt;on_read_msg()&lt;/code&gt; будет следить, чтобы этого не произошло, но все же это может произойти (иногда запись в сокет может быть заблокирована, например, если заполнен его буфер).&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Синхронный код, 10 потоков:&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;using namespace boost::asio;
struct client
{
	// ... same as before
	bool set_reading() 
	{
		boost::mutex::scoped_lock lk(cs_); 
		if ( is_reading_) return false; // already reading
		else { is_reading_ = true; return true; }
	}
	void unset_reading()
	{
		boost::mutex::scoped_lock lk(cs_); 
		is_reading_ = false;
	}
private:
	boost::mutex cs_;
	bool is_reading_;
};
std::vector&amp;lt;client&amp;gt; clients;
void handle_clients() 
{
	for ( int i = 0; i &amp;lt; 10; ++i)
		boost::thread( handle_clients_thread);
}
void handle_clients_thread() 
{
	while ( true)
		for ( int i = 0; i &amp;lt; clients.size(); ++i)
			if ( clients[i].sock.available() ) 
				if ( clients[i].set_reading()) 
				{
					on_read(clients[i]); 
					clients[i].unset_reading();
				}
}
void on_read(client &amp;amp; c)
{
	// same as before
}
void on_read_msg(client &amp;amp; c, const std::string &amp;amp; msg) 
{
	// same as before
}&lt;/pre&gt;
  &lt;p&gt;Для того, чтобы использовать несколько потоков, нам нужно их синхронизировать, что и делают функции &lt;code&gt;set_reading&lt;/code&gt;() и &lt;code&gt;set_unreading()&lt;/code&gt;. Функция &lt;code&gt;set_reading()&lt;/code&gt; является очень важной. Вы хотите, чтобы «проверить можно ли читать и начать читать» выполнялось за один шаг. Если у вас это выполняется за два шага («проверить можно ли читать» и «начать чтение»), то вы можете завести два потока: один для проверки на чтение для какого-либо клиента, другой для вызова функции &lt;code&gt;on_read&lt;/code&gt; для того же клиента, в конечном итоге это может привести к повреждению данных и возможно даже к зависанию системы.&lt;/p&gt;
  &lt;p&gt;Вы заметите, что код становится все более сложным. Возможен и третий вариант для синхронного кода, а именно иметь по одному потоку на каждого клиента. Но так как число одновременных клиентов растет, то это в значительной степени становится непозволительной операцией. А теперь рассмотрим асинхронные варианты. Мы постоянно делали асинхронной операцию чтения. Когда клиент делает запрос, вызывается операция &lt;code&gt;on_read&lt;/code&gt;, мы отвечаем в ответ, а затем ждем, когда поступит следующий запрос (запускаем еще одну операцию асинхронного чтения).&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Асинхронный код, 10 потоков:&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;using namespace boost::asio;
io_service service;
struct client
{
	ip::tcp::socket sock;
	streambuf buff; // reads the answer from the client
}
std::vector&amp;lt;client&amp;gt; clients;
void handle_clients() 
{
	for ( int i = 0; i &amp;lt; clients.size(); ++i)
		async_read_until(clients[i].sock, clients[i].buff, &amp;#x27;\n&amp;#x27;, boost::bind(on_read, clients[i], _1, _2));
	for ( int i = 0; i &amp;lt; 10; ++i)
		boost::thread(handle_clients_thread);
}
void handle_clients_thread() 
{
	service.run();
}
void on_read(client &amp;amp; c, const error_code &amp;amp; err, size_t read_bytes) 
{
	std::istream in(&amp;amp;c.buff);
	std::string msg;
	std::getline(in, msg);
	if ( msg == &amp;quot;request_login&amp;quot;)
		c.sock.async_write( &amp;quot;request_ok\n&amp;quot;, on_write);
	else if ...
		...
		// now, wait for the next read from the same client
		async_read_until(c.sock, c.buff, &amp;#x27;\n&amp;#x27;, boost::bind(on_read, c, _1, _2));
}&lt;/pre&gt;
  &lt;p&gt;Обратите внимание, насколько проще стал код. Структура client имеет только два члена, handle_clients() просто вызывает async_read_until, а затем создает десять потоков, каждый из которых вызывает service.run(). Эти потоки будут обрабатывать все операции асинхронного чтения или записи клиенту. Еще одно нужно отметить, что функция on_read() будет постоянно готовиться к следующей операции асинхронного чтения (смотрите последнюю строку).&lt;br /&gt;&lt;/p&gt;

</content></entry><entry><id>mrnimda:rk5_8HvCr</id><link rel="alternate" type="text/html" href="https://teletype.in/@mrnimda/rk5_8HvCr?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=mrnimda"></link><title>Методы отладки и тестирования программы</title><published>2019-12-18T06:25:54.432Z</published><updated>2019-12-20T05:29:07.185Z</updated><summary type="html">&lt;img src=&quot;https://teletype.in/files/1a/96/1a960ea9-226d-4354-9ae1-888dc113ae41.png&quot;&gt;Теоретические сведения</summary><content type="html">
  &lt;p&gt;&lt;strong&gt;Теоретические сведения&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Ошибки, сделанные в программе, можно разделить на два типа: синтакси­ческие и семантические. Синтаксические ошибки возникают при наруше­нии правил записи программы на выбранном языке программирования. Со­временные компиляторы хорошо выявляют такие ошибки и выводят на экран толковые сообщения о типе ошибки и месте ее обнаружения. Эти сообщения позволяют быстро найти место ошибки и исправить ее. Дело несколько осложняется тремя обстоятельствами.&lt;/p&gt;
  &lt;p&gt;Во-первых, компилятор указывает не ту строку исходного текста програм­мы, в которой произошла ошибка, а ту, в которой она проявилась. Эта строка может оказаться гораздо ниже, чем та, на которой сделана ошибка. Например, если вы забудете закончить описание класса на языке С++ точ­кой с запятой, как в следующем примере, то компилятор будет считать, что описание продолжается, и сообщит об ошибке только тогда, когда в этом &amp;quot;продолжении&amp;quot; возникнет какое-нибудь противоречие. Оно может про­явиться спустя несколько десятков строк исходного текста, и вы будете ло­мать голову, недоумевая, какая же в этом месте может быть ошибка. Нужен некоторый опыт, чтобы сообразить, что ошибка находится гораздо выше.&lt;/p&gt;
  &lt;pre&gt;class А{ int п;
public:
A(int n){ this.n = n;
}
}
class B{ } ;&lt;/pre&gt;
  &lt;p&gt;Во-вторых, ошибка может произойти по разным причинам, компилятор же укажет только одну. Иногда компилятор честно сообщает, что не может определить причину ошибки, написав просто: &amp;quot;Syntax error&amp;quot;. В большинстве случаев он указывает наиболее часто встречающуюся, по мнению разработ­чиков компилятора, ошибку, в вашей программе может оказаться совсем другая, поэтому принимайте сообщения компилятора не как истину, а толь­ко как один из возможных вариантов.&lt;/p&gt;
  &lt;p&gt;В-третьих, некоторые ошибки компилятор не в состоянии обнаружить. К ним относится деление на ноль или переполнение в арифметических выражениях, отрицательное или слишком большое значение индекса массива, и другие ошибки, возникающие в процессе вычислений. Такие ошибки проявляются уже на этапе выполнения (run time) программы и сообщает о них не компиля­тор, а исполняющая система. Их следует учитывать уже при проектировании, включая в программу обработку исключительных ситуаций.&lt;/p&gt;
  &lt;p&gt;Синтаксические ошибки обнаруживаются и устраняются довольно легко после нескольких запусков программы. Без исправления этих ошибок про­грамма просто не будет работать. Гораздо труднее исправить семантические ошибки — ошибки в алгоритме работы программы.&lt;/p&gt;
  &lt;p&gt;Часто встречающаяся семантическая ошибка — неправильная расстановка скобок в выражении. Компилятор может проверить парность скобок, но если число открывающих скобок совпадает с числом закрывающих скобок, то компилятор не может сказать, в нужных ли местах они стоят. Здесь надо быть особенно внимательным и отдавать себе отчет в том, что вы хотите вычислить. Например, переменная х может получить значение, вычислен­ное следующим оператором:&lt;/p&gt;
  &lt;pre&gt;double х = 2 * (а - Ь) / (а + Ь) + к * 5 * (а + Ь) / (а * Ь) ;&lt;/pre&gt;
  &lt;p&gt;Но значение х вполне можно вычислить и таким оператором:&lt;/p&gt;
  &lt;pre&gt;double х = 2 * (а - b) / (а + Ь) + к * (5 * а + Ь) / (а * Ь) ;&lt;/pre&gt;
  &lt;p&gt;Компилятору обе записи &amp;quot;кажутся&amp;quot; правильными. Выбор того или иного оператора зависит от задачи, в которой вычисляется переменная х.&lt;/p&gt;
  &lt;p&gt;Еще одна распространенная ошибка, не обнаружимая компилятором, воз­никает из-за неправильной расстановки или отсутствия фигурных скобок. &lt;/p&gt;
  &lt;p&gt;В следующем примере, очевидно, переменная s, в которой накапливается сумма, должна изменяться при каждой итерации цикла, но компилятор не может &amp;quot;понять&amp;quot;, что, фактически, она меняется только один раз: после вы­полнения цикла.&lt;/p&gt;
  &lt;pre&gt;int m, S = 0;
for (int k = 0; k &amp;lt; a.sizeO - 1; k++) m = a[k + 1] - a[k]; s += 2 * m * m - 3 * m + 2;&lt;/pre&gt;
  &lt;p&gt;Чтобы избежать таких ошибок, специалисты рекомендуют записывать тело любого составного оператора в фигурных скобках.&lt;/p&gt;
  &lt;p&gt;Семантические ошибки выявить гораздо труднее, чем синтаксические. По­скольку компилятор не может их заметить, он не выдает никаких сообще­ний. Программа может долго работать, не попадая в условия, в которых проявляются семантические ошибки. Бывает, что ошибочные результаты появляются только после нескольких месяцев эксплуатации программы. Поэтому для выявления таких ошибок зачастую приходится прикладывать специальные усилия, а в проекте необходимо предусматривать специальную фазу отладки и тестирования.&lt;/p&gt;
  &lt;p&gt;Процесс отладки проходит несколько этапов. Ошибку сначала надо обнару­жить. Потом найти место ее появления, как говорят, локализовать ошибку. После этого ошибку надо устранить. Рассмотрим подробнее каждую стадию отладки.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Обнаружение ошибки&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Большинство синтаксических ошибок обнаружи­вается компилятором или выявляется сразу же после первых запусков про­граммы. Их нетрудно обнаружить и исправить. Как правило, трудности вы­зывает обнаружение семантических ошибок. О них обычно и идет речь, когда говорят об отладке.&lt;/p&gt;
  &lt;p&gt;После того как очевидные ошибки устранены, и программа при запуске не сообщает об ошибках, могут возникнуть следующие ситуации:&lt;/p&gt;
  &lt;p&gt;1.	Программа не дает никаких результатов. Это происходит чаще всего в результате зацикливания или ожидания какого-то события, которое по разным причинам не может наступить.&lt;br /&gt;2.	Программа дает неверные результаты. Самый распространенный вари-ант, возникающий из-за ошибок в алгоритме или из-за ошибок в коди-ровании алгоритма.&lt;br /&gt;3.	Программа дает правдоподобные результаты. Это самый опасный слу-чай, поскольку получаемые значения можно принять за верные резуль-таты и передать в эксплуатацию ошибочную программу.&lt;br /&gt;4. Программа работает правильно. Почти фантастика. Рассмотрим подробнее каждую ситуацию.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Программа не дает результатов&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;В этой ситуации надо, прежде всего, добиться все-таки от программы хоть какой-то выдачи. Это достигается добавлением в программу функций выво­да printf(), fprintf о, puts() или других функций, выводящих промежу­точные значения всех или наиболее важных переменных. Они образуют так называемую отладочную печать. Это название появилось в эпоху телетай­пов, когда результаты выводились на бумажную ленту. Сейчас отладочные сведения выводятся на экран дисплея или записываются в файл, но слово &amp;quot;печать&amp;quot; осталось в лексиконе программистов, означая выдачу результатов на какое-нибудь устройство вывода.&lt;/p&gt;
  &lt;p&gt;Что входит в отладочную печать? Прежде всего, надо распечатать исходные данные, чтобы убедиться, что ввод происходит правильно. Затем надо про­верить начало и окончание каждого цикла, распечатав начальные значения цикла и значения, полученные по окончании цикла. Печать внутри цикла обычно не делается, она будет выполняться при каждом повторении цикла и приведет к огромной выдаче, в которой будет трудно что-либо понять.&lt;/p&gt;
  &lt;p&gt;Отладочная печать ставится перед разветвлениями, а также в начало каждой ветви, чтобы убедиться в правильном прохождении условных операторов.&lt;/p&gt;
  &lt;p&gt;Очень много ошибок возникает при передаче аргументов в функции. Ком­пилятор работает с каждой функцией отдельно, у него нет сведений о дру­гих функциях, и он не может проверить правильность фактических пара­метров. Поэтому отладочная печать ставится в начале каждой функции, чтобы проверить правильность полученных ею аргументов. Кроме того, нужно распечатывать значение, возвращенное функцией, чтобы убедиться в точности ее работы.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Программа дает неверные результаты&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Эта ситуация встречается чаще всего. Она может возникнуть по самым раз­ным причинам. К ней могут привести и простая ошибка в наборе текста программы, и неверный алгоритм решения задачи. Например, вы можете случайно исказить оператор присваивания:&lt;/p&gt;
  &lt;pre&gt;double х = 3.45 * а[к - 1] + 2.5 * а[к + 1];&lt;/pre&gt;
  &lt;p&gt;и написать, поставив вместо десятичной точки звездочку:&lt;/p&gt;
  &lt;pre&gt;double х = 3.45 * а[к - 1] + 2*5 * а[к + 1];&lt;/pre&gt;
  &lt;p&gt;Из-за этой мало заметной ошибки значение переменной х изменится в четыре раза.&lt;/p&gt;
  &lt;p&gt;Такие ошибки легко обнаружить, но очень трудно локализовать. Зачастую при­ходится делать полную трассировку программы, поставив отладочную печать после почти каждого оператора, тем самым тщательно отслеживая изменения переменных. Трассировкой не следует злоупотреблять: в результате выдается масса информации, которую трудно проанализировать. Поэтому на практике к этому методу прибегают только в самых крайних случаях.&lt;/p&gt;
  &lt;p&gt;Гораздо чаще для обнаружения ошибки в программу устанавливаются кон­трольные точки (breakpoints), в те места, где ошибки наиболее вероятны. Программа останавливается на каждой контрольной точке. После останова можно просмотреть значения переменных и продолжить выполнение про­граммы до следующей контрольной точки. Многие инструментальные сред­ства программирования позволяют даже изменить переменные в контроль­ной точке и продолжить выполнение программы с другими значениями.&lt;/p&gt;
  &lt;p&gt;Еще одно средство поиска ошибок — пошаговое выполнение программы, при котором она приостанавливается после выполнения каждого оператора. После останова можно просмотреть промежуточные значения переменных и выпол­нить следующий оператор. Разумеется, полное пошаговое выполнение всей программы займет слишком много времени. Поэтому его обычно начинают с какой-либо контрольной точки и прекращают, как только ошибка найдена.&lt;/p&gt;
  &lt;p&gt;Еще труднее найти ошибки в алгоритме решения задачи. Они могут накап­ливаться понемногу от оператора к оператору. По отладочной печати трудно отследить момент появления таких ошибок. Пошаговое выполнение про­граммы тоже не поможет найти ее. В таком случае надо проверять сам алго­ритм и убеждаться в его правильности. &lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Программа дает правдоподобные результаты&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Это самая коварная ситуация. Получив результаты, близкие к предполагае­мым, вы можете подумать, что программа работает правильно, и передать ее в эксплуатацию.&lt;/p&gt;
  &lt;p&gt;Как убедиться в том, что результаты правильны, а не правдоподобны? Здесь помогает тестирование программы. Тщательно подобранные тесты позволят отличить правдоподобные значения от правильных результатов. Как прави­ло, одного набора тестов недостаточно для убедительного доказательства неверности работы программы. Специалисты-тестировщики всегда подби­рают несколько наборов тестов так, чтобы точнее выявить дефекты про­граммы.&lt;/p&gt;
  &lt;p&gt;Выявить правдоподобные результаты часто помогает прием, заимствован­ный из математики. Обычно мы проверяем правильность вычисления корня уравнения, подставляя его в уравнение и убеждаясь в том, что левая часть уравнения равна правой его части. Тем не менее, математикам давно из­вестно, что из того, что левая часть уравнения, при подстановке в него при­ближенно вычисленного корня, почти совпадает с правой частью, вовсе не вытекает, что этот корень близок к настоящему корню уравнения.&lt;/p&gt;
  &lt;p&gt;Особенность таких некорректных задач заключается в том, что небольшое изменение исходных данных приводит к сильному изменению результата, хотя такой сильный скачок не вытекает из условий задачи. Это дает способ проверки правильности правдоподобных результатов. При тестировании программы надо провести серию испытаний с близкими значениями исход­ных данных и следить за тем, как меняются результаты ее работы. Неоправ­данно сильное изменение результатов при незначительном изменении дан­ных должно вызвать сомнение в правильности вычислений, если только это не вытекает из условий задачи.&lt;/p&gt;
  &lt;p&gt;Локализация ошибки&lt;/p&gt;
  &lt;p&gt;После обнаружения ошибки надо найти место ее возникновения, как гово­рят, локализовать ошибку. Это не такая простая задача, как кажется пона­чалу. Ошибка может быть сделана где-то в начале исходного текста про­граммы, а обнаружена позже, спустя несколько десятков строк исходного текста или даже в другой функции. Для локализации приходится делать об­ратный просмотр исходного текста программы, начиная от места обнаруже­ния ошибки. В этом помогает трассировка программы, расстановка контрольных точек и пошаговое выполнение. Контрольная точка устанав­ливается в некотором отдалении от места обнаружения ошибки, в том мес­те, где по вашим соображениям может начаться неправильное выполнение программы. После этого тщательно проверяется участок программы от кон­трольной точки до места обнаружения ошибки.&lt;/p&gt;
  &lt;p&gt;Иногда такое обратное отслеживание хода выполнения программы не помо­гает. Тогда приходится прослеживать ее работу по исходному тексту, просматривая выполнение операторов со значениями, полученными из кон­трольной печати, и доходя до места обнаружения ошибки. Такое про­слеживание часто называют прокруткой программы. Прокрутка програм­мы — трудоемкое занятие, ее лучше проводить на небольшом участке программы, сузив предварительно участок прокрутки с помощью отладоч­ной печати.&lt;/p&gt;
  &lt;p&gt;Когда и прокрутка не помогает, приходится проверять логику выполнения программы. Проверка начинается с места обнаружения ошибки. Просмат­ривается полученная в этой точке отладочная печать, и проводятся рассуж­дения вроде следующих: &amp;quot;Такие результаты мог дать оператор А или опера­тор В. Но оператор В в этом случае не может выполняться. Значит, посмотрим на оператор А. Оператор А дал значение С, а оно могло полу­читься только в ситуации D. Чтобы возникла ситуация D, надо, чтобы...&amp;quot;. На каждом логическом шаге приходится делать отладочную печать и не­большую прокрутку. Привлечение логических рассуждений помогает быст­рее проделать эту трудную работу, поскольку концентрирует внимание только на тех переменных, которые могут получить ошибочные значения, а таких переменных немного.&lt;/p&gt;
  &lt;p&gt;Наконец, каждый программист знает, что при попытке объяснить ошибку, которая никак не поддается локализации, своему коллеге по работе или по­стороннему консультанту, неожиданно, в процессе рассказа, самому автору программного кода вдруг становится ясно, в чем заключается ошибка. Ино­гда даже письменное изложение проблемы помогает найти ее решение. Это объясняется тем, что при последовательном изложении проблемы мысли приходят в порядок, выстраивается логическая цепочка, приводящая к ре­шению задачи. Поэтому очень важно периодически обсуждать возникающие проблемы всей командой разработчиков. Такие обсуждения не только по­зволяют устранять трудности, возникающие в процессе разработки, но и держат программистов в курсе всех задач, решаемых на каждом этапе реали­зации проекта.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Устранение ошибки&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;После того как ошибка обнаружена и найдено место ее возникновения, на­ступает время исправления программы. Некоторые ошибки, такие как не­правильно записанные выражения, неверно определенные начальные зна­чения переменных, неправильные входы и выходы из циклов, ошибки в логических условиях, исправить легко. Для этого достаточно изменить один или несколько операторов в исходном коде программы. После этого надо снова запустить те же наборы тестов, чтобы убедиться в том, что ошибки исчезли.&lt;/p&gt;
  &lt;p&gt;Другие ошибки, например, те, которые возникают из-за неправильно запро­граммированного алгоритма, исправить сложнее. Их устранение требует из­менения больших кусков программного кода. Такие изменения зачастую вносят в программу новые ошибки. Эти ошибки снова надо обнаруживать, локализовать и устранять. Процесс отладки становится циклическим. Его приходится повторять несколько раз. Как гласит шутливая аксиома отладки: &amp;quot;Каждая последняя ошибка является предпоследней&amp;quot;.&lt;/p&gt;
  &lt;p&gt;Чтобы не попасть в неприятный цикл отладки или быстрее выйти из него, следуйте простому правилу: &amp;quot;Исправлять за один раз только одну ошибку&amp;quot;. Тогда вы будете знать, из-за чего возникла новая ошибка, и сможете быстро устранить ее.&lt;/p&gt;
  &lt;p&gt;Труднее всего исправить ошибки алгоритма, заложенного в программу. Из­менение алгоритма часто влечет переработку структуры программы, введе­ние новых классов или значительное изменение существующих классов. Происходит возврат к этапу проектирования.&lt;/p&gt;
  &lt;p&gt;Наконец, не так уж редко встречается ошибка в определении требований к программе. Программа выдает не те результаты, которые ожидает заказчик программного продукта, да и вообще не то, что он хотел. Можно считать это не ошибкой, а недоговоренностью или недоразумением, но такое поло­жение надо исправлять. Для исправления приходится возвращаться к само­му первому этапу разработки и начинать все сначала, уточняя требования к программе.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Средства отладки&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Трудоемкость процесса отладки всегда вызывала стремление автоматизиро­вать его. С появлением первых компиляторов стали появляться и программы- отладчики (debuggers), называемые на жаргоне программистов &amp;quot;дебаггерами&amp;quot;. Они предоставляют программные средства для выполнения основных работ по отладке. С их помощью легко установить контрольные точки, сделать трассировку и пошаговое выполнение программы, просмотреть текущие зна­чения всех или выбранных переменных.&lt;/p&gt;
  &lt;p&gt;Отладчики тесно связаны с компиляторами. Для улучшения отладки компи­лятор может вставлять в машинный код дополнительную, отладочную, ин­формацию, которую отладчик использует при прогоне программы. Поэтому отладчики чаще всего поставляются вместе с компиляторами, в одной ин­тегрированной среде разработки IDE (Integrated Development Environment). В меню Options, или каком-нибудь другом меню, предназначенном для на­стройки параметров компилятора в такой интегрированной среде, можно выбрать один из нескольких режимов работы компилятора, в том числе от­ладочный режим, Debug, или окончательный режим, Release.&lt;/p&gt;
  &lt;p&gt;Во время разработки программы надо выбрать отладочный режим компиля­тора. Тогда можно будет использовать все средства отладчика, которые обычно перечислены в меню Debug интегрированной среды. После оконча­ния отладки, когда уже решено передавать программу в эксплуатацию, ком­пилятор надо перестроить на создание окончательной версии (release, build). В режиме Release компилятор удаляет из машинного кода отладочную ин­формацию и генерирует оптимизированный рабочий код.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p&gt;&lt;strong&gt;Тестирование&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Нельзя сказать, что программа, успешно прошедшая тестирование, свободна от ошибок. Тем не менее, после серьезного тести­рования можно запускать программу в промышленную эксплуатацию с большой долей уверенности. Практика показывает, что полностью протес­тированный программный продукт успешно справляется со своей задачей.&lt;/p&gt;
  &lt;p&gt;Тестирование не следует отождествлять с отладкой, хотя они тесно связаны. Задача тестирования — выявить наличие дефектов в программе, а задача отладки — отыскать их местоположение и устранить ошибки. Сначала с помощью тестирования надо найти ошибки, а потом, во время отладки, устранить их. Иногда тестирование и отладка выполняются одним специа­листом в одном процессе, чаще их осуществляют разные люди.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Unit-тестирование&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;По сути дела, тестированием своего участка программы занимается каждый разработчик программного обеспечения. Написав какой-либо класс, про­цедуру или несколько процедур, программист обязательно проверяет их работу на характерных для этого кода наборах данных. Появившаяся недавно методика unit-тестирования возводит эту привычку в абсолютное правило. Эта методика предписывает проводить тестирование после написания каждой процедуры или класса. Более того, unit-тестирование обязывает писать тесты еще до создания исходного кода программы!&lt;/p&gt;
  &lt;p&gt;Допустим, мы решили написать класс комплексных чисел complex. По ме­тодике unit-тестирования мы начинаем с того, что пишем пустой класс complex и вместе с ним сразу же пишем класс Testcomplex, который будет впоследствии содержать тесты.&lt;/p&gt;
  &lt;pre&gt;class Complex{};
class TestComplex{
Complex z; 
public:
    TestComplex(Complex z)
    { 
        this.z = z;
    }
    void runTest(){ }
};&lt;/pre&gt;
  &lt;p&gt;Весь этот код сразу компилируется, чтобы убедиться в правильности напи­санной конструкции. Затем начинаем разработку класса complex. В процес­се разработки каждый создаваемый метод класса complex записывается еще и в тестовый класс Testcomplex, например, следующим образом:&lt;/p&gt;
  &lt;pre&gt;#include &amp;lt;iostream&amp;gt;
class Complex
{
    double re, im; 
public:
    Complex(double a = 0.0, double b = 0.0)
    { 
         re = a; im = b;
    }
    double mod()
    {
         return sqrt(re * re + im * im) ;
    }
};
class TestComplex
{
Complex z; 
public:
     TestComplex(Complex z) 
     { 
         this.z = z;
     }
     void runTest(double result)
     {
         cout « testMod(result) « endl;
     }
     bool testMod(double result)
     { 
          return z.modO == result;
     }
};
void main()
{
     Complex zl(), z2(0.0, 1.0), z3(3.0, -4.0);
     TestComplex tl(zl), t2(z2), t3(z3); 
     tl.runTest(0.0); 
     t2.runTest(1.0); 
     t2.runTest(5.0);
}&lt;/pre&gt;
  &lt;p&gt;После компиляции и отладки этого кода сразу же начинается тестирование. Только после того, как все тесты выполнены успешно, продолжается созда­ние класса.&lt;/p&gt;
  &lt;p&gt;Такая методика программирования получила название программирования, управляемого тестами (TDP, test-driven programming). Она вошла обязатель­ной составной частью в быстро получившую популярность среди разработ­чиков свободного программного обеспечения методику экстремального про­граммирования ХР (extreme programming). Unit-тестирование первоначально возникло в технологии Java. Не удивительно, что и наибольшее развитие получил свободно распространяемый программный продукт JUnit, автома­тизирующий unit-тестирование Java-ioiaccoB. Для разработчиков, пишущих программы на языке С++, по аналогии с этим программным продуктом, создана библиотека классов, названная CppUnit.&lt;/p&gt;
  &lt;p&gt;Сейчас в распоряжении программистов есть множество программных продук­тов, облегчающих unit-тестирование. Более того, есть продукты, генерирующие наборы тестов. Например, фирмой Parasoft Corporation создан и распространя­ется программный продукт JTest, подготавливающий тесты для JUnit.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Методики тестирования&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;За полувековую историю развития компьютеров предложено множество ме­тодик тестирования. Самая простая рассматривает программу как &amp;quot;черный ящик&amp;quot;, внутренняя структура которого неизвестна. Испытатель проверяет работу &amp;quot;черного ящика&amp;quot;, подавая на его вход определенные сигналы и на­блюдая, как программа реагирует на них. Первым эту методику предложил, пожалуй, Козьма Прутков, сказавший: &amp;quot;Щелкни кобылу в нос, она махнет хвостом&amp;quot;.&lt;/p&gt;
  &lt;p&gt;По методике &amp;quot;черного ящика&amp;quot; каждый тест задает исходные данные програм­ме. Программа выполняется, после чего полученные результаты сравни­ваются с заранее известными тестовыми значениями. Такая методика пока­жет отсутствие ошибок только в том случае, когда набор тестов охватит все возможные исходные данные. Поскольку их необъятно много, всеобъемлю­щий охват исходных данных невозможен. Следовательно, теоретически нет уверенности, что такая методика выявит все ошибки. Кроме того, две ошибки могут взаимно уничтожаться на тестах и не будут выявлены по этой методике.&lt;/p&gt;
  &lt;p&gt;Другая методика обращается с программой как с &amp;quot;белым ящиком&amp;quot; или, дру­гими словами, &amp;quot;прозрачным ящиком&amp;quot;. Она учитывает исходный текст про��граммы. По этой методике набор тестов должен привести к выполнению каждого оператора программы и прохождению каждого из возможных путей выполнения программы хотя бы по одному разу. Эта методика хорошо про­веряет логику выполнения программы, но тоже не может выявить все ошибки. Например, некоторые ветви программы могут быть просто упуще­ны в процессе разработки, а такое тестирование не выявит их отсутствие.&lt;/p&gt;
  &lt;p&gt;Реальные методики тестирования используют оба подхода. Одни методики приближаются к &amp;quot;черному ящику&amp;quot;, другие — к &amp;quot;прозрачному ящику&amp;quot;. Так или иначе, в настоящее время практически во все наборы тестов включают­ся тесты, учитывающие исходный текст программы. Это значительно облег­чает тестирование.&lt;/p&gt;
  &lt;p&gt;Пусть, например, надо протестировать программу, находящую площадь тре­угольника по длинам его сторон а , b, с. Если мы знаем, что эта програм­ма вычисляет отдельно площадь прямоугольного треугольника как полупро­изведение его катетов, и отдельно площадь остальных треугольников по формуле Герона, то нам не надо создавать огромное количество тестов. Мы можем разбить все множество исходных данных а , b, с на два класса — образующих прямоугольный треугольник и не образующих его — и про­гнать всего по одному тесту для каждого класса.&lt;/p&gt;
  &lt;p&gt;По своему назначению тесты делятся на несколько групп:&lt;br /&gt;□	функциональное тестирование;&lt;br /&gt;□	тестирование обращений к базам данных;&lt;br /&gt;□	тестирование логики программы;&lt;br /&gt;□	нагрузочное тестирование;&lt;br /&gt;□	стрессовое тестирование;&lt;br /&gt;□	тестирование интерфейса пользователя;&lt;br /&gt;□	тестирование безопасности и прав доступа;&lt;br /&gt;□	тестирование инсталляции программного продукта. Рассмотрим подробнее каждую из этих групп тестов.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Функциональное тестирование&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Это основной вид тестирования, проверяющий соответствие программы функциональным требованиям, предъявленным к ней на самом первом эта­пе проектирования программного продукта. Функциональное тестирование призвано убедиться в правильности ввода исходных данных, обработки и вывода результатов, а также в правильности работы всех элементов управ­ления программой. Для этого проигрываются все сценарии использования программы (use case). При этом в тестах задаются и правильные и непра­вильные входные данные. Это позволяет проверить реакцию программы на ошибки ввода.&lt;/p&gt;
  &lt;p&gt;В большинстве функциональных тестов программа рассматривается как &amp;quot;чер­ный ящик&amp;quot;, поскольку проверяется работа программы, а не ее реализация. Тем не менее, как уже говорилось выше, знание алгоритма и исходного текста про­граммы может помочь в проверке правильного ее функционирования.&lt;/p&gt;
  &lt;p&gt;уравнение &lt;code&gt;ax + Ьх + с = 0&lt;/code&gt; с действительными коэффициентами. Для этого надо подготовить тестовый набор коэффициентов &lt;code&gt;а, b, с&lt;/code&gt;, учитывающих все возможные ситуации. Не зная о том, что решение уравнения зависит от знака дискриминанта, мы можем упустить один из тестов, приводящих к положительному или отрицательному дискриминанту. Зато, зная алгоритм нахождения корней, мы можем разбить все тестовые значения коэффициен­тов на те, которые дают неотрицательный дискриминант, и те, для которых дискриминант отрицателен. В каждой группе можно взять только один на­бор значений коэффициентов &lt;code&gt;а , b, с&lt;/code&gt;. Дополнив этот набор вырожденны­ми и граничными значениями, мы получим полный набор тестов. &lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://teletype.in/files/1a/96/1a960ea9-226d-4354-9ae1-888dc113ae41.png&quot; width=&quot;508&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Успешно выполнив этот набор, мы можем быть уверены, что обычный школьный алгоритм решения квадратного уравнения работает правильно. Конечно, такую уверенность дает только простота задачи, которую решает тестируемая программа. В реальных условиях убедиться в полноте набора тестов гораздо труднее.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Тестирование обращений к базам данных&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Этот набор тестов проверяет правильность связи программы с базами дан­ных, в которых она хранит информацию. Тесты должны проверить все ис­пользуемые программой обращения к базам данных. Их надо проделать как с верными данными, так и с ошибочными, чтобы убедиться в том, что база данных сохраняет целостность во всех случаях.&lt;/p&gt;
  &lt;p&gt;Это тестирование предполагает знание методов обращения к базе данных, примененных в программе, а также знание особенностей используемого программой сервера базы данных. Поэтому подход к программе, как к &amp;quot;чер­ному ящику&amp;quot; здесь не годится. Подготовка наборов тестов должна вестись со знанием использованных в программе связей с базой данных.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Тестирование бизнес-логики программы&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Тестирование деловых правил, заложенных в программу, призвано прове­рить правильность ее работы в течение определенного отчетного периода времени: дня, недели, месяца, квартала, года. Такие тесты должны за ко­роткое время пробежать расписание всего отчетного периода и проделать все действия и события за этот период. При этом особое внимание уделяет­ся функциям, обрабатывающим даты и интервалы времени. Для их провер­ки применяются как тесты, включающие правильные даты, так и тесты с неправильными датами.&lt;/p&gt;
  &lt;p&gt;Кроме того, в тесты закладывается составление отчетов и других документов, которые должна генерировать программа. Проверяется, происходит ли уве­домление пользователей по электронной почте о событиях, происходящих в системе, если, конечно, такие уведомления заложены в деловые правила.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Нагрузочное тестирование&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Эти тесты дают рабочую нагрузку на программу. Они должны смоделиро­вать реальную обстановку, в которой будет работать программа: многополь­зовательский режим, работу по сети, активное использование устройств ввода-вывода, принтеров и графопостроителей. Кроме того, нагрузочные тесты отслеживают реакцию программы на изменение нагрузки, время от­клика на события, быстроту связи с базами данных, удаленными источни­ками информации. Эти параметры должны находиться в пределах, опреде­ленных техническим заданием.&lt;/p&gt;
  &lt;p&gt;Нагрузочное тестирование должно проверить, как программа использует все необходимые ресурсы компьютера, и убедиться в том, что она правильно работает в заданных условиях.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Стрессовое тестирование&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Стрессовое тестирование заключается в том, чтобы дать предельные нагруз­ки на программу с целью проверить ее работу в условиях дефицита ресур­сов. Эти тесты должны проверить работу программы при недостатке опера­тивной памяти, превышении пропускной способности сетевых средств, переполнении каналов ввода-вывода, одновременного доступа большого количества пользователей и т. п. Успешное выполнение стрессовых тестов даст уверенность в том, что программа сохранит работоспособность при пе­регрузках.&lt;/p&gt;
  &lt;p&gt;Для программ, рассчитанных на быстрое восстановление после сбоев, стрессовое тестирование должно сымитировать такие сбои, чтобы проверить способность восстановления.&lt;/p&gt;
  &lt;p&gt;Стрессовые тесты полезны не только для выявления дефектов программы, но и для получения информации о реакции программы на пиковые нагруз­ки. Эту информацию затем можно включить в инструкцию по эксплуатации программы.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Тестирование интерфейса пользователя&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Тестирование интерфейса содержит наибольший объем ручной работы. Тес- тировщик должен проверить работу всех элементов управления, открыть все окна, имеющиеся в программе, пройти и опробовать все пункты меню. Ему надо попытаться проделать и всевозможные неправильные действия пользо­вателя, чтобы проверить реакцию программы на ошибки использования ин­терфейса.&lt;/p&gt;
  &lt;p&gt;Во время тестирования интерфейса следует вводить всевозможные значения во все поля ввода, чтобы проверить, отвечает ли их диапазон требованиям, предъявленным к программе. Необходимо вводить и невозможные значе­ния, например, в числовые поля вводить символьные данные, чтобы про­смотреть сообщения системы, сделанные в ответ на ввод неправильных данных.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Тестирование безопасности и прав доступа&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Подсистема безопасности требует отдельного тестирования. При этом про­веряются регистрация новых пользователей, локальный и удаленный доступ к системе, разграничение прав доступа к ней. Очень часто система строится так, что одни пользователи могут только просматривать доступную им ин­формацию, другие пользователи могут добавлять, редактировать и удалять ее, а третьи могут менять права пользователей первых двух категорий. Все это обязательно надо протестировать и проверить, правильно ли определя­ются права пользователей на те или иные действия в системе.&lt;/p&gt;
  &lt;p&gt;Для тестирования безопасности в системе создаются несколько временных пользователей с разными правами доступа. Тесты выполняются несколько раз от имени каждого из них. В наборе тестов следует предусмотреть все действия пользователя, имеющиеся в программе, и посмотреть, какие из них разрешены пользователю, а какие — нет.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Тестирование инсталляции программного продукта&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Первая функция программного продукта, с которой сталкивается пользова­тель, — это его установка на компьютер. Если установить продукт не удаст­ся, то потребитель откажется от него. Если при установке возникнут труд­ности, то пользователь потеряет доверие к продукту и к фирме — разработчику этого продукта. Поэтому необходимо тщательно протестиро­вать процесс инсталляции на разном оборудовании, обратив внимание на безошибочное выполнение повторной установки, обновления предыдущей версии, адаптации к разному оборудованию.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Наборы тестов&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Набор тестов (test suite) следует подготовить еще до проектирования про­граммного продукта, на этапе определения требований к нему. Именно тре­бования к программе и составляют круг тех задач, которые должны быть выражены в тестовых заданиях. Кроме того, спроектировав систему, уже трудно беспристрастно оценивать ее. Направление мысли разработчика во- лей-неволей будет идти в русле готового проекта, а это помешает всесторон­ней оценке проделанной работы. Поэтому подготовку тестов и последующее тестирование следует поручить не разработчикам проекта, а профессио­нально подготовленной команде специалистов-тестировщиков.&lt;/p&gt;
  &lt;p&gt;Как составить набор тестов? Это целиком зависит от тестируемой програм­мы и требований к ней. Здесь можно дать только самые общие рекоменда­ции. Во-первых, для каждой из перечисленных выше групп тестов надо составить свой набор тестов. Во-вторых, надо подобрать набор тестов, рабо­тающих с программой как с &amp;quot;черным ящиком&amp;quot;, рассмотрев все возможные области значений исходных данных. В-третьих, надо изучить исходный текст программы и подобрать тесты для прохождения всех путей алгоритма, заложенного в программу, и тесты для выполнения каждого оператора про­граммы.&lt;/p&gt;
  &lt;p&gt;Каждый отдельный тест, называемый специалистами тестовым случаем (test case), состоит из исходных данных, вводимых в тестируемый программный модуль, и предполагаемого результата, который должен получить модуль.&lt;/p&gt;
  &lt;p&gt;Результатом не обязательно должны быть значения, возвращаемые модулем. Это могут быть обновления базы данных, изменения в конфигурационных файлах, отправка сообщений по сети. Важно, чтобы это были четко отсле­живаемые значения, влияющие на ход выполнения программы.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Процесс тестирования&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Не следует думать, что тестирование завершает разработку и тестировать надо уже готовую программу. Напротив, этот процесс надо начинать на са­мых ранних стадиях разработки. Тестирование может выявить упущения в проектировании программного модуля, а такие упущения лучше устранить пораньше. Кроме того, известно, что исправление одних ошибок часто вно­сит другие. Хотя общее число ошибок уменьшается, после каждого исправле­ния приходится снова тестировать программу. Таким образом, тестирование вклинивается в процесс разработки. По этим причинам сейчас получила большую популярность методика непрерывного тестирования программы во время ее разработки. Она называется zero-defect mindset.&lt;/p&gt;
  &lt;p&gt;Согласно методике zero-defect mindset, все ошибки подразделяются на не­сколько уровней по своей грубости. Программист не имеет права добавлять новую функциональность в программу, пока он не исправит все крупные ошибки до определенного уровня. Допустимый уровень ошибки устанавли­вается для каждого этапа разработки, он повышается по мере завершения работы.&lt;/p&gt;
  &lt;p&gt;Итак, по всем современным методикам тестирование выполняется прямо в процессе разработки. При тестировании программы, состоящей из несколь­ких программных модулей, надо выбрать один из двух противоположных сти­лей тестирования: тестирование снизу вверх или тестирование сверху вниз.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Тестирование снизу вверх &lt;/strong&gt;начинает с отдельных подпрограмм: функций и процедур, не вызывающих другие подпрограммы. На их вход подаются тес­товые данные, результаты их работы сравниваются с тестовыми результата­ми. После того как тесты уже не выявляют ошибок в таких подпрограммах, тестировщик переходит к тестированию подпрограмм, вызывающих уже протестированные подпрограммы. При этом выявляются ошибки, возни­кающие при взаимодействии подпрограмм. Затем тестируются подпрограм­мы, вызывающие только что протестированные подпрограммы и т. д. Нако­нец, в последнюю очередь проверяется работа головного модуля всей программы.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Тестирование сверху вниз&lt;/strong&gt;, напротив, начинает сразу с готового прототипа головного модуля программы. Вместо подпрограмм, вызываемых из голов­ного модуля, подставляются заглушки (stubs), выдающие головному модулю заранее определенные значения. Убедившись в правильности работы такого скелета всей программы, тестировщик начинает по очереди заменять за- глушки настоящими подпрограммами или их прототипами, пока не дойдет до самых мелких программных единиц, уже не вызывающих никакие другие подпрограммы.&lt;/p&gt;
  &lt;p&gt;На практике тестировщики редко следуют в чистом виде тому или иному стилю тестирования. Чаще всего одновременно тестируются и отдельные программные единицы, и прототип головного модуля, а окончательная сборка всей программы происходит где-то посередине. В некоторых случаях задача ставится таким образом, что надо протестировать сразу и головной модуль, и какие-то отдельные подпрограммы. В других случаях те или иные модули и программные единицы просто не готовы к тестированию.&lt;/p&gt;
  &lt;p&gt;Какова бы ни была методика тестирования, сразу же после исправления ошибки прогоняется тот же набор тестов, с помощью которого была найде­на ошибка. Это так называемое регрессионное тестирование, позволяет убе­диться в том, что ошибка исправлена. Если регрессионное тестирование не выявило дефектов в программе, то этот набор тестов можно отложить и пе­рейти к следующему этапу тестирования.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Особенности тестирования объектно-ориентированных программ&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Объектно-ориентированный подход к программированию накладывает свой отпечаток и на методику тестирования. Вместо тестирования функций, про­цедур и прочих программных модулей, производится тестирование классов, связей между ними и иерархий классов.&lt;/p&gt;
  &lt;p&gt;Количество классов, их строение и взаимодействие между ними определя­ются на стадии объектно-ориентированного проектирования. На этой ста­дии создаются диаграммы классов и диаграммы связей. Зачастую по этим диаграммам генерируется исходный программный код в виде абстрактных классов и/или интерфейсов. После этого уже можно провести полноценное тестирование полученной модели. Исправление ошибок на такой ранней стадии позволит значительно сэкономить время отладки и всей разработки в целом. Более того, тестирование поможет выяснить, соответствует ли мо­дель требованиям, предъявленным к программному продукту, и своевре­менно исправить проект.&lt;/p&gt;
  &lt;p&gt;Тестирование объектно-ориентированных программ чаще всего выполняет­ся снизу вверх. Вначале проверяется программный код методов класса и тестируются отдельные методы. Затем начинается тестирование класса.&lt;/p&gt;
  &lt;p&gt;Для тестирования класса пишется тестовая программа, в которой создаются объекты класса с разными значениями его полей. Тестовая программа про­веряет выполнение контрактов методами классов. Для этого она обращается ко всем методам класса и отслеживает результаты их выполнения. При про­верке работы методов, обращающихся к другим объектам, создаются объек- ты-заглушки, содержащие только поля и методы, нужные для тестирования основного объекта. Поля объекта-заглушки получают определенные, хоро­шо узнаваемые значения. Методы объекта-заглушки очень просты, они только сигнализируют каким-нибудь образом обо всех обращениях к ним.&lt;/p&gt;
  &lt;p&gt;Тестирование шаблонов классов требует особого внимания. Здесь легко допустить ошибку и трудно обнаружить ее. Разработчик должен в точности знать, какой класс создаст компилятор по шаблону, написанному им, а для этого нужен большой опыт работы с этим компилятором. Тестировщик то­же должен знать особенности компилятора, чтобы создать тесты для каждо­го класса, создаваемого по шаблону.&lt;/p&gt;
  &lt;p&gt;После того как тестирование отдельных классов уже не выявляет ошибок в них, создаются объекты разных классов, и проверяется их взаимодействие. Выстраивается иерархия классов и тестируется правильность наследования в этой иерархии. Здесь удобнее стиль тестирования сверху вниз, от базовых классов к порожденным классам, от вершины иерархии классов к самым конкретным классам. Для тестирования абстрактных классов специально создаются его реализации-заглушки.&lt;/p&gt;
  &lt;p&gt;Особую трудность вызывает тестирование классов, использующих поли­морфизм. Каждый объект такого класса обладает своим поведением, отлич­ным от поведения других объектов того же типа. Тип объекта маскируется, класс полиморфного объекта трудно определить, поэтому нелегко проверить контракт объекта, тем более что в процессе разработки такие объекты могут легко заменяться другими объектами того же типа. При составлении тестов приходится учитывать будущее поведение полиморфного объекта, а для это­го надо хорошо знать разрабатываемый проект.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Средства тестирования&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Процесс автоматизации коснулся и тестирования. Множество фирм занима­ется выпуском программных продуктов, предназначенных для тестирования. Наиболее известны компании: Parasoft Corporation, TestQuest, Optimyz Soft­ware, Segue Software, Compuware Coiporation, AutomatedQA Corporation. Ими и другими фирмами создано множество программных продуктов, облег­чающих и автоматизирующих этот процесс. Мы уже упоминали JUnit, JTest, CppUnit. К ним можно добавить программные продукты Rational Functional Center, Insure++, ITG Center, SilkPerformer, CARS. Это только малая часть средств автоматизации тестирования и генерации тестов.&lt;/p&gt;
  &lt;p&gt;Такое многообразие программных инструментов тестирования объясняется не только важностью и актуальностью самой задачи тестирования, но и тем, что эта задача сильно специализирована. Почти каждый новый программ­ный продукт ставит новые задачи тестировщикам. Для их решения прихо­дится разрабатывать новое средство автоматизации или значительно моди- фицировать имеющиеся средства. Некоторые из этих новых средств полу­чают дальнейшее развитие и распространение, другие завершают свой жиз­ненный цикл с завершением тестирования.&lt;/p&gt;

</content></entry><entry><id>mrnimda:rJ8jUlL0S</id><link rel="alternate" type="text/html" href="https://teletype.in/@mrnimda/rJ8jUlL0S?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=mrnimda"></link><title>Разработка сетевых приложений</title><published>2019-12-17T06:33:02.154Z</published><updated>2019-12-17T06:33:02.154Z</updated><summary type="html">&lt;img src=&quot;https://teletype.in/files/8f/41/8f414034-ef7a-4af0-876e-550118e1f0fc.png&quot;&gt;Модель OSI</summary><content type="html">
  &lt;p&gt;&lt;strong&gt;Модель OSI&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Чтобы понять все принципы взаимодействия компьютеров на расстоянии, надо знать так называемую модель OSI (ISO OSI == International Organization for Standardization Open System Interconnection - Взаимодействие Открытых Систем по Стандарту Международной Организации по Стандартизации).&lt;/p&gt;
  &lt;p&gt;Чтобы понять все принципы взаимодействия компьютеров на расстоянии, надо знать так называемую модель OSI (ISO OSI == International Organization for Standardization Open System Interconnection - Взаимодействие Открытых Систем по Стандарту Международной Организации по Стандартизации).&lt;/p&gt;
  &lt;p&gt;&lt;u&gt;Вот эти уровни:&lt;/u&gt;&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;7. Прикладной&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Это уровень, максимально приближенный к пользовательскому интерфейсу. Пользователи конечного программного продукта не волнует, как передаются данные, зачем и через какое место... Они сказали &amp;quot;ХОЧУ!&amp;quot; - а мы, программисты, должны им это обеспечить. В качестве примера можно взять на рассмотрение любую сетевую игру: для игрока она работает на этом уровне. Пользователь куда то ткнул, в интерфейсной части программы зафиксирована его команда. Что надо передать? Что то приняли, что произошло в мире игры?&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;6. Представительский&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Здесь программист имеет дело с данными, полученными от низших уровней. В основном, это конвертирование и представление данных в удобоваримом для пользователя виде.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;5. Сеансовый&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Этот уровень позволяет пользователям осуществлять &amp;quot;сеансы связи&amp;quot;. То есть именно на этом уровне передача пакетов становится для программиста прозрачной, и он может, не задумываясь о реализации, непосредственно передавать данные, как цельный поток. Здесь на сцену вступают протоколы HTTP, FTP, Telnet, SMTP и т.д.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;4. Транспортный&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Осуществляет контроль над передачей данных (сетевых пакетов). То есть, проверяет их целостность при передаче, распределяет нагрузку и т.д. Этот уровень реализует такие протоколы, как TCP, UDP и т.д. Для нас представляет наибольший интерес.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;3. Сетевой&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Логически контролирует адресацию в сети, маршрутизацию и т.д. Должен быть интересен разработчикам новых протоколов и стандартов. На этом уровне реализованы протоколы IP, IPX, IGMP, ICMP, ARP. В основном, управляется драйверами и операционными системами. Сюда влезать, конечно, стоит, но только когда ты знаешь, что делаешь, и полностью в себе уверен.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;2. Канальный&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Этот уровень определяет, как биты собираются в пакеты, какую служебную информацию надо передавать и как на неё реагировать, но поле данных каждого пакета максимально сырое и представляет из себя просто биты в навал, нет вообще ни какой последовательности пакетов в длинных информационных потоках. В поле данных просто вложены пакеты сетевых протоколов, всё, что чего не хватает должно быть сделано в них, или ещё выше.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;1. Аппаратный (Физический)&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Контролирует передачи представление битов и служебных сигналов физическими процессами и отправку физических сигналов между аппаратными устройствами, входящими в сеть. То есть управляет передачей электронов по проводам. Нас он не интересует, потому что все, что находится на этом уровне, контролируется аппаратными средствами (реализация этого уровня - это задача производителей хабов, мультиплексоров, повторителей и другого оборудования). Мы не физики-радиолюбители, а геймдевелоперы.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p&gt;Итак, подведем небольшой итог к тому, что было представлено... Мы видим, что, чем выше уровень - тем выше степень абстракции от передачи данных, к работе с самими данными. Это и есть смысл всей модели OSI: поднимаясь все выше и выше по ступенькам ее лестницы, мы все меньше и меньше заботимся о том, как данные передаются, мы все больше и больше становимся заинтересованными в самих данных, нежели в средствах для их передачи. Каждый следующий уровень скрывает в себе предыдущий, облегчая жизнь пользователю этого уровня, будь он программист, радиоинженер или твоя подруга, которая не знает, как настроить MS Outlook Express...&lt;/p&gt;
  &lt;p&gt;Нас, как программистов, интересуют уровни 3, 4 и 5. Мы должны использовать средства, которые они предоставляют, для того чтобы построить 6 и 7 уровни, с которыми смогут работать конечные пользователи.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Сокеты &lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;У каждой современной операционной системы есть средства для взаимодействия с другими компьютерами. Самым распространенным среди программистов средством для упомянутых целей являются сокеты. Сокеты - это API (Application Programming Interface - Интерфейс Программирования Приложений) для работы с уровнями OSI. Сокеты настолько гибки, что позволяют работать почти с любым из уровней модели OSI. Хочешь - формируй IP-пакеты руками и займись хакингом, отправляя &amp;quot;неправильные&amp;quot; пакеты, которые будут вводить сервера в ступор, хочешь - займись более благоразумным делом и создай новый удобный голосовой чат, хочешь - игрульку по сети гоняй, не хочешь - твое право, но этот случай мы в данном руководстве не рассматриваем... :)&lt;/p&gt;
  &lt;p&gt;Когда мы создаем сокет (socket - гнездо), мы получаем возможность доступа к нужному нам уровню OSI. Ну а дальше мы можем использовать соответствующие вызовы для взаимодействия с ним. Когда мы создаем сокет, мы также заставляем систему организовать два канала: входящий (это как громкоговоритель у телефона) и исходящий (микрофон). Осуществляя чтение и запись в эти каналы, мы приказываем системе взять на себя дальнейшую судьбу данных, т.е. передать и проследить, чтоб данные дошли вовремя, в нужной последовательности, не искаженные и т.п. Система должна давать (и дает) максимум гарантий (для каждого уровня OSI - гарантии свои), что данные будут переданы правильно. Наша задача - поместить их в очередь, а на другом конце - прочитать из входящей очереди и обработать должным образом. В каждой ОС степень поддержки сокетов разная, но точно могу сказать: в современных операционных системах MS и *nix - сокеты поддерживаются настолько, насколько нам, они могут понадобиться.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Введение в Windows Sockets API C++&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Выделяют клиентские и слушающие (серверные) сокеты. Различия между ними очевидны: клиентские подключаются к процессу (к слушающему сокету), слушающие сокеты, соответственно, обрабатывают эти подключения. Передача данных между процессами происходит через клиентские сокеты.&lt;/p&gt;
  &lt;h4&gt;&lt;strong&gt;Общая схема работы с сокетами в Windows&lt;/strong&gt;&lt;/h4&gt;
  &lt;p&gt;&lt;strong&gt;Клиент&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;•	Инициализация WSA&lt;br /&gt;•	Создание сокета&lt;br /&gt;•	Присоединение к серверу&lt;br /&gt;•	Прием/передача данных&lt;br /&gt;•	Разрыв соединения&lt;br /&gt;&lt;strong&gt;Сервер&lt;/strong&gt;&lt;br /&gt;•	Инициализация WSA&lt;br /&gt;•	Создание слушающего сокета и привязка к порту&lt;br /&gt;•	Прослушивание порта&lt;br /&gt;•	Обработка входящих подключений&lt;br /&gt;•	Прием/передача данных&lt;br /&gt;•	Разрыв соединения&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Пример. Клиентское и серверное приложение&lt;/strong&gt;&lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://teletype.in/files/8f/41/8f414034-ef7a-4af0-876e-550118e1f0fc.png&quot; width=&quot;583&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;&lt;strong&gt;КЛИЕНТ:&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;#include &amp;lt; winsock2.h &amp;gt; 
#include &amp;lt; ws2tcpip.h &amp;gt;
#include &amp;lt; iostream &amp;gt;

#define ip &amp;quot;127.0.0.1&amp;quot;
#define port 27015
#define bufsize 256
#pragma comment(lib, &amp;quot;Ws2_32.lib&amp;quot;)

int main()
{
	setlocale(LC_ALL, &amp;quot;Russian&amp;quot;);

	//Инициализация WSA
	WSADATA wsaData;

	int result;
	result = WSAStartup(MAKEWORD(2, 2), &amp;amp;wsaData);
	if (result != 0)
	{
		std::cout &amp;lt;&amp;lt; &amp;quot;Ошибка WSAStartup: &amp;quot; &amp;lt;&amp;lt; result &amp;lt;&amp;lt; std::endl;
		return 1;
	}

	//Создание сокета
	SOCKET clientSocket = INVALID_SOCKET;
	clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (clientSocket == INVALID_SOCKET)
	{
		std::cout &amp;lt;&amp;lt; &amp;quot;Ошибка socket(): &amp;quot; &amp;lt;&amp;lt; WSAGetLastError() &amp;lt;&amp;lt; std::endl;
		WSACleanup();
		return 1;
	}

	//Присоединение к серверу
	sockaddr_in clientService;
	clientService.sin_family = AF_INET;
	clientService.sin_addr.s_addr = inet_addr(ip);
	clientService.sin_port = htons(port);
	result = connect(
		clientSocket,
		reinterpret_cast&amp;lt; SOCKADDR* &amp;gt;(&amp;amp;clientService),
		sizeof(clientService)
		);
	if (result != 0)
	{
		std::cout &amp;lt;&amp;lt; &amp;quot;Ошибка в connect(): &amp;quot; &amp;lt;&amp;lt; WSAGetLastError() &amp;lt;&amp;lt; std::endl;
		WSACleanup();
		return 1;
	}

	//Передача данных серверу
	char data[] = &amp;quot;Test&amp;quot;;
	result = send(clientSocket, data, static_cast&amp;lt; int &amp;gt;(strlen(data)), 0);
	if (result &amp;lt; 0)
	{
		std::cout &amp;lt;&amp;lt; &amp;quot;Ошибка в send(): &amp;quot; &amp;lt;&amp;lt; WSAGetLastError() &amp;lt;&amp;lt; std::endl;
		return 1;
	}

	//Прием данных от сервера
	char buf[bufsize];
	int r;
	do
	{
		r = recv(clientSocket, buf, bufsize, 0);
		if (r &amp;gt; 0)
			std::cout &amp;lt;&amp;lt; &amp;quot;Приянтно &amp;quot; &amp;lt;&amp;lt; r &amp;lt;&amp;lt; &amp;quot; байт&amp;quot; &amp;lt;&amp;lt; std::endl;
		else if (r == 0)
			std::cout &amp;lt;&amp;lt; &amp;quot;Соединение разорвано&amp;quot; &amp;lt;&amp;lt; std::endl;
		else
			std::cout &amp;lt;&amp;lt; &amp;quot;Ошибка в recv(): &amp;quot; &amp;lt;&amp;lt; WSAGetLastError() &amp;lt;&amp;lt; std::endl;
	} while (r &amp;gt; 0);
	
    //Разрыв соединения
	closesocket(clientSocket);
	// Если работа с сокетами больше не предполагается вызываем WSACleanup()
	WSACleanup();
	return 0;
}&lt;/pre&gt;
  &lt;p&gt;&lt;strong&gt;СЕРВЕР:&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;#include &amp;lt; winsock2.h &amp;gt; 
#include &amp;lt; ws2tcpip.h &amp;gt;
#include &amp;lt; iostream &amp;gt;

#define DEFAULT_PORT &amp;quot;27015&amp;quot;
#define DEFAULT_BUFLEN 512
#pragma comment(lib, &amp;quot;Ws2_32.lib&amp;quot;)

int main()
{
	setlocale(LC_ALL, &amp;quot;Russian&amp;quot;);

	//Инициализация WSA
	int iResult;
	WSAData d;
	iResult = WSAStartup(MAKEWORD(2, 2), &amp;amp;d);
	
    if (iResult != 0)
	{
		std::cout &amp;lt;&amp;lt; &amp;quot;Error at WSAStartup: &amp;quot; &amp;lt;&amp;lt; iResult;
		return 1;
	}
	
    //Подготовка данных для создания сокета
	struct addrinfo *result = NULL, *ptr = NULL, hints;
	ZeroMemory(&amp;amp;hints, sizeof (hints));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	hints.ai_flags = AI_PASSIVE;
	iResult = getaddrinfo(NULL, DEFAULT_PORT, &amp;amp;hints, &amp;amp;result);
	
    if (iResult != 0)
	{
		std::cout &amp;lt;&amp;lt; &amp;quot;Ошибка getaddrinfo: &amp;quot; &amp;lt;&amp;lt; iResult;
		WSACleanup();
		return 1;
	}
	
    //Создание сокета
	SOCKET listenSocket = INVALID_SOCKET;
	listenSocket = socket(result-&amp;gt;ai_family, result-&amp;gt;ai_socktype, result-&amp;gt;ai_protocol);
	
    if (listenSocket == INVALID_SOCKET)
	{
		std::cout &amp;lt;&amp;lt; &amp;quot;Error at socket(): &amp;quot; &amp;lt;&amp;lt; WSAGetLastError();
		freeaddrinfo(result);
		WSACleanup();
		return 1;
	}
	
    //Связывание сокета с сетвевым адресом
	iResult = bind(listenSocket, result-&amp;gt;ai_addr, result-&amp;gt;ai_addrlen);
	if (iResult == SOCKET_ERROR)
	{
		std::cout &amp;lt;&amp;lt; &amp;quot;Bind failed with error: &amp;quot; &amp;lt;&amp;lt; WSAGetLastError();
		freeaddrinfo(result);
		closesocket(listenSocket);
		WSACleanup();
		return 1;
	}
	
    freeaddrinfo(result);
	
    //Прослушивание подключений
	if (listen(listenSocket, SOMAXCONN) == SOCKET_ERROR)
	{
		std::cout &amp;lt;&amp;lt; &amp;quot;Listen failed with error: &amp;quot; &amp;lt;&amp;lt; WSAGetLastError();
		closesocket(listenSocket);
		WSACleanup();
		return 1;
	}
	
    //Обработка запроса на подключение
	SOCKET ClientSocket;
	ClientSocket = INVALID_SOCKET;
	ClientSocket = accept(listenSocket, NULL, NULL);
	if (ClientSocket == INVALID_SOCKET)
	{
		std::cout &amp;lt;&amp;lt; &amp;quot;Accept failed with error: &amp;quot; &amp;lt;&amp;lt; WSAGetLastError();
		closesocket(listenSocket);
		WSACleanup();
		return 1;
	}
	
    //Обмен данными
	char recvbuf[DEFAULT_BUFLEN];
	int iSendResult;
	int recvbuflen = DEFAULT_BUFLEN;
	do
	{
		// Принимаем данные от клиента
		iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
		if (iResult &amp;gt; 0)
		{
			std::cout &amp;lt;&amp;lt; &amp;quot;Принято &amp;quot; &amp;lt;&amp;lt; iResult &amp;lt;&amp;lt; &amp;quot; байт&amp;quot; &amp;lt;&amp;lt; std::endl;
			// Отправляем данные, принтые от клиента, обратно
			iSendResult = send(ClientSocket, recvbuf, iResult, 0);
			if (iSendResult == SOCKET_ERROR)
			{
				std::cout &amp;lt;&amp;lt; &amp;quot;Send failed with error: &amp;quot; &amp;lt;&amp;lt; WSAGetLastError();
				closesocket(ClientSocket);
				WSACleanup();
				return 1;
			}
			std::cout &amp;lt;&amp;lt; &amp;quot;Отправлено &amp;quot; &amp;lt;&amp;lt; iSendResult &amp;lt;&amp;lt; &amp;quot; байт&amp;quot;;
		}
		else if (iResult == 0)
			std::cout &amp;lt;&amp;lt; &amp;quot;Соединение закрыто...&amp;quot; &amp;lt;&amp;lt; std::endl;
		else
		{
			std::cout &amp;lt;&amp;lt; &amp;quot;Ошибка recv &amp;quot; &amp;lt;&amp;lt; WSAGetLastError();
			closesocket(ClientSocket);
			WSACleanup();
			return 1;
		}
	} while (iResult &amp;gt; 0);
}&lt;/pre&gt;

</content></entry><entry><id>mrnimda:SymaZ3nTB</id><link rel="alternate" type="text/html" href="https://teletype.in/@mrnimda/SymaZ3nTB?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=mrnimda"></link><title>Создание и использование DLL</title><published>2019-12-10T05:48:43.362Z</published><updated>2019-12-11T06:19:29.687Z</updated><summary type="html">&lt;img src=&quot;https://teletype.in/files/83/83c7b15b-d4ea-4ee2-945b-bc263a1ecd27.png&quot;&gt;При разработке программ часто оказывается, что разным программным приложениям требуются одни и те же объекты, их свойства методы, процедуры и функции. Например, почти все программы выводят информацию на экран и пользуются стандартными объектами интерфейса Windows (окна, кнопки, меню…) Было бы в высшей степени неразумно запихивать код отрисовки каждого такого элемента во все программы. </summary><content type="html">
  &lt;p&gt;При разработке программ часто оказывается, что разным программным приложениям требуются одни и те же объекты, их свойства методы, процедуры и функции. Например, почти все программы выводят информацию на экран и пользуются стандартными объектами интерфейса Windows (окна, кнопки, меню…) Было бы в высшей степени неразумно запихивать код отрисовки каждого такого элемента во все программы. &lt;/p&gt;
  &lt;p&gt;Таким образом, возникает задача разделения большой программы на отдельные независимые модули, каждый из которых содержит определенный набор процедур и функций. Процедуры и функции такого модуля может вызывать любая другая программа. Подобные модули получили название &lt;em&gt;динамически подключаемых библиотек &lt;/em&gt;(DLL – dynamically linked library). Слово &amp;quot;динамический&amp;quot; указывает на то, что подключение библиотеки происходит не на этапе компиляции, а уже после запуска программы. &lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://teletype.in/files/83/83c7b15b-d4ea-4ee2-945b-bc263a1ecd27.png&quot; width=&quot;406&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;DLL-библиотеки нашли самое широкое применение в большинстве программ. Скажем, сама операционная система Windows включает в свой состав несколько сотен DLL, и заключенная в них функциональность может использоваться нашими программами во избежание очередного изобретения велосипеда. &lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Создание повторно используемого кода &lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;С помощью Visual C++ можно создавать библиотеки трех следующих типов:&lt;br /&gt;•	библиотеки динамической компоновки (DLL);&lt;br /&gt;•	статические библиотеки;&lt;br /&gt;•	управляемые сборки.&lt;/p&gt;
  &lt;p&gt;Как правило, если необходимо создать библиотеку, которая могла бы использоваться машинным кодом C++, то следует предпочесть библиотеку динамической компоновки или статическую библиотеку. Если необходимо создать библиотеку, которая могла бы использоваться кодом на языках C++/CLI или на любом ином языке .NET, например C# или Visual Basic, то следует предпочесть управляемую сборку.&lt;/p&gt;
  &lt;p&gt;&lt;u&gt;Создание и использование библиотеки DLL (C++)&lt;/u&gt;&lt;/p&gt;
  &lt;p&gt;Сперва мы создадим библиотеку динамической компоновки (DLL). Библиотеки DLL являются хорошим способом повторного использования кода. Вместо того чтобы каждый раз реализовывать одни и те же подпрограммы в каждом создаваемом приложении, их можно создать единожды и затем вызывать из приложений для обеспечения соответствующей функциональности.&lt;/p&gt;
  &lt;p&gt;В этом пошаговом руководстве рассматриваются следующие действия:&lt;br /&gt;•	создание проекта библиотеки динамической компоновки (DLL);&lt;br /&gt;•	добавление класса в библиотеку динамической компоновки;&lt;br /&gt;•	создание приложения, ссылающегося на библиотеку динамической компоновки;&lt;br /&gt;•	использование функциональных возможностей библиотеки классов в консольном приложении;&lt;br /&gt;•	запуск приложения.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Создание проекта библиотеки динамической компоновки (DLL)&lt;/strong&gt;&lt;br /&gt;1.	В меню Файл выберите пункт Создать и затем пункт Проект....&lt;br /&gt;2.	В узле Visual C++ области Типы проектов выберите Win32.&lt;br /&gt;3.	В области Шаблоны выберите Консольное приложение Win32.&lt;br /&gt;4.	Выберите имя проекта, например MathFuncsDll, и введите его в поле Имя. Выберите имя решения, например DynamicLibrary, и введите его в поле Имя решения.&lt;br /&gt;5.	Для запуска мастера приложений Win32 нажмите кнопку ОК. На странице Общие сведения диалогового окна Мастер приложений Win32 нажмите кнопку Далее.&lt;br /&gt;6.	На странице Параметры приложения диалогового окна Мастер приложений Win32, в поле Тип приложения, выберите пункт DLL, если он доступен, либо пункт Консольное приложение в противном случае. В некоторых версиях Visual Studio создание проектов DLL с помощью мастеров не поддерживается. Необходимые изменения можно внести позднее для компиляции проекта в библиотеку DLL.&lt;br /&gt;7.	На странице Параметры приложения диалогового окна Мастер приложений Win32 в поле Дополнительные параметры выберите пункт Пустой проект.&lt;br /&gt;8.	Чтобы создать проект, нажмите кнопку Готово.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Добавление класса в библиотеку динамической компоновки&lt;/strong&gt;&lt;br /&gt;1.	Чтобы создать файл заголовка для нового класса, в меню Проект выберите команду Добавить новый элемент.... Откроется диалоговое окно Добавление нового элемента. В узле Visual C++ области Категории выберите пункт Код. В области Шаблоны выберите пункт Заголовочный файл (.h). Выберите имя заголовочного файла, например MathFuncsDll.h, и нажмите кнопку Добавить. Отобразится пустой файл.&lt;br /&gt;2.	Добавьте простой класс с именем MyMathFuncs, осуществляющий обычные арифметические операции, такие как сложение, вычитание, умножение и деление. Код должен выглядеть примерно следующим образом:&lt;/p&gt;
  &lt;pre&gt;// MathFuncsDll.h
namespace MathFuncs
{
 class MyMathFuncs
 {
 public:
 // Returns a + b
 static __declspec(dllexport) double Add(double a, double b);
 // Returns a - b
 static __declspec(dllexport) double Subtract(double a, double b);
 // Returns a * b
 static __declspec(dllexport) double Multiply(double a, double b);
 // Returns a / b
 // Throws DivideByZeroException if b is 0
 static __declspec(dllexport) double Divide(double a, double b);
 };
}&lt;/pre&gt;
  &lt;p&gt;3.	Обратите внимание на модификатор __declspec(dllexport) в объявлениях методов в этом коде. Этот модификатор разрешает экспорт метода библиотекой DLL для использования его другими приложениями. Дополнительные сведения см. в разделе dllexport, dllimport.&lt;br /&gt;4.	Чтобы создать исходный файл для нового класса, в меню Проект выберите команду Добавить новый элемент.... Откроется диалоговое окно Добавление нового элемента. В узле Visual C++ области Категории выберите пункт Код. В области Шаблоны выберите пункт Файл C++ (.cpp). Выберите имя исходного файла, например MathFuncsDll.cpp, и нажмите кнопку Добавить. Отобразится пустой файл.&lt;br /&gt;5.	Реализуйте функциональность класса MyMathFuncs в исходном файле. Код должен выглядеть примерно следующим образом:&lt;/p&gt;
  &lt;pre&gt;// MathFuncsDll.cpp
// compile with: /EHsc /LD
#include &amp;quot;MathFuncsDll.h&amp;quot;
#include &amp;lt;stdexcept&amp;gt;
using namespace std;
namespace MathFuncs
{
 double MyMathFuncs::Add(double a, double b)
 {
 return a + b;
 }
 double MyMathFuncs::Subtract(double a, double b)
 {
 return a - b;
 }
 double MyMathFuncs::Multiply(double a, double b)
 {
 return a * b;
 }
 double MyMathFuncs::Divide(double a, double b)
 {
 if (b == 0)
 {
 throw new invalid_argument(&amp;quot;b cannot be zero!&amp;quot;);
 }
 return a / b;
 }
}&lt;/pre&gt;
  &lt;p&gt;6.	Чтобы построить библиотеку DLL проекта, в меню Проект выберите СвойстваMathFuncsDll. В левой области в поле Свойства конфигурации выберите Общие. В правой области в поле Тип конфигурации выберите Динамическая библиотека (.dll). Нажмите кнопку ОК для сохранения изменений.&lt;br /&gt;7.	Скомпилируйте библиотеку динамической компоновки, выбрав команду Построить решение в меню Построение. В результате будет создана библиотека DLL, которая может использоваться другими программами. &lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Создание приложения, ссылающегося на библиотеку динамической компоновки&lt;/strong&gt;&lt;br /&gt;1.	Чтобы создать приложение, которое будет ссылаться и использовать созданную ранее библиотеку динамической компоновки, в меню Файл выберите пункт Создать и затем пункт Проект....&lt;br /&gt;2.	В узле Visual C++ области Типы проектов выберите Win32.&lt;br /&gt;3.	В области Шаблоны выберите Консольное приложение Win32.&lt;br /&gt;4.	Выберите имя проекта, например MyExecRefsDll, и введите его в поле Имя. В раскрывающемся списке рядом с полем Решение выберите пункт Добавить в решение. После этого новый проект будет добавлен в то же решение, что и библиотека динамической компоновки.&lt;br /&gt;5.	Для запуска мастера приложений Win32 нажмите кнопку ОК. На странице Общие сведения диалогового окна Мастер приложений Win32 нажмите кнопку Далее.&lt;br /&gt;6.	На странице Параметры приложения диалогового окна Мастер приложений Win32 в поле Тип приложения выберите пункт Консольное приложение.&lt;br /&gt;7.	На странице Параметры приложения диалогового окна Мастер приложений Win32 в поле Дополнительные параметры снимите флажок Предкомпилированный заголовок.&lt;br /&gt;8.	Чтобы создать проект, нажмите кнопку Готово.&lt;/p&gt;
  &lt;p&gt;&lt;br /&gt;&lt;strong&gt;Использование функциональных возможностей библиотеки классов в консольном приложении&lt;/strong&gt;&lt;br /&gt;1.	По завершении создания консольного приложения будет создана пустая программа. Имя исходного файла будет совпадать с именем, выбранным ранее для проекта. В этом примере он имеет имя MyExecRefsDll.cpp.&lt;br /&gt;2.	Для использования математических процедур из библиотеки динамической компоновки необходимо сослаться на эту библиотеку. Для этого в меню Проект(Project) выберите пункт Ссылки....(Reference…) В диалоговом окне Окна свойств(Properties) разверните узел Общие свойства (Common Properties), выберите пункт Ссылки, а затем нажмите кнопку Добавить новую ссылку.... (Add New Rewference…).&lt;br /&gt;3.	Появится диалоговое окно Добавить ссылку. В этом диалоговом окне отображается список всех библиотек, на которые можно ссылаться. На вкладке Проект перечисляются все проекты текущего решения и включенные в них библиотеки. На вкладке Проекты выберите MathFuncsDll. Затем нажмите кнопку ОК.&lt;br /&gt;4.	Для создания ссылки на заголовочные файлы библиотеки динамической компоновки необходимо изменить путь к каталогам включения. Для этого в диалоговом окне Окна свойств (Properties) последовательно разверните узлы Свойства конфигурации (Configuration Properties), C/C++, а затем выберите Общие (General). Рядом с полем Дополнительные каталоги включения (Additional Include Directories) введите путь к месту размещения заголовочного файла MathFuncsDll.h.&lt;br /&gt;5.	Исполняемый файл не загружает библиотеки динамической компоновки во время выполнения. Необходимо указать системе место для поиска библиотеки MathFuncsDll.dll. Это можно сделать с помощью переменной среды PATH. Для этого в диалоговом окне Окна свойств (Properties) разверните узел Свойства конфигурации (Configuration Properties), а затем выберите Отладка(Debugging). В поле Среда(Environment) введите следующую строку: PATH=&amp;lt;путь к файлу MathFuncsDll.dll&amp;gt;, где вместо &amp;lt;путь к файлу MathFuncsDll.dll&amp;gt; необходимо подставить фактическое местоположение библиотеки MathFuncsDll.dll. Нажмите кнопку ОК для сохранения всех изменений.&lt;br /&gt;6.	Теперь класс MyMathFuncs можно использовать в приложении. Замените код в файле MyExecRefsDll.cpp следующим кодом:&lt;/p&gt;
  &lt;pre&gt;// MyExecRefsDll.cpp
// compile with: /EHsc /link MathFuncsDll.lib
#include &amp;lt;iostream&amp;gt;
#include &amp;quot;MathFuncsDll.h&amp;quot;
using namespace std;
int main()
{
 double a = 7.4;
 int b = 99;
 cout &amp;lt;&amp;lt; &amp;quot;a + b = &amp;quot; &amp;lt;&amp;lt;
 MathFuncs::MyMathFuncs::Add(a, b) &amp;lt;&amp;lt; endl;
 cout &amp;lt;&amp;lt; &amp;quot;a - b = &amp;quot; &amp;lt;&amp;lt;
 MathFuncs::MyMathFuncs::Subtract(a, b) &amp;lt;&amp;lt; endl;
 cout &amp;lt;&amp;lt; &amp;quot;a * b = &amp;quot; &amp;lt;&amp;lt;
 MathFuncs::MyMathFuncs::Multiply(a, b) &amp;lt;&amp;lt; endl;
 cout &amp;lt;&amp;lt; &amp;quot;a / b = &amp;quot; &amp;lt;&amp;lt;
 MathFuncs::MyMathFuncs::Divide(a, b) &amp;lt;&amp;lt; endl;
 return 0;
}&lt;/pre&gt;
  &lt;p&gt;7.	Постройте исполняемый файл, выбрав команду Построить решение в меню Построение.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Запуск приложения&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;1. Убедитесь, что проект MyExecRefsDll выбран в качестве проекта по умолчанию. В Обозревателе решений выберите проект MyExecRefsDll и затем в меню Проект выберите команду Назначить запускаемым проектом.&lt;br /&gt;2.	Чтобы запустить проект, в меню Отладка выберите команду Запуск без отладки. Результат выполнения должен выглядеть примерно следующим образом:&lt;/p&gt;
  &lt;pre&gt;a + b = 106.4
a - b = -91.6
a * b = 732.6
a / b = 0.0747475&lt;/pre&gt;
  &lt;p&gt;&lt;br /&gt;&lt;strong&gt;Создание и использование статической библиотеки (C++)&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Следующим типом библиотеки, которую мы создадим, является статическая библиотека (LIB). Статические библиотеки являются хорошим способом повторного использования кода. Вместо того чтобы каждый раз реализовывать одни и те же подпрограммы в каждом создаваемом приложении, их можно создать единожды и затем вызывать из приложений для обеспечения соответствующей функциональности.&lt;/p&gt;
  &lt;p&gt;В этом пошаговом руководстве рассматриваются следующие действия:&lt;br /&gt;•	создание проекта статической библиотеки;&lt;br /&gt;•	добавление класса в статическую библиотеку;&lt;br /&gt;•	создание приложения, ссылающегося на статическую библиотеку;&lt;br /&gt;•	использование функциональных возможностей статической библиотеки в консольном приложении;&lt;br /&gt;•	запуск приложения.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Создание проекта статической библиотеки&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;1.	В меню Файл выберите пункт Создать и затем пункт Проект....&lt;br /&gt;2.	В узле Visual C++ области Типы проектов выберите Win32.&lt;br /&gt;3.	В области Шаблоны выберите Консольное приложение Win32.&lt;br /&gt;4.	Выберите имя проекта, например MathFuncsLib, и введите его в поле Имя. Выберите имя решения, например StaticLibrary, и введите его в поле Имя решения.&lt;br /&gt;5.	Для запуска мастера приложений Win32 нажмите кнопку ОК. На странице Общие сведения диалогового окна Мастер приложений Win32 нажмите кнопку Далее.&lt;br /&gt;6.	На странице Параметры приложения диалогового окна Мастер приложений Win32 в поле Тип приложения выберите пункт Статическая библиотека.&lt;br /&gt;7.	На странице Параметры приложения диалогового окна Мастер приложений Win32 в поле Дополнительные параметры снимите флажок Предкомпилированный заголовок.&lt;br /&gt;8.	Чтобы создать проект, нажмите кнопку Готово.&lt;br /&gt;&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Добавление класса в статическую библиотеку&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;1.	Чтобы создать файл заголовка для нового класса, в меню Проект выберите команду Добавить новый элемент.... Откроется диалоговое окно Добавление нового элемента. В узле Visual C++ области Категории выберите пункт Код. В области Шаблоны выберите пункт Заголовочный файл (.h). Выберите имя заголовочного файла, например MathFuncsLib.h, и нажмите Добавить. Отобразится пустой файл.&lt;br /&gt;2.	Добавьте простой класс с именем MyMathFuncs, осуществляющий обычные арифметические операции, такие как сложение, вычитание, умножение и деление. Код должен выглядеть примерно следующим образом:&lt;/p&gt;
  &lt;pre&gt;// MathFuncsLib.h
namespace MathFuncs
{
 class MyMathFuncs
 {
 public:
 // Returns a + b
 static double Add(double a, double b);
 // Returns a - b
 static double Subtract(double a, double b);
 // Returns a * b
 static double Multiply(double a, double b);
 // Returns a / b
 // Throws DivideByZeroException if b is 0
 static double Divide(double a, double b);
 };
}&lt;/pre&gt;
  &lt;p&gt;3.	Чтобы создать исходный файл для нового класса, в меню Проект выберите команду Добавить новый элемент.... Откроется диалоговое окно Добавление нового элемента. В узле Visual C++ области Категории выберите пункт Код. В области Шаблоны выберите пункт Файл C++ (.cpp). Выберите имя исходного файла, например MathFuncsLib.cpp, и нажмите Добавить. Отобразится пустой файл.&lt;br /&gt;4.	Реализуйте функциональность класса MyMathFuncs в исходном файле. Код должен выглядеть примерно следующим образом:&lt;/p&gt;
  &lt;pre&gt;// MathFuncsLib.cpp
// compile with: /c /EHsc
// post-build command: lib MathFuncsLib.obj
#include &amp;quot;MathFuncsLib.h&amp;quot;
#include &amp;lt;stdexcept&amp;gt;
using namespace std;
namespace MathFuncs
{
 double MyMathFuncs::Add(double a, double b)
 {
 return a + b;
 }
 double MyMathFuncs::Subtract(double a, double b)
 {
 return a - b;
 }
 double MyMathFuncs::Multiply(double a, double b)
 {
 return a * b;
 }
 double MyMathFuncs::Divide(double a, double b)
 {
 if (b == 0)
 {
 throw new invalid_argument(&amp;quot;b cannot be zero!&amp;quot;);
 }
 return a / b;
 }
}&lt;/pre&gt;
  &lt;p&gt;5.	Чтобы построить статическую библиотеку проекта, в меню Проект выберите СвойстваMathFuncsLib. В левой области в поле Свойства конфигурации выберите Общие. В правой области в поле Тип конфигурации выберите Статическая библиотека (.lib). Нажмите кнопку ОК для сохранения изменений.&lt;br /&gt;6.	Скомпилируйте статическую библиотеку, выбрав команду Построить решение в меню Построение. В результате будет создана статическая библиотека, которая может использоваться другими программами.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Создание приложения, ссылающегося на статическую библиотеку&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;1.	Чтобы создать приложение, которое будет ссылаться и использовать созданную ранее статическую библиотеку, в меню Файл выберите пункт Создать и затем пункт Проект....&lt;br /&gt;2.	В узле Visual C++ области Типы проектов выберите Win32.&lt;br /&gt;3.	В области Шаблоны выберите Консольное приложение Win32.&lt;br /&gt;4.	Выберите имя проекта, например MyExecRefsLib, и введите его в поле Имя. В раскрывающемся списке рядом с полем Решение выберите пункт Добавить в решение. После этого новый проект будет добавлен в то же решение, что и статическая библиотека.&lt;br /&gt;5.	Для запуска мастера приложений Win32 нажмите кнопку ОК. На странице Общие сведения диалогового окна Мастер приложений Win32 нажмите кнопку Далее.&lt;br /&gt;6.	На странице Параметры приложения диалогового окна Мастер приложений Win32 в поле Тип приложения выберите пункт Консольное приложение.&lt;br /&gt;7.	На странице Параметры приложения диалогового окна Мастер приложений Win32 в поле Дополнительные параметры снимите флажок Предкомпилированный заголовок.&lt;br /&gt;8.	Чтобы создать проект, нажмите кнопку Готово.&lt;br /&gt;&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Использование функциональных возможностей статической библиотеки в консольном приложении&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;1.	По завершении создания консольного приложения мастер создаст пустую программу. Имя исходного файла будет совпадать с именем, выбранным ранее для проекта. В этом примере он имеет имя MyExecRefsLib.cpp.&lt;br /&gt;2.	Для использования математических процедур из статической библиотеки необходимо сослаться на эту библиотеку. Для этого в меню Проект выберите пункт Ссылки.... В диалоговом окне Окна свойств разверните узел Общие свойства и выберите пункт Ссылки. Затем нажмите кнопку Добавить новую ссылку….&lt;br /&gt;3.	Появится диалоговое окно Добавить ссылку. В этом диалоговом окне отображается список всех библиотек, на которые можно ссылаться. На вкладке Проект перечисляются все проекты текущего решения и включенные в них библиотеки. На вкладке Проекты выберите MathFuncsLib. Затем нажмите кнопку ОК.&lt;br /&gt;4.	Для создания ссылки на заголовочные файлы статической библиотеки необходимо изменить путь к каталогам включения. Для этого в диалоговом окне Окна свойств последовательно разверните узлы Свойства конфигурации, C/C++, а затем выберите Общие. Рядом с полем Дополнительные каталоги включения введите путь к месту размещения заголовочного файла MathFuncsLib.h.&lt;br /&gt;5.	Теперь класс MyMathFuncs можно использовать в приложении. Замените код в файле MyExecRefsLib.cpp следующим кодом:&lt;/p&gt;
  &lt;pre&gt;// MyExecRefsLib.cpp
// compile with: /EHsc /link MathFuncsLib.lib
#include &amp;lt;iostream&amp;gt;
#include &amp;quot;MathFuncsLib.h&amp;quot;
using namespace std;
int main()
{
 double a = 7.4;
 int b = 99;
 cout &amp;lt;&amp;lt; &amp;quot;a + b = &amp;quot; &amp;lt;&amp;lt;
 MathFuncs::MyMathFuncs::Add(a, b) &amp;lt;&amp;lt; endl;
 cout &amp;lt;&amp;lt; &amp;quot;a - b = &amp;quot; &amp;lt;&amp;lt;
 MathFuncs::MyMathFuncs::Subtract(a, b) &amp;lt;&amp;lt; endl;
 cout &amp;lt;&amp;lt; &amp;quot;a * b = &amp;quot; &amp;lt;&amp;lt;
 MathFuncs::MyMathFuncs::Multiply(a, b) &amp;lt;&amp;lt; endl;
 cout &amp;lt;&amp;lt; &amp;quot;a / b = &amp;quot; &amp;lt;&amp;lt;
 MathFuncs::MyMathFuncs::Divide(a, b) &amp;lt;&amp;lt; endl;
 return 0;
}&lt;/pre&gt;
  &lt;p&gt;6.	Постройте исполняемый файл, выбрав команду Построить решение в меню Построение.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Запуск приложения&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;1.	Убедитесь, что проект MyExecRefsLib выбран в качестве проекта по умолчанию. В Обозревателе решений выберите проект MyExecRefsLib и затем в меню Проект выберите команду Назначить запускаемым проектом.&lt;br /&gt;2.	Чтобы запустить проект, в меню Отладка выберите команду Запуск без отладки. Результат выполнения должен выглядеть примерно следующим образом:&lt;/p&gt;
  &lt;pre&gt;a + b = 106.4
a - b = -91.6
a * b = 732.6
a / b = 0.0747475&lt;/pre&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Создание и использование управляемой сборки (C++)&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Следующим типом библиотеки, которую мы создадим, является управляемая сборка. Управляемые сборки являются хорошим способом повторного использования кода. Вместо того чтобы каждый раз реализовывать одни и те же подпрограммы в каждом создаваемом приложении, их можно создать единожды и затем вызывать из приложений для обеспечения соответствующей функциональности.&lt;br /&gt;В этом пошаговом руководстве рассматриваются следующие действия:&lt;br /&gt;•	создание нового проекта библиотеки классов;&lt;br /&gt;•	добавление класса в библиотеку классов;&lt;br /&gt;•	создание приложения, ссылающегося на библиотеку классов;&lt;br /&gt;•	использование функциональных возможностей библиотеки классов в консольном приложении;&lt;br /&gt;•	запуск приложения.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Создание нового проекта библиотеки классов&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;1.	В меню Файл выберите пункт Создать и затем пункт Проект....&lt;br /&gt;2.	В узле Visual C++ области Типы проектов выберите CLR. При этом будет создан проект, предназначенный для среды CLR.&lt;br /&gt;3.	В области Шаблоны выберите пункт Библиотека классов.&lt;br /&gt;4.	Выберите имя проекта, например MathFuncsAssembly, и введите его в поле Имя. Выберите имя решения, например ManagedAssemblies, и введите его в поле Имя решения.&lt;br /&gt;5.	Нажмите ОК, чтобы создать проект.&lt;br /&gt;6.	По умолчанию создаваемые проекты настраиваются на использование предкомпилированных заголовков. Чтобы отключить предкомпилированные заголовки, в меню Проект выберите Свойства. Последовательно разверните узлы Свойства конфигурации, C/C++, а затем выберите пункт Предварительно скомпилированные заголовки. В раскрывающемся списке рядом с полем Создавать или использовать предварительно скомпилированный заголовочный файл выберите пункт Не использовать предкомпилированный заголовок. Нажмите кнопку ОК для сохранения этих изменений. &lt;br /&gt;&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Добавление класса в библиотеку классов&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;1.	По завершении создания библиотеки классов CLR мастер создаст простейший класс. Имена файла заголовка и файла исходного кода будет совпадать с именем, выбранным ранее для проекта. В этом примере они имеют имена MathFuncsAssembly.h и MathFuncsAssembly.cpp.&lt;br /&gt;2.	Замените код в файле MathFuncsAssembly.h простейшим классом MyMathFuncsAssembly, осуществляющим обычные арифметические операции, такие как сложение, вычитание, умножение и деление. Код должен выглядеть примерно следующим образом:&lt;/p&gt;
  &lt;pre&gt;// MathFuncsAssembly.h
using namespace System;
namespace MathFuncs
{
 public ref class MyMathFuncs
 {
 public:
 // Returns a + b
 static double Add(double a, double b);
 // Returns a - b
 static double Subtract(double a, double b);
 // Returns a * b
 static double Multiply(double a, double b);
 // Returns a / b
 // Throws DivideByZeroException if b is 0
 static double Divide(double a, double b);
 };&lt;/pre&gt;
  &lt;p&gt;3.	Реализуйте функциональность класса MyMathFuncs в исходном файле. Код должен выглядеть примерно следующим образом:&lt;/p&gt;
  &lt;pre&gt;// MathFuncsAssembly.cpp
// compile with: /clr /LD
#include &amp;quot;MathFuncsAssembly.h&amp;quot;
namespace MathFuncs
{
 double MyMathFuncs::Add(double a, double b)
 {
 return a + b;
 }
 double MyMathFuncs::Subtract(double a, double b)
 {
 return a - b;
 }
 double MyMathFuncs::Multiply(double a, double b)
 {
 return a * b;
 }
 double MyMathFuncs::Divide(double a, double b)
 {
 if (b == 0)
 {
 throw gcnew DivideByZeroException(&amp;quot;b cannot be zero!&amp;quot;);
 }
 return a / b;
 }
}&lt;/pre&gt;
  &lt;p&gt;4.	Скомпилируйте библиотеку классов, выбрав команду Построить решение в меню Построение. В результате будет создана библиотека динамической компоновки (DLL), которая может использоваться другими программами.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Создание приложения, ссылающегося на библиотеку классов&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;1.	Чтобы создать приложение, которое будет ссылаться и использовать созданную ранее библиотеку классов, в меню Файл выберите пункт Создать и затем пункт Проект....&lt;br /&gt;2.	В узле Visual C++ области Типы проектов выберите CLR. При этом будет создан проект, предназначенный для среды CLR.&lt;br /&gt;3.	В области Шаблоны выберите Консольное приложение CLR.&lt;br /&gt;4.	Выберите имя проекта, например MyExecRefsAssembly, и введите его в поле Имя. В раскрывающемся списке рядом с полем Решение выберите пункт Добавить в решение. После этого новый проект будет добавлен в то же решение, что и библиотека классов.&lt;br /&gt;5.	Нажмите ОК, чтобы создать проект.&lt;br /&gt;6.	По умолчанию создаваемые проекты настраиваются на использование предкомпилированных заголовков. Чтобы отключить предкомпилированные заголовки, в меню Проект выберите Свойства. Последовательно разверните узлы Свойства конфигурации, C/C++, а затем выберите пункт Предварительно скомпилированные заголовки. В раскрывающемся списке рядом с полем Создавать или использовать предварительно скомпилированный заголовочный файл выберите пункт Не использовать предкомпилированный заголовок. Нажмите кнопку ОК для сохранения этих изменений. Дополнительные сведения о предкомпилированных заголовках см. в разделе Создание файлов предкомпилированных заголовков.&lt;br /&gt;&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Использование функциональных возможностей библиотеки классов в консольном приложении&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;1.	По завершении создания консольного приложения CLR будет создана программа, выводящая на консоль приветствие &amp;quot;Hello World&amp;quot;. Имя исходного файла будет совпадать с именем, выбранным ранее для проекта. В этом примере он имеет имя MyExecRefsAssembly.cpp.&lt;br /&gt;2.	Для использования математических процедур из библиотеки классов необходимо сослаться на эту библиотеку. Для этого в меню Проект выберите пункт Ссылки.... В диалоговом окне Окна свойств разверните узел Общие свойства, выберите пункт Ссылки, а затем нажмите кнопку Добавить новую ссылку.... &lt;br /&gt;3.	Появится диалоговое окно Добавить ссылку. В этом диалоговом окне отображается список всех библиотек, на которые можно ссылаться. На вкладке .NET перечислены библиотеки, включенные в .NET Framework. На вкладке COM перечислены все COM-компоненты, установленные на компьютере. На вкладке Проект перечисляются все проекты текущего решения и включенные в них библиотеки. На вкладке Проекты выберите MathFuncsAssembly, после чего нажмите кнопку ОК.&lt;br /&gt;4.	Теперь класс MyMathFuncs можно использовать в приложении. Замените содержимое функции в файле MyExecRefsAssembly.cpp следующим кодом:&lt;/p&gt;
  &lt;pre&gt;// MyExecRefsAssembly.cpp
// compile with: /clr /FUMathFuncsAssembly.dll
using namespace System;
int main(array&amp;lt;System::String ^&amp;gt; ^args)
{
 double a = 7.4;
 int b = 99;
 Console::WriteLine(&amp;quot;a + b = {0}&amp;quot;,
 MathFuncs::MyMathFuncs::Add(a, b));
 Console::WriteLine(&amp;quot;a - b = {0}&amp;quot;,
 MathFuncs::MyMathFuncs::Subtract(a, b));
 Console::WriteLine(&amp;quot;a * b = {0}&amp;quot;,
 MathFuncs::MyMathFuncs::Multiply(a, b));
 Console::WriteLine(&amp;quot;a / b = {0}&amp;quot;,
 MathFuncs::MyMathFuncs::Divide(a, b));
 return 0;
}&lt;/pre&gt;
  &lt;p&gt;5.	Постройте исполняемый файл, выбрав команду Построить решение в меню Построение.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Запуск приложения&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;1.	Убедитесь, что проект MyExecRefsAssembly выбран в качестве проекта по умолчанию. В Обозревателе решений выберите проект MyExecRefsAssembly и затем в меню Проект выберите команду Назначить запускаемым проектом.&lt;br /&gt;2.	Чтобы запустить проект, в меню Отладка выберите команду Запуск без отладки. Результат должен выглядеть следующим образом:&lt;/p&gt;
  &lt;pre&gt;a + b = 106.4
a - b = -91.6
a * b = 732.6
a / b = 0.0747474747474748&lt;/pre&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Создание DLL &lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Ничего особенного здесь нет. Как обычно, вы просто пишите функции, как в обычной программе. Если вы используете MSVC, создайте новый проект и укажите, что вы создаете Win32 Dynamic-Link Library, в опциях указать &lt;strong&gt;Пустой проект(Empty project)&lt;/strong&gt;. После компиляции вы получите DLL, библиотеку импорта (.lib) и библиотеку экспорта (.exp). Далее показан примерный код вашей DLL:&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Заголовочный файл (DLLTEST.H)&lt;/strong&gt; &lt;/p&gt;
  &lt;pre&gt;#ifndef _DLLTEST_H_
#define _DLLTEST_H_ 

#include &amp;lt;iostream&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;windows.h&amp;gt; 

using namespace std;
extern &amp;quot;C&amp;quot; __declspec(dllexport) void NumberList();
extern &amp;quot;C&amp;quot; __declspec(dllexport) void LetterList(); 

#endif &lt;/pre&gt;
  &lt;p&gt;&lt;strong&gt;Код библиотеки (DLLTEST.CPP)&lt;/strong&gt; &lt;/p&gt;
  &lt;pre&gt;#include &amp;quot;dlltest.h&amp;quot; 
#define MAXMODULE 255 
char module[MAXMODULE]; 
extern &amp;quot;C&amp;quot; __declspec(dllexport) void NumberList() 
{
	GetModuleFileNameA(NULL, module, MAXMODULE);
 cout &amp;lt;&amp;lt; &amp;quot;\n\nThis function was called from \n&amp;quot;
 &amp;lt;&amp;lt; module 
 &amp;lt;&amp;lt; endl &amp;lt;&amp;lt; endl;
 cout &amp;lt;&amp;lt; &amp;quot;NumberList(): &amp;quot;;
 for(int i=0; i&amp;lt;10; i++) 
 {
 cout &amp;lt;&amp;lt; i &amp;lt;&amp;lt; &amp;quot; &amp;quot;;
 }
 cout &amp;lt;&amp;lt; endl &amp;lt;&amp;lt; endl;
} 
extern &amp;quot;C&amp;quot; __declspec(dllexport) void LetterList() 
{
	GetModuleFileNameA(NULL, module, MAXMODULE);
 cout &amp;lt;&amp;lt; &amp;quot;\n\nThis function was called from \n&amp;quot;
 &amp;lt;&amp;lt; module 
 &amp;lt;&amp;lt; endl &amp;lt;&amp;lt; endl;
 cout &amp;lt;&amp;lt; &amp;quot;LetterList(): &amp;quot;;
 for(int i=0; i&amp;lt;26; i++) 
 {
 cout &amp;lt;&amp;lt; char(97 + i) &amp;lt;&amp;lt; &amp;quot; &amp;quot;;
 }
 cout &amp;lt;&amp;lt; endl &amp;lt;&amp;lt; endl;
} &lt;/pre&gt;
  &lt;p&gt;Как видите, ничего особенного в коде нет. Приложение, используемое для примера - консольное, так что здесь просто запрограммированы две функции, выводящие текст. Строка extern &lt;strong&gt;&amp;quot;C&amp;quot; __declspec(dllexport)&lt;/strong&gt;  означает, что функция будет видна вне DLL (т.е. ее можно вызывать из нашей программы).  После компиляции мы получим библиотеку. Теперь посммотрим, как ее можно использовать. Использование DLL без библиотеки импорта. Динамическое подключение. Чтобы загрузить DLL в программу на этапе выполнения, нужно пройти несколько шагов.&lt;br /&gt;&lt;u&gt;Шаг 1:&lt;/u&gt;&lt;br /&gt;Использовать функцию LoadLibrary или LoadLibraryEx, чтобы загрузить DLL.&lt;br /&gt;&lt;u&gt;Шаг 2:&lt;/u&gt;&lt;br /&gt;Использовать функцию GetProcAddress, чтобы получить указатель на интересующую нас функцию.&lt;br /&gt;&lt;u&gt;Шаг 3:&lt;/u&gt;&lt;br /&gt;По окончании работы не забыть выгрузить DLL с помощью функции FreeLibrary.&lt;br /&gt;В своей программе я пользовался функцией LoadLibrary. Эта функция возвращает дескриптор DLL.&lt;br /&gt;&lt;u&gt;Код:&lt;/u&gt;&lt;br /&gt;HINSTANCE hDllInstance = LoadLibrary( &amp;quot;DLLTEST.dll&amp;quot;);&lt;br /&gt;Первый параметр - путь к DLL. &lt;br /&gt;Дальше - нам нужно получить указатель на функцию. Тоесть нужно определить тип указателя. Это делается строкой (в нашем случае)&lt;br /&gt;&lt;u&gt;Код:&lt;/u&gt;&lt;br /&gt;typedef void (WINAPI *cfunc)( );&lt;br /&gt;Тоесть мы определили cfunc как тип, который указывает на функцию, которая без параметра и возвращает значение void. Далее - дело за малым.&lt;br /&gt;&lt;u&gt;Код:&lt;/u&gt;&lt;br /&gt;cfunc NumberList;&lt;br /&gt;cfunc LetterList; &lt;br /&gt;NumberList=(cfunc)GetProcAddress((HMODULE)hLib, &amp;quot;NumberList&amp;quot;);&lt;br /&gt;LetterList=(cfunc)GetProcAddress((HMODULE)hLib, &amp;quot;LetterList&amp;quot;);&lt;/p&gt;
  &lt;p&gt;Мы объявили указатель, и с помощью функции &lt;strong&gt;GetProcAddress&lt;/strong&gt; получаем адрес интересующей нас функции. Первый параметр - дескриптор DLL. Второй - имя функции. Теперь можно вызывать эти функции &lt;br /&gt;&lt;u&gt;Код:&lt;/u&gt;&lt;br /&gt;NumberList();&lt;br /&gt;LetterList();&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Исходный файл приложения, использующего библиотеку DLL - консольное приложение Win32 (DLLRUN02.EXE) &lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;#include &amp;lt;windows.h&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;conio.h&amp;gt; 
using namespace std;

#define MAXMODULE 255 

typedef void (WINAPI *cfunc)(); 
cfunc NumberList;
cfunc LetterList; 

void main() 
{
	HINSTANCE hLib=LoadLibrary(TEXT(&amp;quot;DLLTEST.DLL&amp;quot;));
 if(hLib==NULL) 
 {
 cout &amp;lt;&amp;lt; &amp;quot;Unable to load library!&amp;quot; &amp;lt;&amp;lt; endl;
 getch();
 return;
 } 
	char mod[MAXMODULE];

 GetModuleFileNameA((HMODULE)hLib, mod, MAXMODULE);
 cout &amp;lt;&amp;lt; &amp;quot;Library loaded: &amp;quot; &amp;lt;&amp;lt; mod &amp;lt;&amp;lt; endl;
 NumberList=(cfunc)GetProcAddress((HMODULE)hLib, &amp;quot;NumberList&amp;quot;);
 LetterList=(cfunc)GetProcAddress((HMODULE)hLib, &amp;quot;LetterList&amp;quot;);
 if((NumberList==NULL) || (LetterList==NULL)) 
 {
 cout &amp;lt;&amp;lt; &amp;quot;Unable to load function(s).&amp;quot; &amp;lt;&amp;lt; endl;
 FreeLibrary((HMODULE)hLib);
 return;
 }
 NumberList();
 LetterList();
 FreeLibrary((HMODULE)hLib);
 getch();
}&lt;/pre&gt;
  &lt;p&gt;Этот код загружает DLL (если она находится в путях или в текущем каталоге), а затем определяет адреса функций, которые мы будем вызывать. Конечно, в этом случае пришлось написать намного больше кода, и, соответственно, придется отловить немало ошибок. Однако такой подход универсальней.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Результаты работы DLLRUN02.EXE &lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;Library loaded: C:\DLLTEST\DLLTEST.DLL 

This function was called from C:\DLLTEST\DLLRUN02.EXE
NumberList(): 0 1 2 3 4 5 6 7 8 9 
This function was called from C:\DLLTEST\DLLRUN02.EXE
LetterList(): a b c d e f g h i j k l m n o p q r s t u v w x y z &lt;/pre&gt;

</content></entry><entry><id>mrnimda:Sy7-OmL6B</id><link rel="alternate" type="text/html" href="https://teletype.in/@mrnimda/Sy7-OmL6B?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=mrnimda"></link><title>Многопоточность С++</title><published>2019-12-05T06:47:23.404Z</published><updated>2019-12-09T05:52:17.997Z</updated><summary type="html">&lt;img src=&quot;https://teletype.in/files/5f/5f2c05f8-4ba6-48cc-b482-a00694528c5d.png&quot;&gt;Процессом (process) называется экземпляр программы, загруженной в память. Этот экземпляр может создавать нити (thread), которые представляют собой последовательность инструкций на выполнение. Важно понимать, что выполняются не процессы, а именно нити. </summary><content type="html">
  &lt;p&gt;Процессом (process) называется экземпляр программы, загруженной в память. Этот экземпляр может создавать нити (thread), которые представляют собой последовательность инструкций на выполнение. Важно понимать, что выполняются не процессы, а именно нити. &lt;/p&gt;
  &lt;p&gt;Причем любой процесс имеет хотя бы одну нить. Эта нить называется главной (основной) нитью приложения.&lt;/p&gt;
  &lt;p&gt;Потоки предоставляют возможность проведения параллельных или псевдопараллельных, в случае одного процессора, вычислений. Потоки могут порождаться во время работы программы, процесса или другого потока. Основное отличие потоков от процессов заключается в том, что различные потоки имеют различные пути выполнения, но при этом пользуются общей памятью. Путь выполнения потока задается при его создании, указанием его стартовой функции, созданный поток начинает выполнять команды этой функции, и завершается когда происходит возврат из функции.&lt;/p&gt;
  &lt;p&gt;Так как практически всегда нитей гораздо больше, чем физических процессоров для их выполнения, то нити на самом деле выполняются не одновременно, а по очереди (распределение процессорного времени происходит именно между нитями). Но переключение между ними происходит так часто, что кажется, будто они выполняются параллельно. &lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Инициализация потока&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;В новом стандарте C++11 многопоточность осуществлен в классе &lt;strong&gt;thread&lt;/strong&gt;, который определен в файле thread.h. Для того чтобы создать новый поток нужно создать объект класса thread и инициализировать передав в конструктор имя функции которая должна выполнятся в потоке. &lt;/p&gt;
  &lt;pre&gt;#include&amp;lt;iostream&amp;gt; 
#include&amp;lt;thread&amp;gt; //Файл в котором определен класс thread 
using namespace std; 
void anyFunc() { 
	cout&amp;lt;&amp;lt;&amp;quot;thread function&amp;quot;; 
} 
int main() { 
	thread func_thread(anyFunc); 
	return 0; 
}&lt;/pre&gt;
  &lt;p&gt;В этой программе создается новый объект класса thread и в объявлении конструктору передается имя функции anyFunc, который печатает на экране текст &amp;quot;thread function&amp;quot;. Но если скомпилировать данное приложение и запустить, то как бы странно не было оно закончится аварийна с сообщением об ошибке. Все дело в том что главная функция программы main создает объект &lt;strong&gt;func_thread&lt;/strong&gt;, с параметром конструктора &lt;strong&gt;anyFunc&lt;/strong&gt; и продолжает свое выполнение не дожидаясь чтобы процесс закончился, что и вызывает ошибку времени выполнения. Чтобы ошибки не было надо чтобы до того как закончится функция main все потоки были закончены. Это осуществляется путем синхронизации потоков вызывая метод join. Метод join возвращает выполнение программе когда поток заканчивается, после чего объект класса &lt;strong&gt;thread&lt;/strong&gt; можно безопасно уничтожить. &lt;/p&gt;
  &lt;pre&gt;using namespace std; 
void anyFunc() { 
	cout &amp;lt;&amp;lt; &amp;quot;thread function&amp;quot;; 
} 
int main() { 
	thread func_thread(anyFunc); 
	func_thread.join(); 
	// Выполнение возвращается функции main когда поток заканчивается 
	return 0; 
}&lt;/pre&gt;
  &lt;p&gt;Метод &lt;strong&gt;join&lt;/strong&gt; нужно указывать там, где мы хотим дождаться выполнения нашего потока.&lt;/p&gt;
  &lt;p&gt;Позвольте добавить что перед вызовом функции join надо проверить является ли объект joinable то есть представляет он реальный поток или нет, к примеру объект может быть объявлен но не инициализирован или уже закончен вызовом функции join. Проверка делается функцией joinable, который возвращает true в случае если объект представляет исполняемый поток и false в противном случаи. Надо отметить что может быть ситуация когда нам ненужно ждать чтобы поток закончился, для этого у класса thread есть другой метод по имени &lt;strong&gt;detach&lt;/strong&gt;. Обе метода ничего не принимают и не возвращают и после их вызова объект становится not joinable и можно безопасно уничтожить.&lt;/p&gt;
  &lt;pre&gt;#include&amp;lt;iostream&amp;gt; 
#include&amp;lt;thread&amp;gt; //Файл в котором определен класс thread 
using namespace std; 

void anyFunc() 
{ 
	cout &amp;lt;&amp;lt; &amp;quot;thread function&amp;quot;; 
}
 
int main() 
{ 
	thread func_thread(anyFunc); 
	if (func_thread.joinable()) 
		func_thread.join(); 
	// Выполнение возвращается функции main когда поток заканчивается 
	// func_thread.detach(); В этом случае поток заканчивается принудительно 
	return 0; 
}&lt;/pre&gt;
  &lt;p&gt;В инициализацию объекта можно и передать параметры в функции перечисляя их после имени функции как продемонстрировано в следующем примере:&lt;/p&gt;
  &lt;pre&gt;#include&amp;lt;iostream&amp;gt; 
#include&amp;lt;thread&amp;gt; //Файл в котором определен класс thread 
using namespace std; 

void printStr(char * str) 
{ 
	cout &amp;lt;&amp;lt; str &amp;lt;&amp;lt; &amp;#x27;\n&amp;#x27;; 
}
 
void printArray(int a[],const int len) 
{ 
	for (int i = 0; i &amp;lt; len; i++) { 
		cout &amp;lt;&amp;lt; a[i] &amp;lt;&amp;lt; &amp;#x27; &amp;#x27;; 
	} 
} 

int main() 
{ 
	char* str = &amp;quot;thread function with parametrs&amp;quot;; 
	const int len = 8; 
	int arr[len] = {12, 45, -34, 57, 678, 89, 0, 1}; 
	// Передаем параметр функции во время инициализации 
	thread func_thread(printStr, str); 
	// Параметров может быть много 
	thread func_thread2(printArray, arr, len); 
	if (func_thread.joinable()) func_thread.join(); 
	if (func_thread2.joinable()) func_thread2.join(); 
	return 0; 
}&lt;/pre&gt;
  &lt;p&gt;Параметры можно передавать не только по значению но и по ссылке. Для чего воспользуемся функцией ref из пространства имен std. В ниже приведенной программе вызываемому потоку передается массив и его длина, а в потоке в него добавляются 5 новых значений.&lt;/p&gt;
  &lt;pre&gt;#include&amp;lt;iostream&amp;gt; 
#include&amp;lt;cstdlib&amp;gt; 
#include&amp;lt;thread&amp;gt; 
using namespace std; 

void addElements(int a[], int &amp;amp;len) 
{ 
	for (int i = len; i &amp;lt; len + 5; i++) { 
		a[i] = rand(); 
	} 
	len += 5; 
} 

int main() 
{ 
	const int LENGTH = 20; 
	int arr[LENGTH] = {1, 2, 3, 4, 5}, current_length = 5; 
	cout &amp;lt;&amp;lt; &amp;quot;Output the array before thread\n&amp;quot;; 
	for (int i = 0; i &amp;lt; current_length; i++) { 
		cout &amp;lt;&amp;lt; arr[i] &amp;lt;&amp;lt; &amp;#x27; &amp;#x27;; 
	} 
	thread arr_thread(addElements, arr, ref(current_length)); 
	if (arr_thread.joinable()) arr_thread.join(); 
	cout &amp;lt;&amp;lt; &amp;quot;\nOutput th array after thread\n&amp;quot;; 
	for (int i = 0; i &amp;lt; current_length; i++) { 
		cout &amp;lt;&amp;lt; arr[i] &amp;lt;&amp;lt; &amp;#x27; &amp;#x27;; 
	} 
	return 0; 
}&lt;/pre&gt;
  &lt;p&gt;Потоки могут быть инициализированны не только функцией но и объект функцией, то есть в классе объекта определен метод operator(), и обычным открытым методом класса. В первом случае нужно передать объект этого класса в конструктор thread, а во втором случае ссылку на функцию и адрес объекта, конечно не надо забыть про список параметров если они есть.&lt;/p&gt;
  &lt;pre&gt;#include&amp;lt;iostream&amp;gt; 
#include&amp;lt;thread&amp;gt; 
using namespace std;
 
class arrayModifier 
{ 
public: 
	void operator()(int a[], int len) { 
		for (int i = 0; i &amp;lt; len; i++) { 
			a[i] *= 2; 
		} 
	} 
	void invers(int a[], int len) { 
		for (int i = 0; i &amp;lt; len; i++) { 
			a[i] *= -1; 
		} 
	} 
}; 

int main() 
{ 
	const int length = 5; 
	int arr[length] = {1, 2, 3, 4, 5}; 
	arrayModifier obj; 
	cout &amp;lt;&amp;lt; &amp;quot;Output the array before threads\n&amp;quot;; 
	for (int i = 0; i &amp;lt; length; i++) 
    { 
		cout &amp;lt;&amp;lt; arr[i] &amp;lt;&amp;lt; &amp;#x27; &amp;#x27;; 
	} 
	// Инициализируется объект функцией 
	thread arr_thread(obj, arr, length); 
	// Инициализируется обычным открытым методом 
	thread arr_thread2(&amp;amp;arrayModifier::invers, &amp;amp;obj, arr, length); 
	if (arr_thread.joinable()) arr_thread.join(); 
	if (arr_thread2.joinable()) arr_thread2.join(); 
	cout &amp;lt;&amp;lt; &amp;quot;\nOutput th array after threads\n&amp;quot;; 
	for (int i = 0; i &amp;lt; length; i++) 
    { 
		cout &amp;lt;&amp;lt; arr[i] &amp;lt;&amp;lt; &amp;#x27; &amp;#x27;; 
	} 
	return 0; 
}&lt;/pre&gt;
  &lt;p&gt;В вышеприведенном примере у класса arrayModifier есть два метода первый это operator() который элементы массива умножает на 2, а второй умножает на -1. Во втором объекте мы передаем адрес объекта связи с тем что метод класса в качестве скриптового параметра принимает адрес объекта для которого был вызван этот метод.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;ID потока&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt; У каждого потока есть свой уникальный номер который отличается от других потоков этой программы. Для этого в классе thread есть закрытый член id и открытый метод get_id вызов которого возвращает значение этого члена.&lt;/p&gt;
  &lt;pre&gt;#include&amp;lt;iostream&amp;gt; 
#include&amp;lt;cstdlib&amp;gt; 
#include&amp;lt;thread&amp;gt; 
using namespace std; 

class printNumber 
{ 
public: 
	void operator()(int number,int arr[],int idx) 
    { 
		int sum = 0; 
		for(int i = 0; i &amp;lt; number; i++) 
        { 
			if(1%15 == 0) continue; 
			if(i%3 == 0) sum += 3*i; 
			if(i%5 == 0) sum += 5*i; 
		} 
		arr[idx] = sum; 
	} 
}; 

int main() 
{ 
	const int length = 10; 
	thread::id id; 
	thread thread_array[length]; 
	int res_arr[length] = {0}; 
	for (int i = 0; i &amp;lt; length; i++) 
    { 
		thread_array[i] = thread(printNumber(), rand(),res_arr,i); 
	} 
	for (int i = 0; i &amp;lt; length; i++) 
    { 
		if (thread_array[i].joinable()) 
        { 
			id = thread_array[i].get_id(); 
			thread_array[i].join(); 
			cout &amp;lt;&amp;lt; &amp;quot;Thread with id &amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; &amp;quot; finished. With result &amp;quot;&amp;lt;&amp;lt;res_arr[i]&amp;lt;&amp;lt;&amp;quot;\n&amp;quot;; 
		} 
	} 
	return 0; 
}&lt;/pre&gt;
  &lt;p&gt;&lt;strong&gt;Пространство имен this_thread&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt; В заголовочном файле thread.h определено пространство имен this_thread который содержит в себе функции для работы с конкретным потоком. Три из этих функций для того чтобы на некоторое время остановить выполнение потока: &lt;strong&gt;sleep_until&lt;/strong&gt; - передается переменная класса &lt;strong&gt;chrono:time_point&lt;/strong&gt; и блокируется выполнение потока пока системные часы не дойдут до этого времени; &lt;strong&gt;sleep_for&lt;/strong&gt; - передается переменная класса &lt;strong&gt;chrono::duration&lt;/strong&gt; и выполнение потока блокируется пока не прошло столько времени сколько было преданно; &lt;strong&gt;yield&lt;/strong&gt; - останавливает выполнение потока на некоторое время предоставляя возможность выполнится другим потокам. А четвертая функция это &lt;strong&gt;get_id&lt;/strong&gt; и как метод класса thread возвращает id потока.&lt;/p&gt;
  &lt;pre&gt;#include&amp;lt;iostream&amp;gt; 
#include&amp;lt;sstream&amp;gt; 
#include&amp;lt;chrono&amp;gt; 
#include&amp;lt;thread&amp;gt; 
using namespace std; 

class printNumber 
{ 
public: 
	void operator()() 
    { 
		ostringstream out; 
		thread::id id = this_thread::get_id(); 
		out &amp;lt;&amp;lt; &amp;quot;Thread with id &amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; &amp;quot; started\n&amp;quot;; 
		cout &amp;lt;&amp;lt; out.str(); 
		// Останавливает выполнение на одну секунду 
		this_thread::sleep_for(chrono::seconds(1)); 
		out.str(&amp;quot;&amp;quot;); 
		out &amp;lt;&amp;lt; &amp;quot;Thread with id &amp;quot; &amp;lt;&amp;lt; id &amp;lt;&amp;lt; &amp;quot; finished\n&amp;quot;; 
		cout &amp;lt;&amp;lt; out.str(); 
	} 
}; 

int main() 
{ 
	const int length = 10; 
	thread thread_array[length]; 
	for (int i = 0; i &amp;lt; length; i++) 
    { 
		thread_array[i] = thread(printNumber()); 
	} 
	for (int i = 0; i &amp;lt; length; i++) 
    { 
		if (thread_array[i].joinable()) 
        { 
			thread_array[i].join(); 
		} 
	} 
	return 0; 
}&lt;/pre&gt;
  &lt;p&gt;&lt;strong&gt;Одновременный доступ к ресурсам&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt; В вышеприведенном примере для печати на экран использовался sstream, предварительно превращая в строку то что хочу печатать а потом передаю в поток вывода. А что если сразу передать в поток вывода, сперва первую строку, потом переменную типа &lt;strong&gt;thread::id&lt;/strong&gt; и наконец вторую строку. Но в этом случае потоки не по очереди будут передавать в поток вывода и в итоге получается совсем не то что мы хотели. Такая ситуация бывает когда несколько потоков работают с одним и тем же объектом. Для того чтобы предотвратить это воспользуемся классом mutex который определен в файле mutex. Переменная типа mutex можно блокировать и разблокировать. Когда вызывается метод lock класса mutex, метод проверяет объект и если он разблокирован то блокирует его и возвращает выполнение, в противном случае оно ждет пока объект разблокируется и после чего делает то же самое. А метод unlock разблокирует объект класса mutex этим позволяя другим процессам его блокировать.&lt;/p&gt;
  &lt;pre&gt;#include&amp;lt;iostream&amp;gt; 
#include&amp;lt;cstdlib&amp;gt; 
#include&amp;lt;vector&amp;gt; 
#include&amp;lt;mutex&amp;gt; 
#include&amp;lt;thread&amp;gt; 
using namespace std; 
const int elementsCount = 10; 

void push(vector&amp;lt;int&amp;gt; &amp;amp;arr, mutex&amp;amp; m_arr, mutex&amp;amp; m_out) 
{ 
	int num; 
	for (int i = 0; i &amp;lt; elementsCount; i++) 
    { 
		m_arr.lock(); 
		num = rand(); 
		arr.push_back(num); 
		m_arr.unlock(); 
		m_out.lock(); 
		cout &amp;lt;&amp;lt; &amp;quot;Push &amp;quot; &amp;lt;&amp;lt; num &amp;lt;&amp;lt; &amp;quot;\n&amp;quot;; 
		m_out.unlock(); 
	} 
} 
void pop(vector&amp;lt;int&amp;gt; &amp;amp;arr, mutex&amp;amp; m_arr, mutex&amp;amp; m_out) 
{ 
	int i = 0, num; 
	while (i &amp;lt; elementsCount) 
    { 
		m_arr.lock(); 
		if (arr.size() &amp;gt; 0) 
        { 
			num = arr.back(); 
			arr.pop_back(); 
			m_out.lock(); 
			cout &amp;lt;&amp;lt; &amp;quot;Pop &amp;quot; &amp;lt;&amp;lt; num &amp;lt;&amp;lt; &amp;quot;\n&amp;quot;; 
			m_out.unlock(); 
			i++; 
		} 
		m_arr.unlock(); 
	} 
} 

int main() 
{ 
	mutex m_arr, m_out; 
	vector&amp;lt;int&amp;gt; vec; 
	thread push_thread(push, ref(vec), ref(m_arr), ref(m_out)); 
	thread pop_thread(pop, ref(vec), ref(m_arr), ref(m_out)); 
	if (push_thread.joinable()) push_thread.join(); 
	if (pop_thread.joinable()) pop_thread.join(); 
	return 0; 
}&lt;/pre&gt;
  &lt;p&gt;В вышеприведенной программе мы создаем два объекта класса thread первый инициализируем функцией push, который добавляет в вектор 10 элементов блокируя и раз блокируя объекты m_arr и m_out, а второй функцией pop, который удаляет 10 элементов из вектора, конечно опять блокируя и раз блокируя объекты m_arr и m_out.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h3&gt;Механизмы синхронизации&lt;/h3&gt;
  &lt;p&gt;&lt;strong&gt;Вводные понятия синхронизации потоков&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;&lt;em&gt;Критический участок&lt;/em&gt; – часть кода потока, в которой производится обращение к общим данным и их изменение. &lt;/p&gt;
  &lt;p&gt;&lt;em&gt;Взаимоисключение потоков&lt;/em&gt; – ситуация, при которой поток, находящийся в критическом участке, не должен допускать входа в критический участок всех других потоков, использующих эти же данные.&lt;/p&gt;
  &lt;p&gt;&lt;em&gt;Синхронизация потоков&lt;/em&gt; – согласование потоками взаимного порядка доступа к общим ресурсам и очередности обработки событий.&lt;/p&gt;
  &lt;p&gt;При создании многопоточного приложения необходимо уделить внимание обеспечению синхронизации потоков. В противном случае могут появиться ошибки, которые трудно поддаются локализации. В чем заключается синхронизация потоков? Если сказать кратко, то синхронизация потоков (в том числе принадлежащих разным приложениям) заключается в том, что некоторые потоки должны приостанавливать свое выполнение до тех пор, пока не произойдут те или иные события, связанные с другими потоками.&lt;/p&gt;
  &lt;p&gt;Здесь возможно очень много вариантов. Например, главный поток порождает дочерний поток, который выполняет какую-либо длительную работу, например, подсчет страниц в текстовом документе. Если теперь потребуется отобразить количество страниц на экране, главный поток должен дождаться завершения работы, выполняемой дочерним потоком. &lt;/p&gt;
  &lt;p&gt;Другая проблема возникает, если, например, вы собираетесь организовать параллельную работу различных потоков с одними и теми же данными. Особенно сложно выполнить необходимую синхронизацию в том случае, когда некоторые из этих потоков выполняют чтение общих данных, а некоторые - изменение. Типичный пример - текстовый редактор, в котором один поток может заниматься редактированием документа, другой - подсчетом страниц, третий - печатью документа на принтере и так далее.&lt;/p&gt;
  &lt;p&gt;Синхронизация необходима и в том случае, когда вы, например, создаете ядро системы управления базой данных, допускающее одновременную работу нескольких пользователей. При этом вам, вероятно, потребуется выполнить синхронизацию потоков, принимающих запросы от пользователей, и выполняющих эти запросы.&lt;/p&gt;
  &lt;p&gt;В программном интерфейсе операционной системы Microsoft Windows предусмотрены различные средства синхронизации потоков, как выполняющихся в рамках одного процесса, так и принадлежащих разным процессам. В последнем случае принято говорить о синхронизации процессов. Сложность синхронизации процессов заключается в том, что они не имеют общего адресного пространства и нуждаются в создании специальных системных объектов.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Работа с потоками с помощью функций WinAPI &lt;br /&gt;Несинхронизированные потоки&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Первый пример иллюстрирует работу с несинхронизированными потоками. Основной цикл, который является основным потоком процесса, выводит на экран содержимое глобального массива целых чисел. Поток, названный &amp;quot;Thread&amp;quot;, непрерывно заполняет глобальный массив целых чисел.&lt;/p&gt;
  &lt;pre&gt;#include &amp;lt;process.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
int a[ 5 ];
void Thread( void* pParams )
{ 
    int i, num = 0;
    while ( 1 )
    { 
        for ( i = 0; i &amp;lt; 5; i++ ) a[ i ] = num;
        num++;
    }
}

int main( void )
{ 
	_beginthread( Thread, 0, NULL );
	
	while( 1 )
    printf(&amp;quot;%d %d %d %d %d\n&amp;quot;, 
          a[ 0 ], a[ 1 ], a[ 2 ],
          a[ 3 ], a[ 4 ] );
	return 0;
}&lt;/pre&gt;
  &lt;p&gt;Как видно из результата работы процесса, основной поток (сама программа) и поток Thread действительно работают параллельно (подчёркиванием обозначено состояние, когда основной поток выводит массив во время его заполнения потоком Thread):&lt;/p&gt;
  &lt;pre&gt;81751652 81751652 81751651 81751651 81751651
81751652 81751652 81751651 81751651 81751651
83348630 83348630 83348630 83348629 83348629
83348630 83348630 83348630 83348629 83348629
83348630 83348630 83348630 83348629 83348629&lt;/pre&gt;
  &lt;p&gt;Запустите программу, затем нажмите &amp;quot;Pause&amp;quot; для остановки вывода на дисплей (т.е. приостанавливаются операции ввода/вывода основного потока, но поток Thread продолжает свое выполнение в фоновом режиме) и любую другую клавишу для возобновления выполнения.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Критические секции&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;А что делать, если основной поток должен читать данные из массива после его обработки в параллельном процессе? Одно из решений этой проблемы - использование критических секций.&lt;/p&gt;
  &lt;p&gt;&lt;em&gt;Критические секции &lt;/em&gt;обеспечивают синхронизацию подобно мьютексам за исключением того, что объекты, представляющие критические секции, доступны в пределах одного процесса. События, мьютексы и семафоры также можно использовать в &amp;quot;однопроцессном&amp;quot; приложении, однако критические секции обеспечивают более быстрый и более эффективный механизм взаимно-исключающей синхронизации. Подобно мьютексам объект, представляющий критическую секцию, может использоваться только одним потоком в данный момент времени, что делает их крайне полезными при разграничении доступа к общим ресурсам. Трудно предположить что-нибудь о порядке, в котором потоки будут получать доступ к ресурсу, можно сказать лишь, что система будет &amp;quot;справедлива&amp;quot; ко всем потокам. &lt;/p&gt;
  &lt;pre&gt;#include &amp;lt;windows.h&amp;gt;
#include &amp;lt;process.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
CRITICAL_SECTION cs;
int a[5];
void Thread(void* pParams) 
{
	int i, num = 0;
	while (TRUE)
	{
		EnterCriticalSection(&amp;amp;cs);
		for (i = 0; i &amp;lt; 5; i++) a[i] = num;
		LeaveCriticalSection(&amp;amp;cs);
		num++;
	}
}

int main(void) 
{
	InitializeCriticalSection(&amp;amp;cs);
	_beginthread(Thread, 0, NULL);
	while (TRUE)
	{
		EnterCriticalSection(&amp;amp;cs);
		printf(&amp;quot;%d %d %d %d %d\n&amp;quot;,
			a[0], a[1], a[2],
			a[3], a[4]);
		LeaveCriticalSection(&amp;amp;cs);
	}
	return 0;
}&lt;/pre&gt;
  &lt;p&gt;&lt;strong&gt;Мьютексы (взаимоисключения)&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Мьютекс (взаимоисключение, mutex) - это объект синхронизации, который устанавливается в особое сигнальное состояние, когда не занят каким-либо потоком. Только один поток владеет этим объектом в любой момент времени, отсюда и название таких объектов - одновременный доступ к общему ресурсу исключается. Например, чтобы исключить запись двух потоков в общий участок памяти в одно и то же время, каждый поток ожидает, когда освободится мьютекс, становится его владельцем и только потом пишет что-либо в этот участок памяти. После всех необходимых действий мьютекс освобождается, предоставляя другим потокам доступ к общему ресурсу.&lt;/p&gt;
  &lt;p&gt;Два (или более) процесса могут создать мьютекс с одним и тем же именем, вызвав метод &lt;code&gt;CreateMutex&lt;/code&gt; . Первый процесс действительно создает мьютекс, а следующие процессы получают хэндл уже существующего объекта. Это дает возможность нескольким процессам получить хэндл одного и того же мьютекса, освобождая программиста от необходимости заботиться о том, кто в действительности создает мьютекс. Если используется такой подход, желательно установить флаг &lt;em&gt;bInitialOwner&lt;/em&gt; в FALSE, иначе возникнут определенные трудности при определении действительного создателя мьютекса. &lt;/p&gt;
  &lt;p&gt;Несколько процессов могут получить хэндл одного и того же мьютекса, что делает возможным взаимодействие между процессами. Вы можете использовать следующие механизмы такого подхода: &lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Дочерний процесс, созданный при помощи функции &lt;code&gt;CreateProcess&lt;/code&gt; может наследовать хэндл мьютекса в случае, если при его (мьютекса) создании функией &lt;code&gt;CreateMutex&lt;/code&gt; был указан параметр &lt;em&gt;lpMutexAttributes&lt;/em&gt; . &lt;/li&gt;
    &lt;li&gt;Процесс может получить дубликат существующего мьютекса с помощью функции &lt;code&gt;DuplicateHandle&lt;/code&gt; . &lt;/li&gt;
    &lt;li&gt;Процесс может указать имя существующего мьютекса при вызове функций &lt;code&gt;OpenMutex&lt;/code&gt; или &lt;code&gt;CreateMutex&lt;/code&gt; . &lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;Вообще говоря, если вы синхронизируете потоки одного процесса, более эффективным подходом является использование критических секций.&lt;/p&gt;
  &lt;pre&gt;#include &amp;lt;windows.h&amp;gt;
#include &amp;lt;process.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
HANDLE hMutex;
int a[ 5 ];
void Thread( void* pParams )
{ 
	int i, num = 0;
	while ( TRUE ) { 
		WaitForSingleObject( hMutex, INFINITE );
		for ( i = 0; i &amp;lt; 5; i++ ) a[ i ] = num;
		ReleaseMutex( hMutex );
		num++;
	}
}
int main( void )
{
	hMutex = CreateMutex( NULL, FALSE, NULL );
	_beginthread( Thread, 0, NULL );
	while( TRUE )
	{
		WaitForSingleObject( hMutex, INFINITE );
		printf( &amp;quot;%d %d %d %d %d\n&amp;quot;, 
				a[ 0 ], a[ 1 ], a[ 2 ],
				a[ 3 ], a[ 4 ] );
		ReleaseMutex( hMutex );
	}
	return 0;
}&lt;/pre&gt;
  &lt;p&gt;&lt;strong&gt;События&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Событие - это объект синхронизации, состояние которого может быть установлено в сигнальное, путем вызова функций &lt;code&gt;SetEvent&lt;/code&gt; или &lt;code&gt;PulseEvent&lt;/code&gt; . Существует два типа событий:&lt;/p&gt;
  &lt;p&gt;&lt;u&gt;Событие с ручным сбросом&lt;br /&gt;&lt;/u&gt;Это объект, сигнальное состояние которого сохраняется до ручного сброса функцией &lt;code&gt;ResetEvent&lt;/code&gt; . Как только состояние объекта установлено в сигнальное, все находящиеся в цикле ожидания этого объекта потоки продолжают свое выполнение (освобождаются).&lt;/p&gt;
  &lt;p&gt;&lt;u&gt;Событие с автоматическим сбросом&lt;/u&gt;&lt;br /&gt;Объект, сигнальное состояние которого сохраняется до тех пор, пока не будет освобожден единственный поток, после чего система автоматически устанавливает несигнальное состояние события. Если нет потоков, ожидающих этого события, объект остается в сигнальном состоянии.&lt;/p&gt;
  &lt;p&gt;События полезны в тех случаях, когда необходимо послать сообщение потоку, сообщающее, что произошло определенное событие. Например, при асинхронных операциях ввода и вывода из одного устройства, система устанавливает событие в сигнальное состояние когда заканчивается какая-либо из этих операций. Один поток может использовать несколько различных событий в нескольких перекрывающихся операциях, а затем ожидать прихода сигнала от любого из них. &lt;/p&gt;
  &lt;p&gt;Поток может использовать функцию &lt;code&gt;CreateEvent&lt;/code&gt; для создания объекта события. Создающий событие поток устанавливает его начальное состояние. В создающем потоке можно указать имя события. Потоки других процессов могут получить доступ к этому событию по имени, указав его в функции &lt;code&gt;OpenEvent&lt;/code&gt; .&lt;/p&gt;
  &lt;p&gt;Поток может использовать функцию &lt;code&gt;PulseEvent&lt;/code&gt; для установки состояния события в сигнальное и затем сбросить состояние в несигнальное после освобождения соответствующего количества ожидающих потоков. В случае объектов с ручным сбросом освобождаются все ожидающие потоки. В случае объектов с автоматическим сбросом освобождается только единственный поток, даже если этого события ожидают несколько потоков. Если ожидающих потоков нет, &lt;code&gt;PulseEvent&lt;/code&gt; просто устанавливает состояние события в несигнальное.&lt;/p&gt;
  &lt;pre&gt;#include &amp;lt;windows.h&amp;gt;
#include &amp;lt;process.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
HANDLE hEvent1, hEvent2;
int a[ 5 ];
void Thread( void* pParams )
{
   int i, num = 0;
   while ( TRUE )
   {
      WaitForSingleObject( hEvent2, INFINITE );
      for ( i = 0; i &amp;lt; 5; i++ ) a[ i ] = num;
      SetEvent( hEvent1 );
      num++;
   }
}
int main( void )
{
    hEvent1 = CreateEvent( NULL, FALSE, TRUE, NULL );
    hEvent2 = CreateEvent( NULL, FALSE, FALSE, NULL );
    _beginthread( Thread, 0, NULL );
    while( TRUE )
    { 
        WaitForSingleObject( hEvent1, INFINITE );
        printf( &amp;quot;%d %d %d %d %d\n&amp;quot;, 
        a[ 0 ], a[ 1 ], a[ 2 ],
        a[ 3 ], a[ 4 ] );
        SetEvent( hEvent2 );
    }
    return 0;
}&lt;/pre&gt;
  &lt;p&gt;&lt;strong&gt;Потокобезапасность&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;STL не потокобезопасная библиотека, но исправить это очень просто. Предположим, вам необходимо сохранять данные в вашу коллекцию в одном потоке, когда другой поток также сохраняет их туда. Тогда просто используйте критическую секцию или Mutex.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p&gt;&lt;strong&gt;Типовые задачи синхронизации&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Процесс &lt;/em&gt;&lt;/strong&gt;- вычисление, применение конечного множества операций к конечному набору данных. Два процесса считаются параллельными, если первая операция одного процесса начинает выполняться до завершения другого. Ресурсы, используемые несколькими процессами, называются &lt;strong&gt;&lt;em&gt;разделяемыми&lt;/em&gt;&lt;/strong&gt;. &lt;strong&gt;&lt;em&gt;Критический ресурс &lt;/em&gt;&lt;/strong&gt;- это разделяемый ресурс, который одновременно может использоваться не более чем одним процессом. &lt;strong&gt;&lt;em&gt;Критический интервал &lt;/em&gt;&lt;/strong&gt;или &lt;strong&gt;&lt;em&gt;критическая секция &lt;/em&gt;&lt;/strong&gt;- участок кода, где процесс работает с критическим ресурсом. Взаимосвязанные процессы - это процессы, использующие общий ресурс или обменивающиеся информацией. &lt;strong&gt;&lt;em&gt;Синхронизация&lt;/em&gt;&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;процессов &lt;/em&gt;&lt;/strong&gt;- ограничения, накладываемые на порядок выполнения процессов. Эти ограничения задаются с помощью &lt;strong&gt;&lt;em&gt;правил синхронизации&lt;/em&gt;&lt;/strong&gt;, которые описываются с помощью &lt;strong&gt;&lt;em&gt;механизмов синхронизации &lt;/em&gt;&lt;/strong&gt;(примитивов).&lt;/p&gt;
  &lt;p&gt;К типовым задачам синхронизации можно отнести:&lt;br /&gt;•	взаимное исключение;&lt;br /&gt;•	обедающие философы;&lt;br /&gt;•	поставщики - потребители;&lt;br /&gt;•	читатели - писатели.&lt;/p&gt;
  &lt;p&gt;Задача о взаимном исключении формулируется так: есть несколько процессов, программы которых содержат участки, где процессы обращаются к разделяемому ресурсу. Требуется исключить одновременные обращения процессов к критическому интервалу. При этом необходимо, чтобы задержка любого процесса вне его критического интервала не влияла на развитие других процессов. Решение должно быть симметричным для всех процессов, т.е. все процессы равноправны. Решение не должно допускать общих и локальных тупиков. &lt;strong&gt;&lt;em&gt;Общий тупик &lt;/em&gt;&lt;/strong&gt;- это взаимная блокировка всех процессов; &lt;strong&gt;&lt;em&gt;локальный тупик &lt;/em&gt;&lt;/strong&gt;- взаимная блокировка одного или нескольких процессов.&lt;/p&gt;
  &lt;p&gt;Задача &amp;quot;&lt;strong&gt;обедающие философы&lt;/strong&gt;&amp;quot;: на круглом столе находятся k тарелок с едой, между которыми лежит столько же вилок, k&amp;gt;=2. В комнате имеется k философов, чередующих философские размышления с принятием пищи. За каждым философом закреплена своя тарелка; для еды философу нужны две вилки, причем он может использовать только вилки, примыкающие к его тарелке. Требуется так синхронизировать философов, чтобы каждый из них мог получить за ограниченное время доступ к своей тарелке. Предполагается, что длительность еды и размышлений философа конечна, но заранее недетерминирована.&lt;/p&gt;
  &lt;p&gt;Задача &amp;quot;&lt;strong&gt;поставщики-потребители&lt;/strong&gt;&amp;quot;: имеется ограниченный буфер на m мест (m порций информации). Он является критическим ресурсом для процессов двух типов: процессы-поставщики, получая доступ к ресурсу, помещают на свободное место порцию информации; процессы-потребители, получая доступ к ресурсу, считывают из него порцию информации. Требуется исключить одновременный доступ процессов к ресурсу. При полном опустошении буфера задерживаются процессы-потребители, при полном заполнении буфера задерживаются процессы-поставщики.&lt;/p&gt;
  &lt;p&gt;Задача &amp;quot;&lt;strong&gt;читатели-писатели&lt;/strong&gt;&amp;quot;: имеется разделяемый ресурс - область памяти, к которой требуется обеспечить доступ процессам двух типов: процессы-читатели могут получать доступ к ресурсу одновременно, они считывают информацию (неразрушающее считывание); процессы-писатели взаимно исключают друг друга и читателей. Известны два варианта этой задачи:&lt;/p&gt;
  &lt;p&gt;1. читатели, изъявившие желание получить доступ к ресурсу, должны получить его как можно быстрее;&lt;/p&gt;
  &lt;p&gt;2. читатели, изъявившие желание получить доступ к ресурсу, должны получить его как можно быстрее, если отсутствуют запросы от писателей. Писатель, требующий доступ к ресурсу, должен получить его как можно быстрее, но после обслуживания читателей, подошедших к ресурсу до первого писателя.&lt;/p&gt;
  &lt;p&gt;Во многих приложениях требуется барьерная синхронизация N параллельных процессов - они все должны прийти к некоторой точке и только после этого все процессы, запросившие барьерную синхронизацию, могут продолжиться.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p&gt;&lt;strong&gt;Механизм семафоров&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Пусть S - &lt;strong&gt;&lt;em&gt;семафор &lt;/em&gt;&lt;/strong&gt;- переменная специального типа с целочисленными значениями, над которой определены две операции: Р (закрытие) и V (открытие). &lt;/p&gt;
  &lt;p&gt;P(S): если S≥1, то процесс продолжает выполняться, а S уменьшается на единицу; если S = 0, то процесс задерживается, а имя его передается в очередь процессов, ожидающих доступа к данному ресурсу (обычно семафоры связывают с некоторыми ресурсами).&lt;/p&gt;
  &lt;p&gt;V(S): если в очереди к семафору S есть процессы, то один из них выбирается и активизируется (переводится в состояние готовности: помещается в очередь процессов, претендующих на процессорное время); если в очереди нет процессов, то выполняется операция S = S+1 (при условии непревышения результатом максимально допустимого значения семафора; для двоичного семафора &lt;em&gt;S &lt;/em&gt;∈{0,1}).&lt;/p&gt;
  &lt;p&gt;Операции Р и V неделимы, т.е. над одним семафором одновременно может работать только один процесс. Так, процесс, начавший работу с семафором, должен ее закончить до переключения процессора на другой процесс в случае квазипараллельных процессов.&lt;/p&gt;
  &lt;p&gt;Все упомянутые задачи синхронизации можно решить с помощью семафоров. Рассмотрим задачу взаимного исключения:&lt;/p&gt;
  &lt;pre&gt;S=1;
Процесс_i:
P(S);
Критическая секция
V(S);&lt;/pre&gt;
  &lt;p&gt;При неаккуратном использовании семафорных переменных могут возникать тупиковые состояния. Например, тупик возможен в случае представленных ниже двух процессов при их одновременном выходе на второй вызов P:&lt;/p&gt;
  &lt;pre&gt;S1=1 S2=1 P(S1) P(S2) P(S2) P(S1)
V(S2) V(S1) V(S1) V(S2)&lt;/pre&gt;
  &lt;p&gt;Следует, однако, отметить, что приведенные в примере процессы могут работать и неограниченно долгое время, не попадая в тупик.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Семафорное решение задачи о философах&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Обозначим через Р1, Р2, Р3, Р4 - процессы-философы; b1, b2, b3, b4 - вилки. Для еды философу необходимо использовать две соседние вилки &lt;/p&gt;
  &lt;figure class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://teletype.in/files/5f/5f2c05f8-4ba6-48cc-b482-a00694528c5d.png&quot; width=&quot;554&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Алгоритм работы каждого философа предполагает использование пяти семафоров (Si, i =1,4 ; S). Семафор S предназначен для взаимного исключения процессов при получении доступа к массиву b, отвечающему за вилки. Каждый семафор Si предназначен для подвешивания i-го философа в том случае, если в результате проверки вилок он обнаружит, что нужных ему вилок в наличии нет. Приведем алгоритм работы первого философа, остальные работают аналогично.&lt;/p&gt;
  &lt;pre&gt;P1: P(S[1]); P(S); if (b1&amp;gt;0)&amp;amp;(b2&amp;gt;0) then begin b1:=0; b2:=0; V(S);
{питание} P(S);
b1:=1; b2:=1; V(S);
for i:=1 to 4 do V(S[i]); {философствование} end else 
V(S); goto P1;&lt;/pre&gt;

</content></entry><entry><id>mrnimda:BkgKvpEar</id><link rel="alternate" type="text/html" href="https://teletype.in/@mrnimda/BkgKvpEar?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=mrnimda"></link><title>Обработка исключительных ситуаций</title><published>2019-12-04T05:43:19.931Z</published><updated>2019-12-04T05:43:19.931Z</updated><summary type="html">Исключительная ситуация, или исключение — это возникновение непредвиден­ного или аварийного события, которое может порождаться некорректным ис­пользованием аппаратуры. Например, это деление на ноль или обращение по не­существующему адресу памяти. Обычно эти события приводят к завершению программы с системным сообщением об ошибке. C++ дает программисту воз­можность восстанавливать программу и продолжать ее выполнение.</summary><content type="html">
  &lt;p&gt;Исключительная ситуация, или исключение — это возникновение непредвиден­ного или аварийного события, которое может порождаться некорректным ис­пользованием аппаратуры. Например, это деление на ноль или обращение по не­существующему адресу памяти. Обычно эти события приводят к завершению программы с системным сообщением об ошибке. C++ дает программисту воз­можность восстанавливать программу и продолжать ее выполнение.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Try-catch-throw&lt;/strong&gt;&lt;br /&gt;Давайте же разберем основы обработки исключений в С++. Чтобы комфортно работать с исключениями в С++ вам нужно знать лишь три ключевых слова:&lt;br /&gt;•	try (пытаться) - начало блока исключений;&lt;br /&gt;•	catch (поймать) - начало блока, &amp;quot;ловящего&amp;quot; исключение;&lt;br /&gt;•	throw (бросить) - ключевое слово, &amp;quot;создающее&amp;quot; (&amp;quot;возбуждающее&amp;quot;) исключение.&lt;br /&gt;А теперь пример, демонстрирующий, как применить то, что вы узнали:&lt;/p&gt;
  &lt;pre&gt;void func()
{
	try
	{
		throw 1;
	}
	catch (int a)
	{
		cout &amp;lt;&amp;lt; &amp;quot;Caught exception number: &amp;quot; &amp;lt;&amp;lt; a &amp;lt;&amp;lt; endl;
		return;
	}
	cout &amp;lt;&amp;lt; &amp;quot;No exception detected!&amp;quot; &amp;lt;&amp;lt; endl;
	return;
}&lt;/pre&gt;
  &lt;p&gt;Если выполнить этот фрагмент кода, то мы получим следующий результат:&lt;br /&gt;&lt;code&gt;Caught exception number: 1&lt;/code&gt;&lt;br /&gt;Теперь закоментируйте строку throw 1; и функция выдаст такой результат:&lt;br /&gt;&lt;code&gt;No exception detected!&lt;/code&gt;&lt;br /&gt;Как видите все очень просто, но если это применить с умом, такой подход покажется вам очень мощным средством обработки ошибок. Catch может &amp;quot;ловить&amp;quot; любой тип данных, так же как и throw может &amp;quot;кинуть&amp;quot; данные любого типа. Т.е. throw &lt;code&gt;AnyClass();&lt;/code&gt; будет правильно работать, так же как и catch &lt;br /&gt;&lt;code&gt;(AnyClass &amp;amp;d) {};&lt;/code&gt;.&lt;/p&gt;
  &lt;p&gt;работать что-нибудь типа этого:&lt;br /&gt;&lt;code&gt;catch(dumbclass) { }&lt;/code&gt;&lt;br /&gt;так же, как и&lt;br /&gt;&lt;code&gt;catch(dumbclass&amp;amp;) { }&lt;/code&gt;&lt;br /&gt;Так же можно &amp;quot;поймать&amp;quot; и все исключения:&lt;br /&gt;&lt;code&gt;catch(...) { }&lt;/code&gt;&lt;/p&gt;
  &lt;p&gt;Троеточие в этом случае показывает, что будут пойманы все исключения. При таком подходе нельзя указать имя переменной. В случае, если &amp;quot;кидаются&amp;quot; данные нестандартного типа (экземпляры определенных вами классов, структур и т.д.), лучше &amp;quot;ловить&amp;quot; их по ссылке, иначе вся &amp;quot;кидаемая&amp;quot; переменная будет скопирована в стек вместо того, чтобы просто передать указатель на нее. Если кидаются данные нескольких типов и вы хотите поймать конкретную переменную (вернее, переменную конкретного типа), то можно использовать несколько блоков &lt;strong&gt;catch&lt;/strong&gt;, ловящих &amp;quot;свой&amp;quot; тип данных:&lt;/p&gt;
  &lt;pre&gt;try {
	throw 1;
	// throw &amp;#x27;a&amp;#x27;;
}
catch (long b) {
	cout &amp;lt;&amp;lt; &amp;quot;пойман тип long: &amp;quot; &amp;lt;&amp;lt; b &amp;lt;&amp;lt; endl;
}
catch (char b) {
	cout &amp;lt;&amp;lt; &amp;quot;пойман тип char: &amp;quot; &amp;lt;&amp;lt; b &amp;lt;&amp;lt; endl;
}&lt;/pre&gt;
  &lt;p&gt;&lt;strong&gt;&amp;quot;Создание&amp;quot; исключений&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Когда возбуждается исключительная ситуация, программа просматривает стек функций до тех пор, пока не находит соответствующий catch. Если оператор catch не найден, STL будет обрабатывать исключение в стандартном обработчике, который делает все менее изящно, чем могли бы сделать вы, показывая какие-то непонятные (для конечного пользователя) сообщения и обычно аварийно завершая программу.&lt;/p&gt;
  &lt;p&gt;Однако более важным моментом является то, что пока просматривается стек функций, вызываются деструкторы всех локальных классов, так что вам не нужно заботиться об освобождении памяти и т.п.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Перегрузка глобальных операторов new/delete&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Если перегрузить стандартные new и delete, то открываются широкие возможности по отслеживанию ошибок (причем ошибок часто критических) с помощью исключений. Например:&lt;/p&gt;
  &lt;pre&gt;char *a;
try
{
	a = new char[10];
}
catch (...) {
	// a не создан - обработать ошибку распределения памяти, 
	// выйти из программы и т.п.
}
// a успешно создан, продолжаем выполнение&lt;/pre&gt;
  &lt;p&gt;Это, на первый взгляд, кажется длиннее, чем стандартная проверка в С &amp;quot;а равен NULL?&amp;quot;, однако если в программе выделяется десяток динамических переменных, то такой метод оправдывает себя.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Операторы throw без параметров&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Итак, мы увидели, как новый метод обработки ошибок удобен и прост. Блок &lt;strong&gt;try-catch&lt;/strong&gt; может содержать вложенные блоки &lt;strong&gt;try-catch&lt;/strong&gt; и если не будет определено соответствующего оператора &lt;strong&gt;catch&lt;/strong&gt; на текущем уровне вложения, исключение будет поймано на более высоком уровне. Единственная вещь, о которой вы должны помнить, - это то, что операторы, следующие за &lt;strong&gt;throw&lt;/strong&gt;, никогда не выполнятся.&lt;/p&gt;
  &lt;pre&gt;try
{
	throw;
	// ни один оператор, следующий далее (до закрывающей скобки) 
	// выполнен не будет
}
catch (...)
{
	cout &amp;lt;&amp;lt; &amp;quot;Исключение!&amp;quot; &amp;lt;&amp;lt; endl;
}&lt;/pre&gt;
  &lt;p&gt;Такой метод может применяться в случаях, когда не нужно передавать никаких данных в блок catch.&lt;/p&gt;

</content></entry><entry><id>mrnimda:B1fVfYX6B</id><link rel="alternate" type="text/html" href="https://teletype.in/@mrnimda/B1fVfYX6B?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=mrnimda"></link><title>Стандарт С++ 11</title><published>2019-12-03T06:35:21.810Z</published><updated>2019-12-03T06:35:21.810Z</updated><summary type="html">https://habr.com/ru/post/182920/</summary><content type="html">
  &lt;p&gt;&lt;a href=&quot;https://habr.com/ru/post/182920/&quot; target=&quot;_blank&quot;&gt;https://habr.com/ru/post/182920/&lt;/a&gt;&lt;/p&gt;
  &lt;p&gt;1 — &lt;strong&gt;auto&lt;/strong&gt;&lt;br /&gt;До С++11, ключевое слово auto использовалось как спецификатор хранения переменной (как, например, register, static, extern). В С++11 auto позволяет не указывать тип переменной явно, говоря компилятору, чтобы он сам определил фактический тип переменной, на основе типа инициализируемого значения. &lt;/p&gt;
  &lt;pre&gt;auto i = 42; // i - int
auto l = 42LL; // l - long long
auto p = new foo(); // p - foo*&lt;/pre&gt;
  &lt;p&gt;Использование auto позволяет сократить код (если, конечно, тип не int, который на одну букву меньше). &lt;/p&gt;
  &lt;pre&gt;// C++03
for (std::vector&amp;lt;std::map&amp;lt;int, std::string&amp;gt;&amp;gt;::const_iterator it = container.begin(); it != container.end(); ++it)
{
 // do smth
}

// C++11
for (auto it = container.begin(); it != container.end(); ++it)
{
 // do smth
}&lt;/pre&gt;
  &lt;p&gt;Стоить отметить, что возвращаемое значение не может быть auto. Однако, вы можете использовать auto вместо типа возвращаемого значения функции. В таком случае, auto не говорит компилятору, что он должен определить тип, он только дает ему команду искать возвращаемый тип в конце функции. В примере ниже, возвращаемый тип функции compose— это возвращаемый тип оператора +, который суммирует значения типа T и E.&lt;/p&gt;
  &lt;pre&gt;template &amp;lt;typename T, typename E&amp;gt;
auto compose(T a, E b) -&amp;gt; decltype(a+b) // decltype - позволяет определить тип на основе входного параметра
{
 return a+b;
}
auto c = compose(2, 3.14); // c - double&lt;/pre&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;p&gt;2 — &lt;strong&gt;nullptr&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Раньше, для обнуления указателей использовался макрос NULL, являющийся нулем — целым типом, что, естественно, вызывало проблемы (например, при перегрузке функций). Ключевое слово nullptr имеет свой собственный тип std::nullptr_t, что избавляет нас от бывших проблем. Существуют неявные преобразования nullptr к нулевому указателю любого типа и к bool (как false), но преобразования к целочисленным типам нет.&lt;/p&gt;
  &lt;pre&gt;void foo(int* p) {}

void bar(std::shared_ptr&amp;lt;int&amp;gt; p) {}

int* p1 = NULL;
int* p2 = nullptr;

if (p1 == p2)
{
}

foo(nullptr);
bar(nullptr);
bool f = nullptr;
int i = nullptr; // ошибка: для преобразования в int надо использовать reinterpret_cast&lt;/pre&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;p&gt;3 — &lt;strong&gt;range-based циклы&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Range-Based for&lt;/strong&gt; — это цикл по контейнеру. Он аналогичен циклу for each в Java или C#. Синтаксически он повторяет for each из Java. Назван он Range-Based в первую очередь потому, чтобы избежать путаницы, ибо в STL уже давно есть алгоритм, именуемыйstd::for_each.&lt;/p&gt;
  &lt;pre&gt;std::vector&amp;lt;int&amp;gt; foo;
// заполняем вектор
for (int x : foo)
std::cout &amp;lt;&amp;lt; x &amp;lt;&amp;lt; std::endl;&lt;/pre&gt;
  &lt;p&gt;Модель ссылок работает также, как и везде:&lt;/p&gt;
  &lt;pre&gt;for (int&amp;amp; x : foo)
 x *= 2;
for (const int&amp;amp; x : foo)
    std::cout &amp;lt;&amp;lt; x &amp;lt;&amp;lt; std::endl;&lt;/pre&gt;
  &lt;p&gt;Красиво и удобно, правда? Рассмотренный выше auto усиливает данную конструкцию:&lt;/p&gt;
  &lt;pre&gt;std::vector&amp;lt;std::pair&amp;lt;int, std::string&amp;gt;&amp;gt; container;
// ...
for (const auto&amp;amp; i : container)
    std::cout &amp;lt;&amp;lt; i.second &amp;lt;&amp;lt; std::endl;&lt;/pre&gt;
  &lt;p&gt;Range-Based for, к слову, работает и на обычных статических массивах:&lt;/p&gt;
  &lt;pre&gt;int foo[] = {1, 4, 6, 7, 8};

for (int x : foo)
    std::cout &amp;lt;&amp;lt; x &amp;lt;&amp;lt; std::endl;&lt;/pre&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;p&gt;4 — &lt;strong&gt;override и final&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Были добавлены два новых идентификатора (не ключевые слова): &lt;strong&gt;override&lt;/strong&gt;, для указания того, что метод является переопределением виртуального метода в базовом классе и &lt;strong&gt;final&lt;/strong&gt;, указывающий что производный класс не должен переопределять виртуальный метод:&lt;/p&gt;
  &lt;pre&gt;class B 
{
public:
 virtual void f(short) {std::cout &amp;lt;&amp;lt; &amp;quot;B::f&amp;quot; &amp;lt;&amp;lt; std::endl;}
};

class D : public B
{
public:
 virtual void f(int) override {std::cout &amp;lt;&amp;lt; &amp;quot;D::f&amp;quot; &amp;lt;&amp;lt; std::endl;}
};&lt;/pre&gt;
  &lt;p&gt;Теперь это вызовет ошибку при компиляции (точно так же, если бы вы использовалиoverride во втором примере):&lt;/p&gt;
  &lt;p&gt;D::f: method with override specifier &amp;#x27;override&amp;#x27; did not override any base class methods&lt;/p&gt;
  &lt;p&gt;С другой стороны, если вы хотите сделать метод, не предназначенный для переопределения (ниже в иерархии), его следует отметить как final. В производном классе можно использовать сразу оба идентификатора.&lt;/p&gt;
  &lt;pre&gt;class B 
{
public:
 virtual void f(int) {std::cout &amp;lt;&amp;lt; &amp;quot;B::f&amp;quot; &amp;lt;&amp;lt; std::endl;}
};

class D : public B
{
public:
 virtual void f(int) override final {std::cout &amp;lt;&amp;lt; &amp;quot;D::f&amp;quot; &amp;lt;&amp;lt; std::endl;}
};

class F : public D
{
public:
 virtual void f(int) override {std::cout &amp;lt;&amp;lt; &amp;quot;F::f&amp;quot; &amp;lt;&amp;lt; std::endl;}
};&lt;/pre&gt;
  &lt;p&gt;Функция, объявленная как final, не может быть переопределена функцией F::f() — в этом случае, она переопределяет метод базового класса (В) для класса D.&lt;/p&gt;
  &lt;p&gt;5 — &lt;strong&gt;строго-типизированный enum&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;У «традиционных» перечислений в С++ есть некоторые недостатки: они экспортируют свои значения в окружающую область видимости (что может привести к конфликту имен), они неявно преобразовываются в целый тип и не могут иметь определенный пользователем тип.&lt;/p&gt;
  &lt;p&gt;Эти проблемы устранены в С++11 с введением новой категории перечислений, названных strongly-typed enums. Они определяются ключевым словом enum class. Они больше не экспортируют свои перечисляемые значения в окружающую область видимости, больше не преобразуются неявно в целый тип и могут иметь определенный пользователем тип (эта опция так же добавлена и для «традиционных» перечислений&amp;quot;).&lt;/p&gt;
  &lt;pre&gt;enum class Options {None, One, All};
Options o = Options::All;&lt;/pre&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;p&gt;6 — &lt;strong&gt;интеллектуальные указатели&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Есть много статей, как на хабре, так и на других ресурсах, написанных на эту тему, поэтому я просто хочу упомянуть об интеллектуальных указателях с подсчетом ссылок и автоматическим освобождением памяти:&lt;/p&gt;
  &lt;p&gt;&lt;a href=&quot;https://en.cppreference.com/w/cpp/memory/unique_ptr&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;unique_ptr&lt;/strong&gt;&lt;/a&gt;: должен использоваться, когда ресурс памяти не должен был разделяемым (у него нет конструктора копирования), но он может быть передан другому unique_ptr&lt;/p&gt;
  &lt;p&gt;&lt;a href=&quot;https://en.cppreference.com/w/cpp/memory/shared_ptr&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;shared_ptr&lt;/strong&gt;&lt;/a&gt;: должен использоваться, когда ресурс памяти должен быть разделяемым&lt;/p&gt;
  &lt;p&gt;&lt;a href=&quot;https://en.cppreference.com/w/cpp/memory/weak_ptr&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;weak_ptr&lt;/strong&gt;&lt;/a&gt;: содержит ссылку на объект, которым управляет shared_ptr, но не осуществляет подсчет ссылок; позволяет избавиться от циклической зависимости&lt;/p&gt;
  &lt;p&gt;Приведенный ниже пример демонстрирует unique_ptr. Для передачи владения объектом другому unique_ptr, используйте std::move (эта функция будет обсуждаться в последнем пункте). После передачи владения, интеллектуальный указатель, который передал владение, становится нулевым и get() вернет nullptr.&lt;/p&gt;
  &lt;pre&gt;void foo(int* p)
{
	std::cout &amp;lt;&amp;lt; *p &amp;lt;&amp;lt; std::endl;
}
std::unique_ptr&amp;lt;int&amp;gt; p1(new int(42));
std::unique_ptr&amp;lt;int&amp;gt; p2 = std::move(p1); // transfer ownership

if (p1)
foo(p1.get());

(*p2)++;
if (p2)
foo(p2.get());&lt;/pre&gt;
  &lt;p&gt;Второй пример демонстрирует shared_ptr. Использование похоже, хотя семантика отличается, поскольку теперь владение совместно используемое.&lt;/p&gt;
  &lt;pre&gt;void foo(int* p)
{
}
void bar(std::shared_ptr&amp;lt;int&amp;gt; p)
{
	++(*p);
}
std::shared_ptr&amp;lt;int&amp;gt; p1(new int(42));
std::shared_ptr&amp;lt;int&amp;gt; p2 = p1;

bar(p1);
foo(p2.get());&lt;/pre&gt;
  &lt;p&gt;И, наконец, пример с weak_ptr. Заметьте, что вы должны получить shared_ptr для объекта, вызывая lock(), чтобы получить доступ к объекту.&lt;/p&gt;
  &lt;pre&gt;auto p = std::make_shared&amp;lt;int&amp;gt;(42);
std::weak_ptr&amp;lt;int&amp;gt; wp = p;
{
	auto sp = wp.lock();
	std::cout &amp;lt;&amp;lt; *sp &amp;lt;&amp;lt; std::endl;
}
p.reset();
if (wp.expired())
    std::cout &amp;lt;&amp;lt; &amp;quot;expired&amp;quot; &amp;lt;&amp;lt; std::endl;&lt;/pre&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;p&gt;7 — &lt;strong&gt;лямбды&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;В новом стандарте наконец-то была добавлена поддержка лямбда-выражений. Мы можете использовать лямбды везде, где ожидается функтор или std::function. Лямбда, вообще говоря, представляет собой более короткую запись функтора, что-то вроде анонимного функтора. &lt;/p&gt;
  &lt;pre&gt;std::vector&amp;lt;int&amp;gt; v;
v.push_back(1);
v.push_back(2);
v.push_back(3);

std::for_each(std::begin(v), std::end(v), [](int n) {std::cout &amp;lt;&amp;lt; n &amp;lt;&amp;lt; std::endl;});

auto is_odd = [](int n) {return n%2==1;};
auto pos = std::find_if(std::begin(v), std::end(v), is_odd);
if(pos != std::end(v))
     std::cout &amp;lt;&amp;lt; *pos &amp;lt;&amp;lt; std::endl;&lt;/pre&gt;
  &lt;p&gt;Теперь немного более хитрые — рекурсивные лямбды. Представьте лямбду, представляющую функцию Фибоначчи. Если вы попытаетесь записать ее, используя auto, то получите ошибку компиляции:&lt;/p&gt;
  &lt;pre&gt;auto fib = [&amp;amp;fib](int n) {return n &amp;lt; 2 ? 1 : fib(n-1) + fib(n-2);};

error C3533: &amp;#x27;auto &amp;amp;&amp;#x27;: a parameter cannot have a type that contains &amp;#x27;auto&amp;#x27;
error C3531: &amp;#x27;fib&amp;#x27;: a symbol whose type contains &amp;#x27;auto&amp;#x27; must have an initializer
error C3536: &amp;#x27;fib&amp;#x27;: cannot be used before it is initialized
error C2064: term does not evaluate to a function taking 1 arguments&lt;/pre&gt;
  &lt;p&gt;Здесь имеет место циклическая зависимость. Чтобы избавиться от нее, необходимо явно определить тип функции, используя std::function.&lt;/p&gt;
  &lt;pre&gt;std::function&amp;lt;int(int)&amp;gt; lfib = [&amp;amp;lfib](int n) {return n &amp;lt; 2 ? 1 : lfib(n-1) + lfib(n-2);};&lt;/pre&gt;

</content></entry><entry><id>mrnimda:H1OiHGgXr</id><link rel="alternate" type="text/html" href="https://teletype.in/@mrnimda/H1OiHGgXr?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=mrnimda"></link><title>Класс String</title><published>2019-11-27T16:36:45.866Z</published><updated>2019-11-27T16:36:45.866Z</updated><summary type="html">Конструктор string (string &amp;&amp; str) noexcept подобен конструктору 
копирования в том смысле, что новый объект string является копией объекта str. </summary><content type="html">
  &lt;p&gt;Конструктор &lt;strong&gt;string (string &amp;amp;&amp;amp; str) noexcept&lt;/strong&gt; подобен конструктору &lt;br /&gt;копирования в том смысле, что новый объект &lt;strong&gt;string&lt;/strong&gt; является копией объекта &lt;strong&gt;str&lt;/strong&gt;. &lt;/p&gt;
  &lt;p&gt;Конструктор &lt;strong&gt;string (initializer_list&amp;lt;char&amp;gt; il)&lt;/strong&gt; обеспечивает возможность&lt;br /&gt;списковой инициализации класса string. To есть он делает возможными объявления наподобие следующих:&lt;/p&gt;
  &lt;pre&gt;string piano_man = { &amp;#x27;L &amp;#x27;, &amp;#x27;i1, &amp;#x27;s &amp;#x27;,&amp;#x27;z&amp;#x27;,&amp;#x27;t&amp;#x27;};
string comp_lang {&amp;#x27;L&amp;#x27;, &amp;#x27;i1, &amp;#x27;s&amp;#x27;, &amp;#x27;p&amp;#x27;};&lt;/pre&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;p&gt;Один из аспектов класса, который следует знать — доступные для него варианты ввода.&lt;/p&gt;
  &lt;pre&gt;string stuff;
cin &amp;gt;&amp;gt; stuff; // чтение слова
getline(cin, stuff); // чтение строки с отбрасыванием символа \п&lt;/pre&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;p&gt;Существует несколько ограничений на длину строки. Первый ограничивающий фактор — максимально допустимая длина строки, задаваемая константой&lt;br /&gt;&lt;strong&gt;string: :npos&lt;/strong&gt;. Обычно она равна максимальному значению типа &lt;strong&gt;unsigned int&lt;/strong&gt;, и на&lt;br /&gt;практике этого хватает для обычного интерактивного ввода. Однако проблемы могут возникнуть при попытке считывания содержимого всего файла в один объект типа &lt;strong&gt;string&lt;/strong&gt;. Второй ограничивающий фактор —размер памяти, доступной программе.&lt;/p&gt;
  &lt;p&gt;Функция &lt;strong&gt;getline ()&lt;/strong&gt;, применяемая к классу &lt;strong&gt;string&lt;/strong&gt;, будет читать данные из &lt;br /&gt;входного потока и сохранять их в объекте string до тех пор, пока не произойдет одно из трех событий.&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Будет достигнут конец файла. В этом случае во входном потоке будет установлен флаг eofbit и обе функции fail () и eof () возвратят значение true.&lt;/li&gt;
    &lt;li&gt;Будет достигнут разделительный символ (\п по умолчанию). Этот символ &lt;br /&gt;удаляется из входного потока, но не сохраняется.&lt;/li&gt;
    &lt;li&gt;Будет прочитано максимально возможное количество символов (меньшее из&lt;br /&gt;значений константы string: :npos и доступного количества байтов памяти).&lt;br /&gt;В этом случае во входном потоке будет установлен флаг failbit и функция&lt;br /&gt;fail () возвратит значение true.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;p&gt;Существуют две функции-члена класса string для определения размера строки — &lt;strong&gt;size ( )&lt;/strong&gt; и &lt;strong&gt;length ( )&lt;/strong&gt;.&lt;/p&gt;
  &lt;p&gt;Метод &lt;strong&gt;find_first_of( )&lt;/strong&gt; отыскивает первое вхождение в строке любого из &lt;br /&gt;символов, переданных в аргументах метода. &lt;/p&gt;
  &lt;pre&gt;int where = snakel.find_first_of(&amp;quot;hark&amp;quot;);&lt;/pre&gt;
  &lt;p&gt;&lt;/p&gt;
  &lt;p&gt;Интеллектуальный указатель (&lt;strong&gt;smart pointer&lt;/strong&gt;) — это объект класса, который &lt;br /&gt;действует подобно указателю, но обладает дополнительными возможностями.&lt;/p&gt;
  &lt;p&gt;Если в программе требуется более одного указателя на объект, необходимо выбрать &lt;strong&gt;shared_ptr&lt;/strong&gt;. Если программа не нуждается в нескольких указателях на один и тот же объект, &lt;strong&gt;unique_ptr&lt;/strong&gt; работает вполне успешно. Это хороший вариант для возвращаемого типа функции, которая возвращает указатель на память, выделенную операцией &lt;strong&gt;new&lt;/strong&gt;. Объекты &lt;strong&gt;unique_ptr&lt;/strong&gt; можно сохранять в контейнере &lt;strong&gt;STL&lt;/strong&gt;, если только не требуется вызывать методы или алгоритмы, такие как &lt;strong&gt;sort ()&lt;/strong&gt;, которые копируют или присваивают один объект unique_ptr другому. &lt;/p&gt;
  &lt;p&gt;Объект unique_ptr можно присваивать объекту shared_ptr при соблюдении тех&lt;br /&gt;же условий, при которых один объект unique_ptr допускается присваивать другому — источником должно быть rvalue. &lt;/p&gt;

</content></entry></feed>