Участник:Qwerty787788/плюсы3сем

Материал из Викиконспекты
Перейти к: навигация, поиск

binding arguments of functions

http://www.dreamincode.net/forums/topic/264061-c11-fun-with-functions/ - четкая статья, обо всем написано //осилили

зачем это надо

function objects

function object binder (aka {boost,std}::bind)

C++11 anonymous functions

type erasure

signals (aka listeners aka observers)

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

зачем это нужно

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

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

Видимо наивная реализация - это колбеки (могу ошибаться) Обратный вызов имеет два основных недостатка. Во-первых, он не является типобезопасным. Мы никогда не можем быть уверены что функция делает обратный вызов с корректными аргументами. Можно передавать function<> тогда будет типобезопасно. Во-вторых, обратный вызов жестко связан с вызывающей его функцией, так как эта функция должна точно знать какой обратный вызов надо делать. Также могут возникнуть проблемы, если в процессе обработки одного из коллбэков список подписавшихся на сигнал изменился. Если это вектор, это может привести, к примеру, к выполнению одного коллбэка дважды или пропуску одного из них. Для решения можно сделать из подписавшихся хороший двусвязный список. Также надо иметь в виду, что до обработки сигнала может быть возможно дойти прямо в процессе его же обработки, и нужно соблюсти reentrancy для обработчика.

понятие reentrancy

http://en.wikipedia.org/wiki/Reentrancy_(computing)

пару слов про существующие реализации (boost::signals, boost::signals2, Qt Signals)

signals2 - thread-safe реализация signals http://habrahabr.ru/post/50812/ http://www.rsdn.ru/article/cpp/signals2.xml

exception safety

some link: [1] - слишком много

зачем это нужно

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

RAII

http://habrahabr.ru/post/157315/

ошибки и как их можно обрабатывать (propagation to caller, assertion, logging, precess termination)

propogation to caller - может быть реализован двумя методами: возвратом функцией кода ошибки или бросанием функцией исключения (и его отловом в вызывающем). Такое допускается для ошибок, не связанных с правильностью программы; чаще используется для перехвата обработки неправильных входных данных или ошибки с устройствами ввода/вывода. Срабатывание таких ошибок в программе считается нормальным, предполагается, что такая ошибка может быть корректно обработана.

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

http://habrahabr.ru/post/141080/

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

Precess termination - крайний выход, свалить программу к чертям. Лучше избегать, если иное может, к примеру, нанести вред аппаратуре. Для отладки не годится, поскольку не дает деталей об ошибке сам по себе.

На практике обычно используются комбинации.

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

UNIX-signals (это не тоже самое что signals из пункта 3), hardware interrupts

http://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B5%D1%80%D1%8B%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5 - hardware interrupts

Главное отличие сигналов от других средств взаимодействия между процессами заключается в том, что их обработка программой обычно происходит сразу же после поступления сигнала (или не происходит вообще), независимо от того, что программа делает в данный момент. Сигнал прерывает нормальный порядок выполнения инструкций в программе и передает управление специальной функции – обработчику сигнала. Если обработка сигнала не приводит к завершению процесса, то по выходе из функции-обработчика выполнение процесса возобновляется с той точки, в которой оно было прервано. У программ также есть возможность приостановить обработку поступающих сигналов временно, на период выполнения какой-либо важной операции. В традиционной терминологии приостановка получения определенных сигналов называется блокированием. Если для поступившего сигнала было установлено блокирование, сигнал будет передан программе, как только она разблокирует данный тип сигналов. Этим блокирование отличается от игнорирования сигнала, при котором сигналы соответствующего типа никогда не передаются программе. Следует помнить, что не все сигналы могут быть проигнорированы. Например, при получении программой сигнала принудительного завершения SIGKILL система ничего не сообщает программе, а просто прекращает ее работу. Таким образом, преимущество сигналов перед другими средствами взаимодействия с программой заключается в том, что посылать программе сигналы можно в любой момент ее работы, не дожидаясь наступления каких-то особых условий. Источником сигналов может быть как сам операционная система, так и другие программы пользователя. Если вам показалось, что сигналы похожи на прерывания, то вы совершенно правы. Для реализации сигналов действительно используются программные прерывания.

зачем это нужно

reentrancy

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

multithreading

зачем это нужно

Ну вроде бы очевидно: увеличить скорость выполнения программы. Однако, стоит понимать, что иногда скорость может даже уменьшиться (в интернете есть куча примеров).

Более подробно - это способ задействовать больше аппаратуры для выполнения алгоритма в современных компьютерах, которые хвастаются многоядерностью/многопроцессорностью - так достигается увеличение скорости выполнения. Есть множество задач, которые хорошо делятся на несколько независимых друг от друга. Например, quicksort - после разделения элементов на группы, каждую из них можно сортировать независимо. Но тут важно не переборщить с его использованием, поскольку создание отдельного потока (thread, треда) тоже требует некоторых ресурсов, и сортировать в отдельном треде группы, скажем, по 2 элемента уже очень нерационально, это может как раз замедлить сортировку.

