
Современный C++, 5 крутых фишек.pptx
- Количество слайдов: 74
Современный C++ 5 крутых фишек Алексей Малов i. Spring Solutions www. ispringsolutions. com
Содержание • Семантика перемещения • Умные указатели • Optional • Variant • Контейнеры, диапазоны, алгоритмы • Применение функционального программирования
Семантика перемещения
Семантика перемещения • Позволяет компилятору заменить дорогостоящую операцию копирования менее дорогими перемещениями • Копирование vector, string • Время: O(N) • Память: O(N) – для хранения копии • Может выбросить исключение • Перемещение vector, string • Время: O(1) • Память: O(1) • Как правило, не бросает исключений • Некоторые типы могут только перемещаться • std: : fstream, std: : unique_ptr, std: : future, std: : thread
struct Person { string name; string surname; }; struct Department { string name; vector
Что происходит при копировании H ap p y Happy n ew new y ea r year! vector
Что происходит при перемещении H ap p y Happy n ew new y ea r year! vector
struct Person { string name; string surname; }; struct Department { string name; vector
Move-only типы • Для них операция копирования не имеет смысла • Полезно иметь возможность перемещения значения от одного объекта • Возврат результата из функции • Передача аргумента в функцию • Пример: • Объекты типа Handle, управляющие некоторыми ресурсами ОС
namespace detail { template
~Handle() { if (m_handle != INVALID) { Close. Handle(m_handle); } } explicit operator bool() const { return m_handle != INVALID; } operator HANDLE() const { return m_handle; } void Close() { if (!Close. Handle(m_handle)) { m_handle = INVALID; throw std: : runtime_error("Failed to close handle"); } m_handle = INVALID; } }; } // namespace detail using File. Handle = detail: : Handle
Пример использования File. Handle Safe. Open. File(LPCWSTR file. Name) { File. Handle fh(Create. File. W(file. Name, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); if (!fh) throw runtime_error("Failed to open file"); return fh; } int main() { auto f = Safe. Open. File(L"main. cpp"); DWORD file. Size = Get. File. Size(f, nullptr); if (file. Size != INVALID_FILE_SIZE) cout << "File size: " << file. Size << "n"; }
Умные указатели
unique_ptr • Единолично владеет объектом через его указатель • Move-only • Нулевой оверхед + RAII • Совместим с stl-контейнерами • Позволяет управлять массивом объектов в куче • Поддержка пользовательского deleter-а • R. I. P. auto_ptr
Идиома Pimpl (pointer to implementation) • Вместо хранения данных, класс хранит указатель на структуру или класс с деталями реализации • Скрывает лишние зависимости из заголовочного файла • Позволяет сократить время компиляции
Пример – идиома Pimpl class Opaque. Obj { Opaque. Obj. h public: Opaque. Obj(int data); ~Opaque. Obj(); void Foo(); private: struct Impl; Impl* m_impl; }; #include "Opaque. Obj. h" Opaque. Obj. cpp struct Opaque. Obj: : Impl { Impl(int data) : m_data(data) {} void Foo() { /* do something */ } int m_data = 42; }; Opaque. Obj: : Opaque. Obj(int data) : m_impl(new Impl(data)) {} Opaque. Obj: : ~Opaque. Obj() { delete m_impl; } Проблема – требуется реализовать или запретить конструктор копирования и оператор присваивания void Opaque. Obj: : Foo() { m_impl->Foo(); }
Идиома Pimpl с unique_ptr class Opaque. Obj { Opaque. Obj. h public: Opaque. Obj(int data); ~Opaque. Obj(); void Foo(); private: struct Impl; unique_ptr
Умное управление ресурсами библиотеки языка C typedef struct tag. Data { int value; } Data; Data* Allocate. Data(); void Do. Something. With. Data(Data *data, int x); void Deallocate. Data(Data* data);
int Calculate. X(int value); Код по-прежнему содержит проблему bool Foo() { Data *p = Allocate. Data(); if (!p) { cout << "Failed to allocated datan"; return false; } if (p->value == 42) { Deallocate. Data(p); return true; } Do. Something. With. Data(p, Calculate. X(p->value)); Deallocate. Data(p); return false; }
int Calculate. X(int value) { if (value < 0) throw std: : invalid_argument("Invalid argument"); return value + 1; } bool Foo() { Data *p = Allocate. Data(); if (!p) { cout << "Failed to allocated datan"; return false; } if (p->value == 42) { Deallocate. Data(p); return true; } Если Calculate. X выбросит исключение, Deallocate. Data вызван не будет Do. Something. With. Data(p, Calculate. X(p->value)); Deallocate. Data(p); return false; }
Пользовательский deleter struct Data. Deleter { void operator()(Data *data) const noexcept { Deallocate. Data(data); } }; using Data. Ptr = std: : unique_ptr; Data. Ptr Safe. Allocate. Data() { if (Data. Ptr p{Allocate. Data()}) return p; throw std: : runtime_error("Failed to allocate data"); }
Было bool Foo() { Data *p = Allocate. Data(); if (!p) { cout << "Failed to allocated datan"; return false; } if (p->value == 42) { Deallocate. Data(p); return true; } Do. Something. With. Data(p, Calculate. X(p->value)); Deallocate. Data(p); return false; }
Стало bool Foo() { try { auto p = Safe. Allocate. Data(); if (p->value == 42) return true; Do. Something. With. Data(p. get(), Calculate. X(p->value)); } catch (std: : exception const &e) { cout << e. what() << "n"; } return false; }
shared_ptr • Умный указатель, основанный на подсчете ссылок • Обеспечивает совместное владение объектом • Возможность управления не только памятью • Накладные расходы • Память для хранения счетчиков • Thread-safe подсчет ссылок shared_ptr
Master weak_ptr • Слабая ссылка на объект • Не влияет на время жизни • Автоматически обнуляется после удаления объекта • Пока объект жив, позволяет получить shared_ptr на него • Решает проблему циклических ссылок и висячих указателей weak_ptr
enable_shared_from_this • Позволяет объекту, управляемому shared_ptr, безопасно создать экземпляр shared_ptr на самого себя • shared_from_this() • weak_from_this () (C++ 17) • Нельзя вызывать shared_from_this() из конструктора и деструктора, а также у объекта, не обернутого в shared_ptr • До C++ 17 грозило UB • В C++17 – исключение bad_weak_ptr • Применимость • Передача shared-ссылки на самого себя • Реализация идиомы weak this • Защита от преждевременного удаления при вызове внешнего кода
using Node. Ptr = shared_ptr
Задача: асинхронная загрузка изображения в GUI-приложении struct Image { /* some image data */ }; using Callback = function
Решение, которое (иногда) не работает Почему? using Callback = function
Идиома weak this А если нельзя владеть Image. View через shared_ptr? struct Image. View : enable_shared_from_this
Немного сахара в C++17 struct Image. View : enable_shared_from_this
Еще больше сахара с Bind. Weak. Ptr struct Image. View : enable_shared_from_this
Пример - Universal. Ptr • Указатель, инкапсулирующий семантику владения ресурсом • No ownership • Unique/shared ownership • Применимость – альтернатива передаче по ссылке или указателю • Объекту нужен доступ к ресурсу, но не нужны права владения • Lifetime объекта не превышает lifetime ресурса • Клиент может передать объекту ресурс вместе с правами владения • Накладные расходы • Обертка над shared_ptr с соответствующими затратами на копирование
template
struct IShape { virtual ~IShape() = default; virtual void Draw()const = 0; }; struct IShape. Factory { virtual ~IShape. Factory() = default; virtual unique_ptr
void Painter. Without. Ownership() { Shape. Factory factory; Painter c 1(factory); Painter c 2(&factory); c 1. Work. With. Shapes(); } Painter Get. Cli. Painter. With. Unique. Access. To. Factory() { return Painter(make_unique
Пример – простой кеш документов string Get. File. Content(const char *file. Name); class Document. Storage : public enable_shared_from_this
Document. Content Get. Document. Content(const string &file. Name) { String. Ptr content; auto it = m_items. find(file. Name); if (it != m_items. end()) content = it->second. lock(); if (!content) { weak_ptr
int main() { auto storage = make_shared
std: : optional
optional • Опциональное значение • Не использует динамическое выделение памяти • Применение • Значение, которого может и не быть • Результат поиска • Undefined-значение (если сам тип его не имеет) • Отложенное конструирование объекта • Поле класса не может быть проинициализировано в конструкторе • Ленивые вычисления • Простейшее информирование об ошибке
struct ICanvas; Прямоугольник с опциональными заливкой и обводкой struct Rect { void Draw(ICanvas & canvas) const { if (fill. Color) canvas. Begin. Solid. Fill(*fill. Color); canvas. Set. Line. Color(line. Color); canvas. Move. To(left, top); canvas. Line. To(left + width, top + height); canvas. Line. To(left, top); if (fill. Color) canvas. End. Fill(); } float left = 0. f, top = 0. f, width = 0. f, height = 0. f; optional
struct ICanvas { virtual ~ICanvas() = default; virtual void Begin. Solid. Fill(const RGBAColor& fill. Color) = 0; virtual void End. Fill() = 0; virtual void Move. To(float x, float y) = 0; virtual void Set. Line. Color(const optional
Ленивое вычисление характеристик цифрового сигнала struct Signal { double Get. Max. Amplitude()const { if (!m_max. Amplitude) { m_max. Amplitude = m_samples. empty() ? 0. 0 : *max_element(m_samples. begin(), m_samples. end()); } return *m_max. Amplitude; } void Set. Samples(vector
variant
variant • Тип, способный хранить значение одного из нескольких типов • Хранение по значению • Примитивные или составные типы • Не требуется какая-либо связь между типами • Достоинства • Типобезопасность по сравнению с union • Не использует динамическое выделение памяти • Надежнее по сравнению с наивной реализацией
Пример – решение квадратного уравнения struct Not. AQudaratic. Equation {}; using Quadratic. Equation. Roots = variant< std: : monostate, // no roots double, // one root pair
Решение квадратного уравнения Quadratic. Equation. Roots Solve(double a, double b, double c) { if (abs(a) < std: : numeric_limits
Обработка при помощи visitor class struct Result. Printer { void operator()(std: : monostate) { cout << "No real rootsn"; } void operator()(double x) { cout << "1 root: " << x << "n"; } void operator()(const pair
Обработка при помощи get_if auto result = Solve(1, 0, -1); if (get_if
Обработка при помощи constexpr if template
Algoritms, Ranges
• Контейнеры • Хранят данные • Алгоритмы (~100 в C++17) • • • Поиск и подсчет элементов Модификация, копирование элементов, перестановки Сортировка и разделение Удаление элементов Сравнение Параллельная обработка (c++17) • Диапазоны • Обещают в C++20 • Есть в библиотеке boost: : range
Исходные данные enum class Gender : uint 8_t { Male, Female }; struct Person { string name; short age; Gender gender; int salary; }; ostream& operator<<(ostream& out, const Person& p); int main() { const vector
Вывод в stdout for (size_t i = 0; i < people. size(); ++i) { cout << people[i] << "n"; } for (auto & person : people) { cout << person << "n"; } copy(people, ostream_iterator
Вывести самого старого (raw loop) if (!people. empty()) { size_t oldest = 0; for (size_t i = 1; i < people. size(); ++i) { if (people[i]. age > people[oldest]. age) { oldest = i; } } cout << "The oldest one is " << people[oldest] << "n"; } else { cout << "No peoplen"; }
Вывести самого старого (max_element) auto ordered. By. Age = [](auto & lhs, auto & rhs) { return lhs. age < rhs. age; }; auto oldest = max_element(people, ordered. By. Age); if (oldest != people. end()) cout << "The oldest one is " << *oldest << "n"; else cout << "No peoplen";
Вывести самого старого (C++17) auto ordered. By. Age = [](auto & lhs, auto & rhs) { return lhs. age < rhs. age; }; auto oldest = max_element(people, ordered. By. Age); oldest != people. end()) if ( cout << "The oldest one is " << *oldest << "n"; else cout << "No peoplen";
Вывести женщин от 20 до 30 лет, а потом мужчин от 25 до 40 for (size_t i = 0; i < people. size(); ++i) { if (people[i]. gender == Gender: : Female && (people[i]. age >= 20 && people[i]. age <= 30)) { cout << people[i] << "n"; } } for (size_t i = 0; i < people. size(); ++i) { if (people[i]. gender == Gender: : Male && (people[i]. age >= 25 && people[i]. age <= 40)) { cout << people[i] << "n"; } }
Range based for (auto& person : people) { if ((person. gender == Gender: : Female) && (person. age >= 20 && person. age <= 30)) { cout << person << "n"; } } for (auto& person : people) { if ((person. gender == Gender: : Male) && (person. age >= 25 && person. age <= 40)) { cout << person << "n"; } }
Filtered-range auto By. Gender. And. Age = [](Gender g, int min. Age, int max. Age) { return [=](auto& p) { return (p. gender == g) && p. age >= min. Age && p. age <= max. Age; }; }; auto To. Stdout = ostream_iterator
Ищем, есть ли женщины за 30 (raw for) bool has. Women. Older. Than 30 = false; for (size_t i = 0; i < people. size(); ++i) { if (people[i]. age > 30 && people[i]. gender == Gender: : Female) { has. Women. Older. Than 30 = true; break; } } if (has. Women. Older. Than 30) cout << "There are women older than 30n"; else cout << "There are no women above 30n";
Ищем, есть ли женщины за 30 (any_of) auto older. Than = [](int age) { return [=](auto& p) { return p. age > age; }; auto is. Female = [](auto& p) { return p. gender == Gender: : Female; }; if (any_of(people | filtered(older. Than(30)), is. Female)) cout << "There are women older than 30n"; else cout << "There are no women above 30n";
Паттерны проектирования в функциональном стиле
Abstract Factory
Продукт struct IProduct { virtual ~IProduct() = default; virtual void Foo() = 0; }; struct Concrete. Product : IProduct { Concrete. Product(int data): m_data(data) {} void Foo() override {} private: int m_data; };
struct IFactory { virtual ~IFactory() = default; virtual unique_ptr
Фабрика в функциональном стиле using Factory = function
Команда
Робот enum class Walk. Direction { North, South, West, East, }; struct Robot { void Turn. On(); void Turn. Off(); void Walk(Walk. Direction direction); void Stop(); … };
struct ICommand { virtual ~ICommand() = default; virtual void Execute() = 0; }; struct Menu { void Add. Item(string shortcut, string descr, unique_ptr
Команда в функциональном стиле struct Menu. FP { typedef std: : function
Спасибо за внимание!
Ссылки • Миграция на современный C++ 17 • Интервалы с C++ • С++ without new and delete https: //github. com/alexey-malov/Cpp. Meeting-Kazan 2017