Михаил Матросов, “С++ без new и delete”

33
C++ без new и delete Матросов Михаил [email protected] Russian C++ User Group, Саратов, 2014

description

Со времён С++98 стандартные контейнеры и идиома RAII позволяли избегать использования оператора delete, что делало код более безопасным. С приходом С++11 и умных указателей отпала необходимость использовать оператор new, что позволило практически полностью переложить управление памятью на плечи компилятора. В докладе объясняется идеология управления памятью и ресурсами в современном С++.

Transcript of Михаил Матросов, “С++ без new и delete”

Page 1: Михаил Матросов, “С++ без new и delete”

C++ без new и delete

Матросов Михаил

[email protected]

Russian C++ User Group, Саратов, 2014

Page 2: Михаил Матросов, “С++ без new и delete”

Вместо содержания

• Что?

– Писать код без new и delete в современном С++

• Как?

– Контейнеры STL

– std::make_shared, std::make_unique

– Cвои обёртки для своих умных указателей

• Почему?

– new и delete увеличивают сложность кода

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

2

Page 3: Михаил Матросов, “С++ без new и delete”

Современный С++

• Современный С++ включает

– Возможности стандарта С++14

– Современные best practices

• C++14 is done!

– Рабочий черновик стандарта ISO/IEC 14882:2014(E) единогласно одобрен

– Полностью или частично поддерживается всеми мажорными компиляторами

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

3

Page 4: Михаил Матросов, “С++ без new и delete”

Что и как?Общие рекомендации

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

4

Page 5: Михаил Матросов, “С++ без new и delete”

Объекты в Сtypedef struct ObjectTag{// Object members

} Object;

int InitObject(Object* io_pObject);int UseObject(Object* io_pObject);int ClearObject(Object* io_pObject);

void ObjectsInC(){Object* pObject;pObject = malloc(sizeof(Object)); // Allocate memoryInitObject(pObject); // Establish invariants and acquire resourcesUseObject(pObject); // Do the workClearObject(pObject); // Release resourcesfree(pObject); // Release memory// By the way: error handling :-/

}

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

5

Page 6: Михаил Матросов, “С++ без new и delete”

Объекты в С++

• Инкапсуляция:

– Конструкторы

– Деструкторы

– Методы

class Object{// Object members

public:Object(); // Establish invariants and acquire resources~Object(); // Release resourcesvoid Use(); // Do the work

};

• Обработка ошибок:

– Исключения

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

6

Page 7: Михаил Матросов, “С++ без new и delete”

Объекты в С++

// 1. Naive C++ styleObject* p = new Object(); // Allocate memoryp->Use(); // Do the workdelete p; // Release memory

// 2. More secure C++98 with RAIIstd::auto_ptr<Object> ap(new Object()); // Allocate memoryap->Use(); // Do the work

// 3. More secure C++11 with RAIIstd::unique_ptr<Object> up(new Object()); // Allocate memoryup->Use(); // Do the work

// 4. New bullet-proof modern C++auto up2 = std::make_unique<Object>(); // Create unique objectup2->Use(); // Do the work

• 2 лучше, чем 1– управление ресурсами

• 3 лучше, чем 2– unique_ptr делает всё, что и

auto_ptr, только лучше

• 4 лучше, чем 3– основная тема доклада

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

7

Page 8: Михаил Матросов, “С++ без new и delete”

Разделяемые объекты в С++void UseObject(Object*); // Shall retain the objectvoid AcceptObject(Object*); // Shall delete the objectvoid ShareObject(std::shared_ptr<Object>); // Both cases

// Naive C++Object* p = new Object();p->Use();UseObject(p); // Want to use object after callp->Use();AcceptObject(p); // No longer need the object

// C++98 & RAII/* No way to say this! */

// Modern C++auto sp = std::make_shared<Object>();sp->Use();ShareObject(sp); // Want to use object after callsp->Use();ShareObject(std::move(sp)); // No longer need the object

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

8

Page 9: Михаил Матросов, “С++ без new и delete”

Объекты со счётчиком ссылок в С++

// Naive C++ (not used)RefCounted* p = new RefCounted();p->AddRef();p->Use();p->Release();

// C++98 & RAII RefPtr<RefCounted> rp = new RefCounted();rp->Use();

// Modern C++auto rp2 = MakeRefPtr<RefCounted>();rp2->Use();

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

9

Page 10: Михаил Матросов, “С++ без new и delete”

Динамические массивы в С++

void UseArray(int*);

int n = 100;

// Naive C++int* p = new int[n];UseArray(p);delete[] p;

// C++98 & RAIIstd::vector<int> v(n);UseArray(&v[0]);

