В продолжении темы про std::accumulate. Как оказывается, тип третьего параметра определяет тип результата и это может приводить к неожиданным последствиям. Найдем сумму вектора double, передав первый раз 0 (то есть как int), а второй 0.0 (то есть как double):
Выведет:
3 4.77
В первом случае результатом будет int, что приводит к отбрасыванию дробной части при каждом прибавлении. Во втором случае результат будет верным. Обожаю плюсы)
#cpp
std::vector<double> iv = { 1.23, 1.55, 1.99 };
std::cout
<< std::accumulate(iv.cbegin(), iv.cend(), 0)
<< " "
<< std::accumulate(iv.cbegin(), iv.cend(), 0.0);
Выведет:
3 4.77
В первом случае результатом будет int, что приводит к отбрасыванию дробной части при каждом прибавлении. Во втором случае результат будет верным. Обожаю плюсы)
#cpp
❤3
Когда нужно сделать безопасный callback из другого потока и время жизни коллбека не известно или не контролируется, часто храню коллбек как std::weak_ptr. Это позволяет lock()-нуть его до shared_ptr и во-первых проверить что он есть, а во вторых убедиться что он будет существовать всё время его вызова.
Код примерно такой (пишу по памяти):
#cpp #multithreading
Код примерно такой (пишу по памяти):
class ICallback {
public:
virtual ~ICallback() {};
virtual void onData() = 0;
};
class ObjectInThreadB {
public:
void setCallback(std::shared_ptr<ICallback> callback) {
_callback = callback; // shared => weak
}
void setData() {
if (auto callback = _callback.lock()) {
callback->onData();
}
}
private:
std::weak_ptr<ICallback> _callback;
};
class ObjectInThreadA
: public ICallback
, public std::enable_shared_from_this<ObjectInThreadA>
{
public:
void init(ObjectInThreadB * objB) {
objB->setCallback(shared_from_this());
}
virtual void onData() override {}
}
Вроде все правильно написано) Единственный минус, то что shared_from_this() нельзя вызывать из конструктора this и приходится делать в другом методе. Как вам такой подход, может что-то проще/современней есть?#cpp #multithreading
Увидела такой пример про использование оператора 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
Немного неожиданное поведение и, на мой взгляд, не совсем удачные названия. Всем известно, что 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
Существуют как минимум 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
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
Увидела на реддит список (довольно базовых?) вопросов с одного собеседования на 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
Рассмотрим шаблон функции, который задает не только типы аргументов, но и тип результата, например дробное или целочисленное деление:
Мы могли бы вызвать этот код так:
Код скомпилируется и верно отработает, выдав заказанное нами дробное значение. Типы int для аргументов он выведет автоматом и нам их не нужно писать в угловых скобках (double написать все же придется).
Ну и что такого, спросите вы, это же ожидаемо? А давайте посмотрим, что было бы, если бы мы написали эту же функцию, но с другим порядком типов:
Этот код не скомпилируется и нам пришлось бы писать все 3 параметра, чтоб уважить компилятор:
Как вы понимаете, порядок важен и пропускать типы можно только для крайних справа параметров.
#cpp #templates
template <typename T1, typename T2, typename T3>
T1 div(T2 a, T3 b) {
return (T1)a / b;
}
Мы могли бы вызвать этот код так:
int main()
{
auto res = div<double>(3, 2);
std::cout << res << std::endl; // 1.5
return 0;
}
Код скомпилируется и верно отработает, выдав заказанное нами дробное значение. Типы int для аргументов он выведет автоматом и нам их не нужно писать в угловых скобках (double написать все же придется).
Ну и что такого, спросите вы, это же ожидаемо? А давайте посмотрим, что было бы, если бы мы написали эту же функцию, но с другим порядком типов:
template <typename T1, typename T2, typename T3>
T3 div_v2(T1 a, T2 b) {
return (T3)a / b;
}
auto res2 = div_v2<int>(3, 2); // won't compile!
Этот код не скомпилируется и нам пришлось бы писать все 3 параметра, чтоб уважить компилятор:
auto res2 = div_v2<int, int, int>(3, 2); // OK
Как вы понимаете, порядок важен и пропускать типы можно только для крайних справа параметров.
#cpp #templates
👍4