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
Не моя вина, но сегодня у меня день рожденюшко. Сделоть подарок можно денюжным переводом (н-р через TG Wallet).
TON: UQCinAVCbNfOxvOfsyBjtmKrZoQr6t1tQ5A-XLS3XwJanTwr
USDT: TCyFuko2mxtduRc9SgPkZtPWUk7cPz3vZt
BTC:
1Ce8eJmJNDUxHdnqQGfgBVkLzixSBhEsaC
TON: UQCinAVCbNfOxvOfsyBjtmKrZoQr6t1tQ5A-XLS3XwJanTwr
USDT: TCyFuko2mxtduRc9SgPkZtPWUk7cPz3vZt
BTC:
1Ce8eJmJNDUxHdnqQGfgBVkLzixSBhEsaC
🎉6
Давайте рассмотрим на первый раз простую задачу - итерирование вектора в обратном порядке, то есть от конца к началу. Для начала возьмем просто индекс:
Вроде бы все хорошо, но на моей машине в int максимум поместится 2147483647, тогда как в size_type вектора (обычно size_t) уже 18446744073709551615.
Давайте исправим ситуацию и возьмем size_t в качестве индекса:
А есть ли способ лучше, через итераторы? Да - реверсивный итератор перебирает контейнер с конца в начало. Для этого доступны rbegin/rend:
Хочу отметить, что здесь нет ошибки и реверсивный итератор действительно инкрементируется, то есть продвигается наоборот. Почему, расскажу в другой раз😉.
std::vector<int> v = {1,2,3,4,5};
const std::vector<int>::size_type size = v.size();
int i;
for (i = size - 1; i != -1; --i) {
std::cout << v[i]; // 54321
}
std::cout << std::endl << i << std::endl; // -1Вроде бы все хорошо, но на моей машине в int максимум поместится 2147483647, тогда как в size_type вектора (обычно size_t) уже 18446744073709551615.
Давайте исправим ситуацию и возьмем size_t в качестве индекса:
size_t j;
for (j = size - 1; j != -1; --j) {
std::cout << v[j]; // 54321
}
std::cout << std::endl << j << std::endl; // 18446744073709551615
А есть ли способ лучше, через итераторы? Да - реверсивный итератор перебирает контейнер с конца в начало. Для этого доступны rbegin/rend:
for (auto it = v.rbegin(); it != v.rend(); ++it) {
std::cout << *it;
}Хочу отметить, что здесь нет ошибки и реверсивный итератор действительно инкрементируется, то есть продвигается наоборот. Почему, расскажу в другой раз😉.
Так как реверсивный итератор перебирает контейнер от последнего элемента к первому, то смысл инкремента инвертирован:
Для получения реверсивного итератора существуют rbegin()/rend(), а также константные аналоги crbegin()/crend(). rbegin указывает на последний элемент контейнера, а rend на элемент перед контейнером, аналогично обычным итераторам, просто наоборот. Изображение пояснит соотношение на векторе.
Возможно, вам покажется неправильным, что ++it идет на предыдущий элемент, но это позволяет использовать все стандартные алгоритмы задом наперед. Например, отсортируем вектор в порядке убывания:
++it переводит итератор на предыдущий элемент, а --it на следующий.Для получения реверсивного итератора существуют rbegin()/rend(), а также константные аналоги crbegin()/crend(). rbegin указывает на последний элемент контейнера, а rend на элемент перед контейнером, аналогично обычным итераторам, просто наоборот. Изображение пояснит соотношение на векторе.
Возможно, вам покажется неправильным, что ++it идет на предыдущий элемент, но это позволяет использовать все стандартные алгоритмы задом наперед. Например, отсортируем вектор в порядке убывания:
std::sort(v.rbegin(), v.rend())👍4
Заканчиваем с реверсивными итераторами. Допустим, что нам нужно найти первое и последнее слово в строке. С первым всё понятно, используем std::find с начала:
Для поиска с конца ищем реверсивно с конца по начало, итератор правильно находится на 8-й позиции, но при получении подстроки слово будет задом наперед:
Почему? Потому что итератор идет в обратном направлении. Для перехода на прямое направление используем вызов base() у реверсивного итератора:
Обратите внимание, что в паре с base() идет обычный итератор.
std::string str = "One Two Three";
auto space = std::find(str.cbegin(), str.cend(), ' ');
std::cout
<< std::string(str.cbegin(), space) // One
<< std::endl;
Для поиска с конца ищем реверсивно с конца по начало, итератор правильно находится на 8-й позиции, но при получении подстроки слово будет задом наперед:
auto rspace = std::find(str.crbegin(), str.crend(), ' ');
std::cout
<< str.crend() - rspace // at 8, correct
<< std::endl;
std::cout
<< std::string(str.crbegin(), rspace) // eerhT
<< std::endl;
Почему? Потому что итератор идет в обратном направлении. Для перехода на прямое направление используем вызов base() у реверсивного итератора:
std::cout
<< std::string(rspace.base(), str.cend()) // Three
<< std::endl;
Обратите внимание, что в паре с base() идет обычный итератор.
❤1👍1
Боян, но все равно смешно)
https://stackoverflow.com/questions/1642028/what-is-the-operator-in-c-c?rq=2
#stackoverflow #юмор
https://stackoverflow.com/questions/1642028/what-is-the-operator-in-c-c?rq=2
#stackoverflow #юмор
Stack Overflow
What is the '-->' operator in C/C++?
After reading Hidden Features and Dark Corners of C++/STL on comp.lang.c++.moderated, I was completely surprised that the following snippet compiled and worked in both Visual Studio 2008 and G++ 4....
😁7
std::list определяет несколько алгоритмов в качестве членов, например sort(), merge(), reverse(), unique(). Обобщенная версия std::sort (та, которая принимает итераторы) требует итераторы произвольного доступа, которых нет в list, поэтому std::sort нельзя использовать с list. Вместо этого следует вызывать член sort() у самого list.
Давайте ради интереса проверим скорость сортировки вектора и списка из 100 тысяч int-ов:
На MSVC на моей тачке (x86_64) получилось, что сортировка списка в 1.8 раза медленней сортировки вектора (если я нигде не обосралась). А почему так, думаю догадаются многие 😁.
Давайте ради интереса проверим скорость сортировки вектора и списка из 100 тысяч int-ов:
#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
#include <chrono>
constexpr int sortSize = 100'000;
int main()
{
std::list<int> list;
for (int i = sortSize; i > 0 ; --i) {
list.push_back(i);
}
auto begin1 = std::chrono::steady_clock::now();
//std::sort(list.begin(), list.end()); // не скомпилится
list.sort(); // скомпилится
auto end1 = std::chrono::steady_clock::now();
std::vector<int> vector;
vector.resize(sortSize);
for (int i = sortSize; i > 0; --i) {
vector[sortSize - i] = i;
}
auto begin2 = std::chrono::steady_clock::now();
std::sort(vector.begin(), vector.end());
auto end2 = std::chrono::steady_clock::now();
auto it_l = list.begin();
for (size_t i = 0; i < 10; ++i) {
std::cout << *it_l++ << " "; // 1 2 3 4 5 6 7 8 9 10
}
std::cout << std::endl;
auto it_v = vector.begin();
for (size_t i = 0; i < 10; ++i) {
std::cout << *it_v++ << " "; // 1 2 3 4 5 6 7 8 9 10
}
std::cout << std::endl;
std::cout << "list: "
<< std::chrono::duration_cast<std::chrono::microseconds>(end1 - begin1).count()
<< " mcs"
<< std::endl;
std::cout << "vector: "
<< std::chrono::duration_cast<std::chrono::microseconds>(end2 - begin2).count()
<< " mcs"
<< std::endl;
// list: 1717 mcs
// vector : 958 mcs
return 0;
}
На MSVC на моей тачке (x86_64) получилось, что сортировка списка в 1.8 раза медленней сортировки вектора (если я нигде не обосралась). А почему так, думаю догадаются многие 😁.