Любимая тема - кеш (который cache, а не наличка).
Если взять современный проц, то при частоте скажем 3ГГц будет 3 миллиарда тиков. Упрощенно, за тик делается одна простая операция. Так вот, тик настолько мал, что за его время фотон пройдет только около 10 см (!). И конечно это, кроме прочего, увеличивает время чтения данных из памяти (RAM) до 500 тиков. Чтобы это ускорить, прямо на чипе проца есть своя маленькая память для кеша, она быстрее, но дороже, а потому меньше по доступному объему (примерно в 1000 раз) чем основная память.
Если данные уже в кеше, то чтение займет всего несколько тиков. Если же данных в кеше нет (это называется промах), то исполнение приложения может остановиться на около 100 наносекунд для копирования данных из памяти в кеш. Поэтому важно соблюдать локальность данных. Временная локальность - когда к одним и тем же данным обращаются очень близко по времени. Пространственная локальность - когда данные лежат рядом с друг другом (по соседним адресам). Процессор копирует данные в кеш не побайтово, а кусками (cache line), типичным размером куска является 64 байта. Поэтому линейный последовательный перебор массива/вектора может оказаться значительно быстрее разбросанного в пространстве списка.
#hardware
Если взять современный проц, то при частоте скажем 3ГГц будет 3 миллиарда тиков. Упрощенно, за тик делается одна простая операция. Так вот, тик настолько мал, что за его время фотон пройдет только около 10 см (!). И конечно это, кроме прочего, увеличивает время чтения данных из памяти (RAM) до 500 тиков. Чтобы это ускорить, прямо на чипе проца есть своя маленькая память для кеша, она быстрее, но дороже, а потому меньше по доступному объему (примерно в 1000 раз) чем основная память.
Если данные уже в кеше, то чтение займет всего несколько тиков. Если же данных в кеше нет (это называется промах), то исполнение приложения может остановиться на около 100 наносекунд для копирования данных из памяти в кеш. Поэтому важно соблюдать локальность данных. Временная локальность - когда к одним и тем же данным обращаются очень близко по времени. Пространственная локальность - когда данные лежат рядом с друг другом (по соседним адресам). Процессор копирует данные в кеш не побайтово, а кусками (cache line), типичным размером куска является 64 байта. Поэтому линейный последовательный перебор массива/вектора может оказаться значительно быстрее разбросанного в пространстве списка.
#hardware
👍2❤1
Пару слов про нейминг и лицензии. Есть вот такой крутой технически проект, Peredvizhnikov Engine, полностью lock-free игровой движок на C++20. Сделан на модели акторов поверх корутин. При реализации задействованы: транзакционная память, lock-free очередь, lock-free сериализация, lock-free std::atomic_shared_ptr, lock-free аллокатор и тд. Как несложно догадаться фамилия автора движка — нет, не Передвижников, а Пермяков 😆.
Собственно, суть поста: сравните название любого популярного движка (Unity, Unreal, Godot, да даже наш Unigine) c Peredvizhnikov Engine с точки зрения иностранца. А теперь еще и посмотрите на лицензию - GPL (даже не LGPL!), которая не позволит сделать на нем закрытую игру (много ли в топе стима игр под GPL?).
https://github.com/eduard-permyakov/peredvizhnikov-engine
#github
Собственно, суть поста: сравните название любого популярного движка (Unity, Unreal, Godot, да даже наш Unigine) c Peredvizhnikov Engine с точки зрения иностранца. А теперь еще и посмотрите на лицензию - GPL (даже не LGPL!), которая не позволит сделать на нем закрытую игру (много ли в топе стима игр под GPL?).
https://github.com/eduard-permyakov/peredvizhnikov-engine
#github
GitHub
GitHub - eduard-permyakov/peredvizhnikov-engine: A fully lock-free game engine written in C++20
A fully lock-free game engine written in C++20. Contribute to eduard-permyakov/peredvizhnikov-engine development by creating an account on GitHub.
🌚1
На картинке два примера того, как процессор (не компилятор) предсказывает ветвление/переход и продолжает выполнение кода дальше, будто бы оно истинно (спекулятивное исполнение, чтобы не тормозить конвейер).
В первой строчке указатель как правило не нулевой, а потому можно (выгодно) продолжить выполнение кода в большинстве случаев. В 4-й строчке массив v очень большой и потому условие for будет нарушено только один раз, а потому выгодно считать себе дальше, а потом ненужное при промахе просто отбросить.
Но! Что же делать с ошибкой предсказания. Ведь в первом случае мы разыменуем нулевой указатель, а во втором изменим не "нашу" память. Как процессор обрабатывает эту ситуацию? Вот так: если произошла ошибка, то она удерживается, пока ветка не будет проверена, а все изменения в памяти не сбрасываются в основную память до окончания проверки (они удерживаются в буферах записи).
Подробности: https://youtu.be/g-WPhYREFjk?si=m2bjcQ7MbV_4tOKc&t=1284
#hardware
В первой строчке указатель как правило не нулевой, а потому можно (выгодно) продолжить выполнение кода в большинстве случаев. В 4-й строчке массив v очень большой и потому условие for будет нарушено только один раз, а потому выгодно считать себе дальше, а потом ненужное при промахе просто отбросить.
Но! Что же делать с ошибкой предсказания. Ведь в первом случае мы разыменуем нулевой указатель, а во втором изменим не "нашу" память. Как процессор обрабатывает эту ситуацию? Вот так: если произошла ошибка, то она удерживается, пока ветка не будет проверена, а все изменения в памяти не сбрасываются в основную память до окончания проверки (они удерживаются в буферах записи).
Подробности: https://youtu.be/g-WPhYREFjk?si=m2bjcQ7MbV_4tOKc&t=1284
#hardware
👍1
Увидела такой пример про использование оператора typeid, который возвращает ссылку на объект типа std::type_info с описанием типа. Поймала себя на мысли, что ни разу не встречала использования typeid в проде (что наверное хорошо, ведь скорей всего typeid это результат плохой архитектуры).
dynamic_cast приходилось использовать, но как правило в конкретных реализациях, а не в коде, где используются абстракции.
Вообще, если нужна какая-то иерархия, где вот прям куча классов наследуют интерфейс, возвращаю enum в виртуальном методе базового класса и дальше static_cast по значению enum. Кажется, это самое приемлемое решение, если все же нужно кастить. Так лучше по производительности, ведь тип по сути уже лежит в vtable.
#cpp #архитектура
dynamic_cast приходилось использовать, но как правило в конкретных реализациях, а не в коде, где используются абстракции.
Вообще, если нужна какая-то иерархия, где вот прям куча классов наследуют интерфейс, возвращаю enum в виртуальном методе базового класса и дальше static_cast по значению enum. Кажется, это самое приемлемое решение, если все же нужно кастить. Так лучше по производительности, ведь тип по сути уже лежит в vtable.
#cpp #архитектура
👍2❤1
Как средствами стандартной библиотеки проще всего удалить их контейнера дубликаты? Воспользоваться функцией std::unique. Она перемещает повторные элементы в конец контейнера, оставляя на месте только первый уникальный. НО! она делает проверку не по всему контейнеру, а только по соседним равным элементам, например если есть последовательность {1 3 3 3 5}, то она сможет переместить лишние 3-ки. Проще всего получить непрерывные последовательности одинаковых элементов путем сортировки контейнера (хотя это и не обязательно). Также особенностью unique является то, что работает она "на месте" не создавая новый контейнер, а возвращая итератор на конец диапазона уникальных значений. Пример:
В коде выше <*> показан итератор, возвращаемый функцией. Как видим, после итератора расположены повторяющиеся элементы. В последней строке мы копируем уникальные элементы в новый вектор.
Что будет, если убрать сортировку? std::unique отработате неверно, результатом будет uniqueV = {1, 5, 7, 8, 5, 1, 8, 3, 5}.
Причина, почему unique просматривает только соседние элементы, как обычно кроется в эффективности - O(n).
#cpp
std::vector<int> v = {1, 5, 7, 8, 5, 1, 8, 3, 5};
std::sort(v.begin(), v.end()); // {1, 1, 3, 5, 6, 7, 7, 8, 8}
auto endUnique = std::unique(v.begin(), v.end()); // {1, 3, 5, 7, 8, <*> 5, 7, 8, 8}
std::vector<int> uniqueV(v.begin(), endUnique); // {1, 3, 5, 7, 8}
В коде выше <*> показан итератор, возвращаемый функцией. Как видим, после итератора расположены повторяющиеся элементы. В последней строке мы копируем уникальные элементы в новый вектор.
Что будет, если убрать сортировку? std::unique отработате неверно, результатом будет uniqueV = {1, 5, 7, 8, 5, 1, 8, 3, 5}.
Причина, почему unique просматривает только соседние элементы, как обычно кроется в эффективности - O(n).
#cpp
👍1
Micro$oft в своем амплуа. Две комплементарные функции GetUserName и GetComputerName, первая возвращает строку С нулевым символом в конце, вторая БЕЗ. Почему? Да хз почему, но хоть в доке описано 😆.
#include <windows.h>#winapi
#include <Lmcons.h>
wchar_t buf[UNLEN + 1] = {0};
DWORD bufLen = UNLEN + 1;
if (GetUserNameW(buf, &bufLen)) {
userName = std::wstring(buf, buf + bufLen - 1); // includes extra \0
}
bufLen = UNLEN + 1;
if (GetComputerNameW(buf, &bufLen)) {
computerName = std::wstring(buf, buf + bufLen); // no extra \0
}
❤2👏1
Немного неожиданное поведение и, на мой взгляд, не совсем удачные названия. Всем известно, что std::string::find ищет первую подстроку в строке, тут все понятно. А теперь посмотрим на результат вызова std::string::find_first_of:
#cpp
std::string str("How to Lose a Guy in 10 Days");
auto pos1 = str.find("10"); // 21
auto pos2 = str.find_first_of("0123456789"); // 21
В обоих случаях результат одинаковый - 21. Дело в том, что find_first_of ищет любой элемент из аргумента в строке и возвращает позицию первого найденного. В данном случае мы ищем первую цифру (0-9) в строке. Код конечно компактный, но вот я бы назвала такую функцию find_any_of.#cpp
❤3
std::any - это безопасный void* с контролем времени жизни, проверкой типа при доступе и другими возможностями. Например, можно проверить, выставлено ли значение с помощью has_value и у нас получится функционал std::optional. Используя std::any_cast можно получить не только сами данные, но и ссылку на данные или даже указатель. А также можно запросить тип данных в рантайме. Код:
1. any не шаблонный класс (в отличие от std::variant)
2. если типы не фиксированы заранее (иначе лучше то std::variant)
Вообще, не фиксированность типов редкая штука, но возможно бывают случаи, когда нужно разрешить клиенту вашего кода хранить что угодно и отдать тип на откуп ему же.
#cpp
std::any a;Зачем же нужен any, если есть optional и variant?
std::cout << a.has_value();
a = 1; // храним int
a = std::string("Mary"); // а теперь строку
try
{
std::cout << std::any_cast<std::string>(a); // Mary
std::any_cast<std::string&>(a) = "Masha"; // кастуем до ссылки
std::cout << std::any_cast<std::string>(a); // Masha
a = 2.0; // поменяем на double
double * ap = std::any_cast<double>(&a); // указатель на данные
std::cout << *ap; // 2.0
if (a.type() == typeid(double)) { // проверяем тип
std::cout << "I'm double";
}
}
catch (const std::bad_any_cast& e) // any_cast не сработал
{
std::cout << e.what() << std::endl;
}
1. any не шаблонный класс (в отличие от std::variant)
2. если типы не фиксированы заранее (иначе лучше то std::variant)
Вообще, не фиксированность типов редкая штука, но возможно бывают случаи, когда нужно разрешить клиенту вашего кода хранить что угодно и отдать тип на откуп ему же.
#cpp
❤5
Почему важно изучать свежие источники про C++, где описаны ноые стандарты? Читаю в одном месте, что в лямбдах нельзя делать аргументы по умолчанию. Думаю, что за ерунда, в чем техническая проблема? Оказалось, что начиная с С++14 можно так делать:
#cpp14
Кстати, cout не нужно захватывать в лямбдах, так как они могут непосредственно использовать статические локальные переменные и переменные, объявленные вне функции.
auto l = [](int i = 4){
std::cout << i;
return i;
};
std::cout << l() << std::endl; // 44
#cpp14
👍2
Существуют как минимум 2 оптимизации, связанные с возвращением значения:
- RVO (Return Value Optimization),
- the NRVO (Named Return Value Optimization)
Рассмотрим код:
В теории, может быть создано 3 объекта типа T:
1. временный объект внутри f(),
2. временный объект, возвращенный из f, созданный копированием объекта 1
3. именованный объект t, созданный копированием объекта 2
RVO позволяет компилятору убрать 2 временных, напрямую инициализировав t аргументами из f(). Для того, чтобы RVO работало, возвращённый объект должен быть сконструирован при return, то есть объект не должен иметь имени.
Named RVO же может удалить промежуточный объект даже если у возвращенного объекта есть имя, то есть объект не сконструирован в return. Пример:
Примечание: нет гарантии, что такие оптимизации будут применены всегда, однако начиная с C++17 есть случаи, когда компилятор обязан применить RVO.
#cpp
- RVO (Return Value Optimization),
- the NRVO (Named Return Value Optimization)
Рассмотрим код:
T f()
{
return T(constructor arguments);
}
T t = f();
В теории, может быть создано 3 объекта типа T:
1. временный объект внутри f(),
2. временный объект, возвращенный из f, созданный копированием объекта 1
3. именованный объект t, созданный копированием объекта 2
RVO позволяет компилятору убрать 2 временных, напрямую инициализировав t аргументами из f(). Для того, чтобы RVO работало, возвращённый объект должен быть сконструирован при return, то есть объект не должен иметь имени.
Named RVO же может удалить промежуточный объект даже если у возвращенного объекта есть имя, то есть объект не сконструирован в return. Пример:
T f()
{
T result(....);
....
return result;
}
Примечание: нет гарантии, что такие оптимизации будут применены всегда, однако начиная с C++17 есть случаи, когда компилятор обязан применить RVO.
#cpp
На первый взгляд, разница в двух лямбдах ниже небольшая, во второй просто захватываем аргумент по ссылке:
Однако же выведутся разные значения: 100 0, так как первая лямбда скопирует i в момент создания, а не вызова. Вторая тоже "скопирует" ссылку в момент создания, но значение ссылки получит во время вызова.
Если же мы захотим изменить значение i, то в первую лямбду нужно добавить mutable, а для второй это делать необязательно:
#cpp #lambda
int i = 100;
auto f1 = [ i](){ return i; };
auto f2 = [&i](){ return i; };
i = 0;
std::cout << f1() << " " << f2() << std::endl;
Однако же выведутся разные значения: 100 0, так как первая лямбда скопирует i в момент создания, а не вызова. Вторая тоже "скопирует" ссылку в момент создания, но значение ссылки получит во время вызова.
Если же мы захотим изменить значение i, то в первую лямбду нужно добавить mutable, а для второй это делать необязательно:
auto f1 = [ i]() mutable { return ++i; };
auto f2 = [&i]() { return ++i; };#cpp #lambda
🔥4
Только что подумала, что пустую лямбду достаточно удобно набирать, так как клавиши расположены рядом и даже Shift надо один раз удерживать:
Чтоб пост не был таким коротким, пример подсчета числа элементов, удовлетворящих предикату:
[](){}Чтоб пост не был таким коротким, пример подсчета числа элементов, удовлетворящих предикату:
std::vector<std::string> v = {"abc", "a", "ab", "c", "sd", "sjc"};
std::cout << std::count_if(v.begin(), v.end(),
[](const std::string &str)
{ return str.size() == 3; }); // 2Допустим у нас имеется некая функция, скажем isClose с двумя параметрами, и нам нужно использовать ее для некоего алгоритма, скажем find_if, который ожидает вызываемый объект с одним параметром. Альтернативой лямбде может стать использование std::bind:
bind получает вызываемый объект и создает новый вызываемый объект, адаптируя список параметров исходного. В данном случае, создастся isCloseToOne, который будет вызывать isClose, прокидывая первый аргумент (placeholders::_1) в isClose, а вторым аргументом будет скопированное значение maxDistance.
#include <functional>
size_t distance(const std::string& str1, const std::string& str2)
{
// TODO: Левенштейн
return str1.size() - str2.size();
}
bool isClose(const std::string& str1, size_t n)
{
return distance(str1, "ATGA") <= n;
}
int main()
{
size_t maxDistance = 1;
auto isCloseToOne = std::bind(isClose, std::placeholders::_1, maxDistance);
maxDistance = -1; // не влияет на копию
std::cout << isCloseToOne("CACAC") << std::endl; // 1
std::vector<std::string> v = { "ACGTTA", "GTTA", "CCATAT" };
auto it = std::find_if(
v.cbegin(), v.cend(),
// isClose); // ошибка: too few arguments
isCloseToOne); // один аргумент
if (it != v.cend()) std::cout << *it; // GTTA
return 0;
}
bind получает вызываемый объект и создает новый вызываемый объект, адаптируя список параметров исходного. В данном случае, создастся isCloseToOne, который будет вызывать isClose, прокидывая первый аргумент (placeholders::_1) в isClose, а вторым аргументом будет скопированное значение maxDistance.
❤3👍1
std::bind можно использовать для перестановки аргументов. Допустим, мы имеем некую функцию func с шестью аргументами. Сделаем новый вызываемый объект, так что его первый аргумент _1 станет пятым, а второй _2 первым и шестым:
Вызовы func2 и func приведут к одинаковым результатам. Знакоместа _1 и _2 займут указанные позиции в func. Как видим, мы можем менять не только порядок, но и повторять аргументы.
#include <functional>
void func(
const std::string& s1, // 1 => _2
int i, // 2
float f, // 3
double d, // 4
char c, // 5 => _1
const std::string& s2) // 6 => _2
{
std::cout << s1 << i << f << d << c << s2 << std::endl;
}
int main()
{
using namespace std::placeholders;
auto func2 = std::bind(func, _2, 1, 2.0f, 3.0, _1, _2);
func2('a', "___"); // ___123a___
func("___", 1, 2.0f, 3.0, 'a', "___"); // ___123a___
return 0;
}
Вызовы func2 и func приведут к одинаковым результатам. Знакоместа _1 и _2 займут указанные позиции в func. Как видим, мы можем менять не только порядок, но и повторять аргументы.
👍3🤯3❤1
std::string::find возврашает позицию найденной подстроки. Если такой строки нет, то вернется std::string::npos, что видимо означает "no pos".
Причина, по которой это поле не назвали nopos, дабы повысить читаемость за счет +1 символа, далека от моего понимания.
Интересно, что беззнаковому npos присваивается знаковое -1:
Такой каст приводит к получению максимального значения для этого типа. На x86-64 у меня получилось 18446744073709551615. Давайте проверим:
Раз так можно в std, то можно и нам пихать -1 в size_t 😁
#cpp #string
Причина, по которой это поле не назвали nopos, дабы повысить читаемость за счет +1 символа, далека от моего понимания.
Интересно, что беззнаковому npos присваивается знаковое -1:
cpp static constexpr auto npos{static_cast<size_type>(-1)};Такой каст приводит к получению максимального значения для этого типа. На x86-64 у меня получилось 18446744073709551615. Давайте проверим:
#include <iostream>
#include <string>
int main()
{
std::string str("abc");
auto pos = str.find("c");
if (pos != str.npos) {
std::cout << "found at: " << pos << std::endl;
}
std::cout << std::numeric_limits<std::string::size_type>::max() << std::endl; // 18446744073709551615
std::cout << std::string::npos << std::endl; // 18446744073709551615
std::cout << static_cast<size_t>(-1) << std::endl; // 18446744073709551615
return 0;
}
Раз так можно в std, то можно и нам пихать -1 в size_t 😁
#cpp #string
🔥2
Добью тему std::bind для закрепления. В примере ниже сортируем вектор строк по длине (кажется, сортировка не стабильная). А затем переиспользуем функцию isShorter, переставив параметры местами с помощью bind, так что у нас получается "обратная" сортировка - по убыванию длины.
#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <algorithm>
bool isShorter(const std::string& str1, const std::string& str2)
{
return str1.size() < str2.size();
}
int main()
{
std::vector<std::string> v = { "ACGTTAC", "GTTA", "CCATAT" };
std::sort(v.begin(), v.end(), isShorter);
for (const auto& str : v) {
std::cout << str << " "; // GTTA CCATAT ACGTTAC
}
std::cout << std::endl;
std::sort(v.begin(), v.end(),
std::bind(isShorter, std::placeholders::_2, std::placeholders::_1));
for (const auto& str : v) {
std::cout << str << " "; // ACGTTAC CCATAT GTTA
}
return 0;
}
🔥2🏆1
Встроенные пространства имен (inline namespace) неявно вызывают директиву using для вложенных неймспейсов. Когда осуществляется поиск имени, члены встроенного пространства используются вперед, даже если такое имя уже объявлено.
В примере ниже, если скомпилировать с USE_INLINE_B, то выведется 1, иначе 2:
Inline namespaces обычно используются для версионности, например в библиотеках (ABI).
Еще пример, в котором доступны outer::inner::foo() и outer::foo():
В примере ниже, если скомпилировать с USE_INLINE_B, то выведется 1, иначе 2:
namespace A {
#if USE_INLINE_B
inline
#endif
namespace B {
int foo(bool) { return 1; }
}
int foo(int) { return 2; }
}
int main(void) {
return A::foo(true);
}Inline namespaces обычно используются для версионности, например в библиотеках (ABI).
Еще пример, в котором доступны outer::inner::foo() и outer::foo():
namespace outer {
inline namespace inner {
void foo();
} // namespace inner
} // namespace outer👍4
Увидела на реддит список (довольно базовых?) вопросов с одного собеседования на C++ разработчика. Может кому-то будет полезно/интересно.
Основы C++:
* Разница между ссылкой и указателем?
* Разница между выделением памяти на куче и стеке?
* Какие типы умных указателей существуют?
* Как реализован unique_ptr? Как сделать, чтоб у объекта был только один владелец?
* Как работает shared_ptr ? Как синхронизируется подсчет ссылок?
* Как можно скопировать unique_ptr или передать от одного объекта другому?
* Что такое rvalue и lvalue?
* Что такое std::move и std::forward() ?
ООП:
* Способы получить доступ к приватным полям некоторого класса?
* Может ли класс наследовать множество классов?
* Инициализируются ли статические поля в конструкторе класса?
* Может ли исключение быть выброшенным в конструкторе/деструкторе? Как это предотвратить?
* Что такое виртуальный метод?
* Зачем нужен виртуальный деструктор?
* Разница между абстрактным классом и интерфейсом?
* Может ли конструктор быть виртуальным?
* Как ключевое слово const используется для методов класса?
* Как защитить объект от копирования?
STL контейнеры:
* Разница между vector и list?
* Разница между map и unordered_map?
* Когда вызов push_back() инвалидирует итератор vector?
* Как изменить свой класс, чтоб использовать с map/unordered_map?
Потоки:
* Разница между процессом и потоком?
* Может ли поток быть запущен дважды?
* Способы синхронизации потоков?
* Что такое дедлок?
#cpp #собеседования
Основы C++:
* Разница между ссылкой и указателем?
* Разница между выделением памяти на куче и стеке?
* Какие типы умных указателей существуют?
* Как реализован unique_ptr? Как сделать, чтоб у объекта был только один владелец?
* Как работает shared_ptr ? Как синхронизируется подсчет ссылок?
* Как можно скопировать unique_ptr или передать от одного объекта другому?
* Что такое rvalue и lvalue?
* Что такое std::move и std::forward() ?
ООП:
* Способы получить доступ к приватным полям некоторого класса?
* Может ли класс наследовать множество классов?
* Инициализируются ли статические поля в конструкторе класса?
* Может ли исключение быть выброшенным в конструкторе/деструкторе? Как это предотвратить?
* Что такое виртуальный метод?
* Зачем нужен виртуальный деструктор?
* Разница между абстрактным классом и интерфейсом?
* Может ли конструктор быть виртуальным?
* Как ключевое слово const используется для методов класса?
* Как защитить объект от копирования?
STL контейнеры:
* Разница между vector и list?
* Разница между map и unordered_map?
* Когда вызов push_back() инвалидирует итератор vector?
* Как изменить свой класс, чтоб использовать с map/unordered_map?
Потоки:
* Разница между процессом и потоком?
* Может ли поток быть запущен дважды?
* Способы синхронизации потоков?
* Что такое дедлок?
#cpp #собеседования
👍7
Адаптер вставки - адаптер итератора, получающий контейнер и возвращающий итератор, позволяющий вставлять элементы в указанный контейнер.
Адаптер back_inserter создает итератор, использующий функцию push_back, тогда как front_inserter использует push_front. Пример:
Адаптер back_inserter создает итератор, использующий функцию push_back, тогда как front_inserter использует push_front. Пример:
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
int main()
{
std::vector<int> v = { 1, 2, 3, 4, 5 };
std::list<int> list1, list2;
copy(v.cbegin(), v.cend(), std::front_inserter(list1));
copy(v.cbegin(), v.cend(), std::back_inserter(list2));
for (auto i : list1)
std::cout << i << " "; // 5 4 3 2 1
std::cout << std::endl;
for (auto i : list2)
std::cout << i << " "; // 1 2 3 4 5
return 0;
}
👍4
Время жизни объектов в C++ можно разделить на 4 категории:
1. Статическое время жизни
Все время работы приложения, выделяются в начале запуска и освобождаются при завершении. Сюда входят глобальные переменные, статические члены, статические локальные переменные.
2. Потоковое время жизни
Объекты существуют в течение жизни потока, к которому они принадлежат. Создаются при запуске потока и разрушаются при выходе потока. Указываются ключевым словом thread_local:
3. Автоматическое время жизни
Создаются в момент определения и разрушаются при выходе из области видимости. Более известны как локальные объекты/объекты на стеке. Например локальные нестатические переменные и параметры функции.
4. Динамическое время жизни
Создаются в рантайме с использованием функци выделения памяти как new, malloc и тп. Временем жизни таких объектов нужно управлять вручную.
1. Статическое время жизни
Все время работы приложения, выделяются в начале запуска и освобождаются при завершении. Сюда входят глобальные переменные, статические члены, статические локальные переменные.
2. Потоковое время жизни
Объекты существуют в течение жизни потока, к которому они принадлежат. Создаются при запуске потока и разрушаются при выходе потока. Указываются ключевым словом thread_local:
thread_local int my_var; // Thread storage duration
3. Автоматическое время жизни
Создаются в момент определения и разрушаются при выходе из области видимости. Более известны как локальные объекты/объекты на стеке. Например локальные нестатические переменные и параметры функции.
4. Динамическое время жизни
Создаются в рантайме с использованием функци выделения памяти как new, malloc и тп. Временем жизни таких объектов нужно управлять вручную.
👍1👏1