// Modern C++std::vector<int> v2(n);UseArray(v2.data());

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

10

Page 11: Михаил Матросов, “С++ без new и delete”

Почему?Аргументация

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

11

Page 12: Михаил Матросов, “С++ без new и delete”

Лучше == проще• Немного авторитета:

– Главный технический императив разработки ПО –управление сложностью [McConnel2004]

– Make Simple Tasks Simple! [Stroustrup2014]– ≈99.9998% разработчиков – не эксперты [Sutter2014]– Принцип KISS (Keep It Simple, Stupid)

• Что значит проще?– Простые задачи должны решаться просто– Сложные – не сложнее, чем необходимо– Сначала – простота, затем – производительность

• Как сделать проще?– Принцип DRY (Don’t Repeat Yourself)– Принцип «по умолчанию» [Sutter2014]

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

12

Page 13: Михаил Матросов, “С++ без new и delete”

Принцип «по умолчанию»

• Основная мысль:

– Известная задача ⇒ известное решение

• Преимущества:

– Взаимопонимание с другими разработчиками

– Уменьшение порога входа для новичков

– Меньше думать – продуктивная лень

– Все те же, что и для правил кодирования

• “A tour of C++” [Stroustrup2013]

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

13

Page 14: Михаил Матросов, “С++ без new и delete”

std::make_unique<T>() vs. std::unique_ptr(new T)

int GenerateId();

std::pair<std::unique_ptr<Object>, int> MakeObjectWithIdWrong(){return std::make_pair(std::unique_ptr<Object>(new Object()),GenerateId());

}

std::pair<std::unique_ptr<Object>, int> MakeObjectWithIdRight(){return std::make_pair(std::make_unique<Object>(), // SafeGenerateId());

}

Дублирование имени типа. DRY!

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

14

// May throw

// May leak!

Page 15: Михаил Матросов, “С++ без new и delete”

std::make_shared<T>() vs. std::shared_ptr(new T)

std::shared_ptr<Object> sp(new Object());

counter

Object

sp

Два выделения памяти

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

15

Page 16: Михаил Матросов, “С++ без new и delete”

std::make_shared<T>() vs. std::shared_ptr(new T)

auto sp = std::make_shared<Object>();

counter Object

Одно выделение памяти. Оптимизация We Know Where

You Live [Lavavej2012].

sp

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

16

Page 17: Михаил Матросов, “С++ без new и delete”

std::make_shared и std::make_unique

• Плюсы обоих:

– Безопасность при исключениях

– Нет дублирования имени типа

• Плюсы std::make_shared:

– Одно обращение к менеджеру памяти

• Минусы есть, но несущественные:– Нет формы для указания собственного аллокатора для std::unique_ptr

(для std::shared_ptr это std::allocated_shared)

– Опасность false sharing объекта и счётчика ссылок [см. вопрос на StackOverflow]

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

17

Page 18: Михаил Матросов, “С++ без new и delete”

Обобщаем

• delete по умолчанию не используется со времён RAII и стандартных контейнеров

• new по умолчанию не используется при работе со стандартной библиотекой

• Постараемся не использовать new при работе с другими библиотеками– Свои объекты со счётчиком ссылок

– Свои умные указатели

– Своё управление памятью

• Теперь и new по умолчанию не используется

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

18

Page 19: Михаил Матросов, “С++ без new и delete”

Как?Некоторые подробности использования

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

19

Page 20: Михаил Матросов, “С++ без new и delete”

Объекты со счётчиком ссылок

• OpenSceneGraph– Открытый 3D-движок под OpenGL на С++

– osg::Referenced – класс со счётчиком ссылок, базовый для большинства классов• ref() увеличивает счётчик

• unref() уменьшает счётчик и удаляет объект если счётчик становится равен нулю

– osg::ref_ptr<T> – умный указатель• вызывает ref() в конструкторе

• вызывает unref() в деструкторе

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

20

Page 21: Михаил Матросов, “С++ без new и delete”

OSG: C++ & RAII

• Пример кода из [Wang2010, стр. 78]

• Идеально подходит для апгрейда

– Может быть выполнен автоматически

osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;// Init verticesosg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;// Init normalsosg::ref_ptr<osg::Geometry> geom = new osg::Geometry;geom->setVertexArray(vertices.get());geom->setNormalArray(normals.get());// Further init geometry and use it

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

21

Page 22: Михаил Матросов, “С++ без new и delete”

OSG: Modern C++

namespace osg{template<typename T, typename... Args>osg::ref_ptr<T> make_ref(Args&&... args){return new T(std::forward<Args>(args)...);

}}

auto vertices = osg::make_ref<osg::Vec3Array>(); // Init verticesauto normals = osg::make_ref<osg::Vec3Array>(); // Init normalsauto geom = osg::make_ref<osg::Geometry>();geom->setVertexArray(vertices.get());geom->setNormalArray(normals.get());// Further init geometry and use it

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

22

Page 23: Михаил Матросов, “С++ без new и delete”

Своё управление памятью

• Qt

– Cross-platform application and UI framework

– QObject – класс, устанавливающий отношение родства, базовый для большинства классов

• Имеет список детей, удаляет их в своём деструкторе

• Имеет ссылку на родителя, которая может быть null

• Может менять родителя в процессе жизни

– QSharedPointer – аналог std::shared_ptr

• Предоставляет QSharedPointer<T>::create() по аналогии с std::make_shared<T>()

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

23

Page 24: Михаил Матросов, “С++ без new и delete”

QObject

• Сценарии использования:

– Создание объекта в стеке

• Удаляется при выходе из блока

– Динамическое создание объекта с родителем

• Удаляется родителем

– Динамическое создание объекта без родителя

• Нужно указать родителя или удалить вручную

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

24

Page 25: Михаил Матросов, “С++ без new и delete”

Qt::MakeChild

• Обёртка для безопасного сценария (см. обсуждение).

• Теперь new означает небезопасный сценарий

namespace Qt{template<class T, class... Args>T* MakeChild(Args&&... args){T* pObject = new T(std::forward<Args>(args)...);Q_ASSERT(pObject->parent() != nullptr);return pObject;

}}

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

25

Page 26: Михаил Матросов, “С++ без new и delete”

Qt::MakeChild

MyWidget::MyWidget(){// Safe, created on the stackQProgressDialog progress("In progress...", "Cancel", 0, 100);

// Safe, parent is specified{// Regular, valid since C++98QPushButton* pButton = new QPushButton("Push me", this);

// Proposed, modern C++ styleauto pButton2 = Qt::MakeChild<QPushButton>("And me", this);

}

// Unsafe, parent is null, need manual deletem_pDialog = new QDialog();

}

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

