Михаил расскажет, как писать на современном С++ простой, выразительный и эффективный код для решения повседневных задач. Приведет пример реального кода, с которым ему приходилось работать, и расскажет, как сделать его лучше с помощью ряда простых средств из STL и boost, а также за счёт применения хороших практик программирования. Доклад рассчитан на базовые знания языка.
3. "Повседневный С++: boost и STL", Михаил Матросов, CoreHard C++ Conf 2016 3
С++17
С++11/14
С++98
С
High level
Expert level
Modern C++
4. “Within C++ is a smaller, simpler, safer
language struggling to get out”
Bjarne Stroustrup
"Повседневный С++: boost и STL", Михаил Матросов, CoreHard C++ Conf 2016 4
5. High level:
Парадигма RAII и исключения (exceptions)
Алгоритмы и контейнеры STL и boost
Семантика перемещения
λ-функции
Классы и конструкторы
Простые шаблоны
"Повседневный С++: boost и STL", Михаил Матросов, CoreHard C++ Conf 2016 5
Expert level:
Операторы new/delete, владеющие
указатели
Пользовательские операции копирования
и перемещения
Пользовательские деструкторы
Закрытое, защищённое, ромбовидное,
виртуальное наследование
Шаблонная магия, SFINAE
Все функции языка Си, препроцессор
«Голые» циклы
Классический слайд с телом и заголовком
6. "Повседневный С++: boost и STL", Михаил Матросов, CoreHard C++ Conf 2016 6
bool doCompare(const Bakery* oldBakery, const Bakery* newBakery)
{
if (!oldBakery || !newBakery)
{
return false;
}
std::vector<Cookie> oldCookies;
std::vector<Cookie> newCookies;
std::vector<std::pair<Cookie, int>> diff;
collectCookies(*oldBakery, oldCookies);
collectCookies(*newBakery, newCookies);
std::sort(oldCookies.begin(), oldCookies.end(), compareCookies);
std::sort(newCookies.begin(), newCookies.end(), compareCookies);
auto oldIt = oldCookies.begin();
auto newIt = newCookies.begin();
while ((oldIt != oldCookies.end()) || (newIt != newCookies.end()))
7. "Повседневный С++: boost и STL", Михаил Матросов, CoreHard C++ Conf 2016 7
bool doCompare(const Bakery* oldBakery, const Bakery* newBakery)
{
if (!oldBakery || !newBakery)
{
return false;
}
std::vector<Cookie> oldCookies;
std::vector<Cookie> newCookies;
std::vector<std::pair<Cookie, int>> diff;
collectCookies(*oldBakery, oldCookies);
collectCookies(*newBakery, newCookies);
std::sort(oldCookies.begin(), oldCookies.end(), compareCookies);
std::sort(newCookies.begin(), newCookies.end(), compareCookies);
auto oldIt = oldCookies.begin();
auto newIt = newCookies.begin();
while ((oldIt != oldCookies.end()) || (newIt != newCookies.end()))
{
if (compareCookies(*oldIt, *newIt))
{
diff.push_back(std::pair<Cookie, int>(*oldIt, 1));
++oldIt;
}
else if (compareCookies(*newIt, *oldIt))
{
diff.push_back(std::pair<Cookie, int>(*newIt, 2));
++newIt;
}
else
{
++oldIt;
++newIt;
}
}
if (oldIt != oldCookies.end())
{
for (; oldIt < oldCookies.end(); oldIt++)
{
diff.push_back(std::pair<Cookie, int>(*oldIt, 1));
}
}
if (newIt != newCookies.end())
{
for (; newIt < newCookies.end(); newIt++)
{
diff.push_back(std::pair<Cookie, int>(*newIt, 2));
}
}
for (const auto& item : diff)
{
std::cout << "Bakery #" << item.second <<
" has a different cookie "" << item.first.name <<
"" (weight=" << item.first.weight <<
", volume=" << item.first.volume <<
", tastiness=" << item.first.tastiness << ")." << std::endl;
}
if (diff.size() > 0)
return false;
return true;
}
bool doCompare(const Bakery& oldBakery, const Bakery& newBakery)
{
auto oldCookies = collectSortedCookies(oldBakery);
auto newCookies = collectSortedCookies(newBakery);
auto reportLost = [](const Cookie& cookie) {
std::cout << "We've lost a friend: " << cookie << "." << std::endl;
};
auto reportAppeared = [](const Cookie& cookie) {
std::cout << "We got a new friend: " << cookie << "." << std::endl;
};
boost::range::set_difference(oldCookies, newCookies,
boost::make_function_output_iterator(reportLost));
boost::range::set_difference(newCookies, oldCookies,
boost::make_function_output_iterator(reportAppeared));
return oldCookies == newCookies;
}
42. "Повседневный С++: boost и STL", Михаил Матросов, CoreHard C++ Conf 2016 42
std::vector<Cookie> lostCookies;
std::set_difference(oldCookies.begin(), oldCookies.end(),
newCookies.begin(), newCookies.end(),
std::back_inserter(lostCookies));
std::vector<Cookie> appearedCookies;
std::set_difference(newCookies.begin(), newCookies.end(),
oldCookies.begin(), oldCookies.end(),
std::back_inserter(appearedCookies));
43. "Повседневный С++: boost и STL", Михаил Матросов, CoreHard C++ Conf 2016 43
std::vector<Cookie> lostCookies;
boost::range::set_difference(oldCookies, newCookies,
std::back_inserter(lostCookies));
std::vector<Cookie> appearedCookies;
boost::range::set_difference(newCookies, oldCookies,
std::back_inserter(appearedCookies));
44. "Повседневный С++: boost и STL", Михаил Матросов, CoreHard C++ Conf 2016 44
std::vector<Cookie> lostCookies;
boost::range::set_difference(oldCookies, newCookies,
std::back_inserter(lostCookies));
std::vector<Cookie> appearedCookies;
boost::range::set_difference(newCookies, oldCookies,
std::back_inserter(appearedCookies));
for (const auto& cookie : lostCookies)
{
std::cout << "We've lost a friend: " << cookie << "." << std::endl;
}
for (const auto& cookie : appearedCookies)
{
std::cout << "We got a new friend: " << cookie << "." << std::endl;
}
45. "Повседневный С++: boost и STL", Михаил Матросов, CoreHard C++ Conf 2016 45
auto reportLost = [](const Cookie& cookie) {
std::cout << "We've lost a friend: " << cookie << "." << std::endl;
};
auto reportAppeared = [](const Cookie& cookie) {
std::cout << "We got a new friend: " << cookie << "." << std::endl;
};
boost::range::set_difference(oldCookies, newCookies,
boost::make_function_output_iterator(reportLost));
boost::range::set_difference(newCookies, oldCookies,
boost::make_function_output_iterator(reportAppeared));
46. "Повседневный С++: boost и STL", Михаил Матросов, CoreHard C++ Conf 2016 46
bool doCompare(const Bakery& oldBakery, const Bakery& newBakery)
{
std::vector<Cookie> oldCookies = collectSortedCookies(oldBakery);
std::vector<Cookie> newCookies = collectSortedCookies(newBakery);
auto reportLost = [](const Cookie& cookie) {
std::cout << "We've lost a friend: " << cookie << "." << std::endl;
};
auto reportAppeared = [](const Cookie& cookie) {
std::cout << "We got a new friend: " << cookie << "." << std::endl;
};
boost::range::set_difference(oldCookies, newCookies,
boost::make_function_output_iterator(reportLost));
boost::range::set_difference(newCookies, oldCookies,
boost::make_function_output_iterator(reportAppeared));
return oldCookies == newCookies;
}
47. "Повседневный С++: boost и STL", Михаил Матросов, CoreHard C++ Conf 2016 47
bool doCompare(const Bakery& oldBakery, const Bakery& newBakery)
{
auto oldCookies = collectSortedCookies(oldBakery);
auto newCookies = collectSortedCookies(newBakery);
auto reportLost = [](const Cookie& cookie) {
std::cout << "We've lost a friend: " << cookie << "." << std::endl;
};
auto reportAppeared = [](const Cookie& cookie) {
std::cout << "We got a new friend: " << cookie << "." << std::endl;
};
boost::range::set_difference(oldCookies, newCookies,
boost::make_function_output_iterator(reportLost));
boost::range::set_difference(newCookies, oldCookies,
boost::make_function_output_iterator(reportAppeared));
return oldCookies == newCookies;
}
48. "Повседневный С++: boost и STL", Михаил Матросов, CoreHard C++ Conf 2016 48
bool doCompare(const Bakery* oldBakery, const Bakery* newBakery)
{
if (!oldBakery || !newBakery)
{
return false;
}
std::vector<Cookie> oldCookies;
std::vector<Cookie> newCookies;
std::vector<std::pair<Cookie, int>> diff;
collectCookies(*oldBakery, oldCookies);
collectCookies(*newBakery, newCookies);
std::sort(oldCookies.begin(), oldCookies.end(), compareCookies);
std::sort(newCookies.begin(), newCookies.end(), compareCookies);
auto oldIt = oldCookies.begin();
auto newIt = newCookies.begin();
while ((oldIt != oldCookies.end()) || (newIt != newCookies.end()))
{
if (compareCookies(*oldIt, *newIt))
{
diff.push_back(std::pair<Cookie, int>(*oldIt, 1));
++oldIt;
}
else if (compareCookies(*newIt, *oldIt))
{
diff.push_back(std::pair<Cookie, int>(*newIt, 2));
++newIt;
}
else
{
++oldIt;
++newIt;
}
}
if (oldIt != oldCookies.end())
{
for (; oldIt < oldCookies.end(); oldIt++)
{
diff.push_back(std::pair<Cookie, int>(*oldIt, 1));
}
}
if (newIt != newCookies.end())
{
for (; newIt < newCookies.end(); newIt++)
{
diff.push_back(std::pair<Cookie, int>(*newIt, 2));
}
}
for (const auto& item : diff)
{
std::cout << "Bakery #" << item.second <<
" has a different cookie "" << item.first.name <<
"" (weight=" << item.first.weight <<
", volume=" << item.first.volume <<
", tastiness=" << item.first.tastiness << ")." << std::endl;
}
if (diff.size() > 0)
return false;
return true;
}
bool doCompare(const Bakery& oldBakery, const Bakery& newBakery)
{
auto oldCookies = collectSortedCookies(oldBakery);
auto newCookies = collectSortedCookies(newBakery);
auto reportLost = [](const Cookie& cookie) {
std::cout << "We've lost a friend: " << cookie << "." << std::endl;
};
auto reportAppeared = [](const Cookie& cookie) {
std::cout << "We got a new friend: " << cookie << "." << std::endl;
};
boost::range::set_difference(oldCookies, newCookies,
boost::make_function_output_iterator(reportLost));
boost::range::set_difference(newCookies, oldCookies,
boost::make_function_output_iterator(reportAppeared));
return oldCookies == newCookies;
}
49. Код, использующий стандартные алгоритмы и контейнеры:
Короче
Проще
Надёжнее
Обычно эффективнее
Проще переиспользовать
Не привязан к конкретной платформе
"Повседневный С++: boost и STL", Михаил Матросов, CoreHard C++ Conf 2016 49
50. И ещё немного
по теме…
"Повседневный С++: boost и STL", Михаил Матросов, CoreHard C++ Conf 2016 50
51. "Повседневный С++: boost и STL", Михаил Матросов, CoreHard C++ Conf 2016 51
struct Cookie
{
double weight;
double volume;
std::string name;
int tastiness;
auto rank() const
{
return std::tie(tastiness, weight, volume, name);
}
};
52. "Повседневный С++: boost и STL", Михаил Матросов, CoreHard C++ Conf 2016 52
struct Cookie
{
double weight;
double volume;
std::string name;
int tastiness;
double density() const
{
return weight / volume;
}
auto rank() const
{
return std::tie(tastiness, density(), name); // Any problems here?..
}
};
53. "Повседневный С++: boost и STL", Михаил Матросов, CoreHard C++ Conf 2016 53
struct Cookie
{
double weight;
double volume;
std::string name;
int tastiness;
double density() const
{
return weight / volume;
}
auto rank() const
{
return std::make_tuple(tastiness, density(), std::ref(name));
}
};
54. "Повседневный С++: boost и STL", Михаил Матросов, CoreHard C++ Conf 2016 54
struct Cookie
{
double weight;
double volume;
std::string name;
int tastiness;
double density() const
{
return weight / volume;
}
auto rank() const // -> std::tuple<int, double, const std::string&>
{
return std::make_tuple(tastiness, density(), std::ref(name));
}
};
55. "Повседневный С++: boost и STL", Михаил Матросов, CoreHard C++ Conf 2016 55
auto f = [](/* ... */) { /* ... */ };
boost::make_function_output_iterator(f) ->
boost::iterators::function_output_iterator<LambdaAnonymousType>
template <class UnaryFunction>
class function_output_iterator {
// ...
UnaryFunction m_f;
};
auto it1 = boost::make_function_output_iterator(f); // Not assignable
auto it2 = boost::make_function_output_iterator(std::ref(f)); // Assignable
56. Советы:
Мыслите на высоком уровне
Код должен ясно выражать намерение
Знайте свои инструменты и используйте их к месту
"Повседневный С++: boost и STL", Михаил Матросов, CoreHard C++ Conf 2016 56
// references
// auto variables
// auto function return type
// move semantics
// lambda functions
operator<();
std::make_tuple();
std::tie();
std::ref();
std::make_pair();
std::vector<T>::emplace_back();
std::vector<T>::empty();
operator<<();
std::back_inserter();
std::set_difference();
boost::range::set_difference();
boost::make_function_output_iterator();
57. Мыслите на высоком уровне
Код должен ясно выражать намерение
Знайте свои инструменты и используйте их к месту
"Повседневный С++: boost и STL", Михаил Матросов, CoreHard C++ Conf 2016
std::make_pair();
std::vector<T>::emplace_back();
std::vector<T>::empty();
operator<<();
std::back_inserter();
std::set_difference();
boost::range::set_difference();
boost::make_function_output_iterator();
Спасибо за внимание!
// references
// auto variables
// auto function return type
// move semantics
// lambda functions
operator<();
std::make_tuple();
std::tie();
std::ref();
57
Editor's Notes
Образ С++
По поводу фичей, взятых из boost: http://stackoverflow.com/q/8851670/261217
Исходный текст функции.
Цель
Посмотрим на указатели
Используем ссылки
Посмотрим на вызов collectCookies
Убедимся, что он инициализирует вектор печенек
Будем передавать его по значению
Посмотрим на два вызова std::sort
Посмотрим на два вызова std::sort
Устраним дублирование: уберём std::sort в collectCookies. Для наглядности переименуем его в collectSortedCookies.
Посмотрим на функцию compareCookies
Посмотрим на прототип функции compareCookies
Используем operator<. Никто не будет сравнивать значения с помощью вызова функции. Ну, если вы не пишете на Java.
Вернёмся к коду основной функции
Используем прямое сравнение
Вернёмся к оператору сравнения
Используем std::tuple для лексикографического сравнения
Посмотрим ещё раз на сам тип Cookie
Используем std::tie, чтобы не копировать std::string лишний раз
Кроме того, посмотрим по дублирование списка членов
Используем функцию rank(), чтобы не дублировать список членов
Добавим комментарий с явным типом возвращаемого значения
Объявим operator== по аналогии
Посмотрим на явный конструктор std::pair
Используем std::make_pair
Используем emplace_back()
Посмотрим на цикл вывода
Добавим оператора вывода в поток для печеньки
Посмотрим на конструкцию вывода
Заменим на эквивалентный вызов empty()
Начинаем догадываться, что всё это значит. По этому поводу пишем юнит-тесты. Находим баг.
Используем готовый алгоритм. Потом обращаем внимание, что неудобно писать много раз begin()/end().
Используем аналог из boost::range
Добавляем код с выводом. Свой вывод для пропавших и появившихся печенек. Понимаем, что вывод – это всё, что нам нужно.
Убираем промежуточный вектор хранения.
ToDo: тут непонятно, что куда переехало
Почти финальная форма. Понимаем, что нам не важно, вектор мы используем или что-то ещё.
Финальная форма! Нет привязки к конкретному типу последовательности.
Цель достигнута!
Сейчас ещё чуть-чуть кода и котик, чтобы вы не расстраивались.
Небольшой бонус относительно std::tie и std::make_tuple. Вспомним определение метода rank().
Что если в вычислении ранка будут участвовать не только поля класса? Код просто не скомпилируется.
Придётся вернуться к make_tuple(), но чтобы не копировать строку каждый раз, обернём её в std::reference_wrapper.
Явно укажем выведенный тип.
И ещё замечание о присваиваемости итератора, создаваемого с помощью make_function_output_iterator.