понятие race condition

Стандартный пример, как не нужно использовать multithreading. Пусть есть некоторые общие данные, а два потоки одновременно пытаются выполнить с ними какие-то операции. Например, есть переменная x = 0, два потока пытаются увеличить ее на 1. В итоге может получится как 1, так и 2 (так и вообще непонятно что, см. [2]). Вывод: нужно lock'ать данные перед тем, как их использовать.

atomic operations

В 11х плюсах, чтобы не получались "race condition" есть atomic operations библиотека. Например, можно писать такой код:

atomic<int> counter(0);

++counter // используется одновременно в нескольких потоках

Эта конструкция является thread-safe, т. е. не нужно делать lock на counter.

mutexes (mutex, recuirsive_mutex, shared_mutex)

mutex - объект, который используется для определения, делает ли с некоторым объектом что-то другой поток.

Пример использования. Пусть есть map и треды, которые одновременно в него что-то пишут. Тогда создадим mutex и будем его lock'ать перед тем как использовать map:

  std::map<std::string, std::string> some_map;
  std::mutex some_mutex;
  void write_to_map(std::string key, std::string value) {
    some_mutex.lock();
    some_map[key]=value;
    some_mutex.unlock();
  }

recursive_mutex позволяет треду lock'ать его несколько раз. Пример:

  void f1() {
     _mutex.lock();
     // some code
     _mutex.unlock();
  }
  
  void f2() {
     _mutex.lock();
     f1(); // deadlock here!
     _mutex.unlock();
  }

Если в данном коде использовать обычный mutex, то программа зависнет (получит deadlock), так как f1 не сможет взять lock и будет ждать. При использовании recursive_mutex все будет нормально (поскольку к объекту обращаются с одного и того же треда, f1 сможет взять lock). Снимать lock нужно столько же раз, сколько и брать, иначе другие потоки не смогут работать с данными.

shared_mutex - boost'овая хрень. Читать тут: [3]. Видимо смысл в том, что в обычных мютексах мы не могли читать одновременно из двух потоков, т.к. нужно было взять lock (вдруг кто-то третий начнет изменять данные). А тут можно.

condition_variable

Пусть есть тред t1, который что-то делает. Пусть другие треды должны ждать, пока t1 не закончит выполнение. Чтобы это сделать используются conditional_variable. Пример:

  std::condition_variable cond_var;
  std::mutex m;
  int sum;
  bool done = false;
  
  std::thread th1([&]() {
    std::unique_lock<std::mutex> lock(m);
    for (int i = 0; i < 5; ++i) {
      sum++;
    }
    done = true;
    cond_var.notify_one();
  }); 
  
  std::thread th2([&]() {
    std::unique_lock<std::mutex> lock(m);
    if (!done)
      cond_var.wait(m);
    std::cout << sum;
  }); 
  
  int main() {
    th1.join();
    th2.join();
  }

Важно, что второй тред уже взял lock на m. Т. е. cond_var.wait(m) делает следующие: отдает lock, ждет, когда кто-то сделает cond_var.notify_one() или cond_var.notify_all(), забирает lock обратно, продолжает работу программы. notify_all отличается от notify_one тем, что он будит сразу все треды, которые ждут, а не только один (случайный?).

понятие dead lock

Первый тред взял lock на A, второй на B. Первый хочет что-то сделать с B, второй с A. Оба ждут.

futures

Если тред бросает исключение, но не ловит его, скорее всего, вся программа упадет. Чтобы такого не случилось, можно использовать futures/promises. А вообще future используется для получение некоторого значения, которое должен вернуть тред. Пример:

  auto promise = std::promise<std::string>();
  
  auto producer = std::thread([&] {
    promise.set_value("Hello World");
  });
  
  auto future = promise.get_future();
  
  auto consumer = std::thread([&] {
    std::cout << future.get();
  });
  
  producer.join();
  consumer.join();

Из примера вроде понятно, что происходит. future_get() ждет, пока другой тред не вернет какое-нибудь значение. Если что-то случилось, то можно в promise положить ошибку:

  promise.set_exception(runtime_error(“message”));

и нормально ее обработать в другом треде.

TLS (thread local storage)

Если хочется иметь для каждого треда свои отдельные данные, то в 11х плюсах есть THL. Пример:

  thread_local int n;
  void f() {
    std::cout<< ++n << " ";
  }
  int main {
    std::thread t1(f);
    std::thread t2(f);
   
    t1.join();
    t2.join();
  }

Программа выведет "1 1 " в любом случае.

asynchronous operations

зачем это нужно

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