26

Page 27: Михаил Матросов, “С++ без new и delete”

Необходимые возможности С++14

• Возможности языка:

– Точная передача (perfect forwarding)

– Шаблоны с переменным количеством параметров (variadic templates)

• Возможности стандартной библиотеки:

– Умные указатели

– std::make_shared, std::make_unique

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

27

Можно обойтись и без них

Можно написать самому

Page 28: Михаил Матросов, “С++ без new и delete”

Заключение

• Хороший код – простой код

• Принцип «по умолчанию» для упрощения

• Проблемы с delete давно известны

• Для стандартной библиотеки код без newлучше, это объявляется подходом по умолчанию – для упрощения

• new и delete теперь ассоциируются с небезопасными операциями

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

28

Page 29: Михаил Матросов, “С++ без new и delete”

Спасибо за внимание!

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

29

Page 30: Михаил Матросов, “С++ без new и delete”

Библиография

[McConnell2004] McConnell, Steve, and Detlef Johannis. Code complete. Vol. 2. Redmond: Microsoft press, 2004.

[Wang2010] Wang, Rui, and Xuelei Qian. OpenSceneGraph 3.0: Beginner's guide. Packt Publishing Ltd, 2010.

[Lavavej2012] Lavavej, Stephan. STL11: Magic && Secrets. Going Native, 2012.

[Stroustrup2013] Stroustrup, Bjarne. A Tour of C++. Addison-Wesley, 2013.

[Stroustrup2014] Stroustrup, Bjarne. Make Simple Tasks Simple!CppCon, 2014.

[Sutter2014] Sutter, Herb. Back to the Basics! Essentials of Modern C++ Style. CppCon, 2014.

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

30

Page 31: Михаил Матросов, “С++ без new и delete”

Принцип «по умолчанию»

Задача Решение по умолчанию

Передача параметров в функцию Pass by value or const&См. [Sutter2014]

Возврат параметров из функции Return by valueСм. [Sutter2014]

Массив произвольного размера?Массив изменяющегося размера?Список произвольных объектов?

std::vector

Массив фиксированного размера std::array

Действие над каждым элементом контейнера

range-based for

Автоматическое управление динамической памятью

std::make_unique или std::make_shared

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

31

Page 32: Михаил Матросов, “С++ без new и delete”

Выражение намерения

• Что сделать, а не как сделать

• Более высокоуровневый код

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

32

Page 33: Михаил Матросов, “С++ без new и delete”

Память – ресурс

std∷make_unique

new/delete=

new/delete

malloc/free=

std∷fstream

fopen/fclose

Матросов Михаил, С++ без new и delete, Russian C++ User Group, Саратов, 2014

33