Пример. Если в главном треде приложения обрабатывается интерфейс пользователя, выполнение запроса к БД (на удалённом сервере) прямо в этом же треде может привести к зависанию всего интерфейса. Если во время этого запроса программа ничего больше не должна делать в принципе - это допустимо (но плохо, потому что интерфейс перестаёт реагировать на любые действия, пользователь или ОС могут посчитать программу зависшей), но встречаются более сложные интерфейсы, в которых во время такого запроса можно делать что-то ещё - подкручивать настройки, например. В этом случае запрос необходимо делать асинхронно.

как это работает

1) можно периодически спрашивать процесс на закончился ли он? 2) можно всякие прерывания юзать 3) ну и главное с помощью многопоточности

механизмы предоставляемые ОС (select, poll, epoll, kqueue, IOCP)

http://en.wikipedia.org/wiki/Asynchronous_I/O#Select.28.2Fpoll.29_loops http://en.wikipedia.org/wiki/IOCP

remote procedure call

зачем это нужно

общаться между процессами, и даже вызывать с одного компа процессы на другом компе

apartment threading model

http://www.rsdn.ru/article/com/apartmnt.xml http://www.introligator.org/articles/3/84 - понятнее

reentrancy

ну в free threaded сам заботься, а apartment сам все сделает

достоинства/недостатки apartment threading model

Главным достоинством апартамента STA является то, что разработка объекта COM и клиентской части проста в том смысле, что нет нужды заботиться о синхронизации вызовов методов и их выполнения: следующий метод начнет выполняться только после того, как закончится выполнение предыдущего. Аналогично не нужно заботиться о синхронизации доступа к данным объекта.

Главным недостатком апартамента STA является его неэффективность. Даже если синхронизация вызовов методов и не нужна, она все равно выполняется.

free threaded threading model

http://www.rsdn.ru/article/com/apartmnt.xml http://www.introligator.org/articles/3/84 - понятнее(тоже самое)

достоинства/недостатки free threaded threading model

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

пару слов про реализации (COM, CORBA, D-Bus)

fibers

это просто реализация концепции сопрограмм http://msdn.microsoft.com/en-us/library/windows/desktop/ms682661(v=vs.85).aspx

http://ru.wikipedia.org/wiki/%D0%A1%D0%BE%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B0 - короче это просто реализация этого принципа. Была нить ее разхреначили на волокна и идут сначала по одному - потом переключились на другой, поделали чето там, вернулись и продолжили идти по первому, например.

зачем это нужно

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

coroutines

http://www.crystalclearsoftware.com/soc/coroutine/

generators

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

как это работает

возвращение множества значений требует только вызова сопрограммы несколько раз.

FLS (fiber local storage)

у каждого фибера есть ствой локал сторадж ФИБЕР МОГУТ ВЫПОЛНЯТЬ РАЗНЫЕ ПОТОКИ

С++11

см. http://gcc.gnu.org/projects/cxx0x.html и http://www.stroustrup.com/C++11FAQ.html

RUS:http://sergeyteplyakov.blogspot.ru/2012/05/c-11-faq.html#auto // круто

про каждую фичу надо говорить, зачем это нужно, как это работает

C++1y and beyond

static_if

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3329.pdf

modules

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3347.pdf

concepts

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

<source lang=Cpp> auto concept LessThanComparable<typename T> {

   requires SomeConcept<T>;
   bool operator<(T, T);

} </source>

  • Ключевое слово auto указывает, что проверять на совместимость с концептом надо любые типы. Всё, что подошло по условию, концептом поддерживается.
  • concept - ключевое слово "это концепт". С уважением, капитан Очевидность.
  • LessThanComparable - собственно, название концепта, а в угловых скобках - аналогично объявлению шаблона, удовлетворяющие ему типы. Далее в фигурных скобках идёт описание концепта.
  • requires - ключевое слово, которое можно применить и при объявлении шаблона - требует выполнения типом T этого концепта другого концепта SomeConcept. Именно типом T, поскольку их может быть несколько.
  • Объявление функции - обязывает претендента на совместимость с концептом такую функцию иметь в ближайшем доступе - будь то где-то снаружи или внутри класса.

Применить его можно так: <source lang=Cpp> template<LessThanComparable T> const T& min(const T &x, const T &y) {

   return (y < x) ? y : x;

} </source> Или так: <source lang=Cpp> template<typename T> requires LessThanComparable<T> const T& min(const T &x, const T &y) {

   return (y < x) ? y : x;

} </source> Последний способ лучше тем, что позволяет указать для типа несколько требований одновременно без необходимости создавать новый концепт, содержащий их все. К слову, концепты можно и наследовать, хотя это и аналогично применению requires.

Концепты были убраны из чернового стандарта C++11 в июле 2009 года. Однако опенсорс подхватил идею и реализовал её в ConceptGCC, форке GNU C++ compiler.

Остальное можете почитать здесь: http://en.wikipedia.org/wiki/Concepts_(C%2B%2B)

ranges

http://www.boost.org/doc/libs/1_52_0/libs/range/doc/html/index.html -- а может это и не оно, хз

зачем это нужно

как это работает