С++ without new and delete

37
C++ without new and delete by Mikhail Matrosov [email protected] [email protected]

Upload: platonov-sergey

Post on 10-Aug-2015

104 views

Category:

Software


0 download

TRANSCRIPT

C++ without new and delete

by Mikhail [email protected]

[email protected]

C++ Russia, Moscow, 2015 2

Instead of contents

• What?– Avoid new and delete in modern C++

• Why?– new and delete increase complexity

• How?– std::make_shared, std::make_unique– Containers (STL, boost, libcds, etc.; or your own)– Wrappers for third-party smart pointers– Wrappers for libraries with specific memory management

C++ Russia, Moscow, 2015 3

Modern С++

• Modern С++ includes– С++14 features– Modern best practices

• C++14 is officially released– ISO/IEC 14882:2014 / December 15, 2014– Fully or partially supported by all major compilers

C++ Russia, Moscow, 2015 4

What & how?Managing memory and objects from C to modern C++

C++ Russia, Moscow, 2015 5

Objects in С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 memory InitObject(pObject); // Establish invariants and acquire resources UseObject(pObject); // Do the work ClearObject(pObject); // Release resources free(pObject); // Release memory // By the way: error handling :-/}

C++ Russia, Moscow, 2015 6

Objects in С++

• Encapsulation:– Constructors– Destructors– Methods

class Object{ // Object memberspublic: Object(); // Establish invariants and acquire resources ~Object(); // Release resources void Use(); // Do the work};

• Error handling:– Exceptions

C++ Russia, Moscow, 2015 7

new• Allocate memory• Initialize object

in constructor

delete• Deinitialize object

in destructor • Free memory

new creates a single object on the heap– Usually to share the object

new[] creates a number of objects on the heap– Usually when number is known at runtime or is big enough

C++ Russia, Moscow, 2015 8

Objects in С++// 1. Naive C++ styleObject* p = new Object();p->Use();delete p;

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

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

// 4. New bullet-proof modern C++auto up2 = std::make_unique<Object>();up2->Use();

C++ Russia, Moscow, 2015 9

Shared objects in С++void UseObject(Object*); // Shall only observe the objectvoid AcceptObject(Object*); // Shall delete the objectvoid ShareObject(std::shared_ptr<Object>); // May do both!

// Naive C++Object* p = new Object();p->Use();UseObject(p); // Preserve ownershipp->Use();AcceptObject(p); // Transfer ownership

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

// Modern C++auto sp = std::make_shared<Object>();sp->Use();ShareObject(sp); // Sharesp->Use();ShareObject(std::move(sp)); // Share and discard ownership

C++ Russia, Moscow, 2015 10

Reference counting objects in С++

// 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();

C++ Russia, Moscow, 2015 11

Dynamic arrays in С++void UseArray(int* ptr, int count);

int n = 100;

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

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

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

C++ Russia, Moscow, 2015 12

Why?Advantages of make functions

C++ Russia, Moscow, 2015 13

Better == simple• A bit of authority:

– Software's Primary Technical Imperative: Managing Complexity [McConnel2004]

– Make Simple Tasks Simple! [Stroustrup2014]– ≈99.9998% developers are not experts [Sutter2014]– KISS principle (Keep It Simple, Stupid)

• What is simple?– Keep simple things simple– Don’t make complex things unnecessarily complex– Write for clarity and correctness first, optimize later

• How to keep it simple?– DRY principle (Don’t Repeat Yourself)– “By default” principle [Sutter2014]

C++ Russia, Moscow, 2015 14

“By default” principle

• The main point:– Common task ⇔ common solution

• Advantages:– Mutual understanding with other developers

• Programs are parallelized in development-time too!

– Easy for beginners– Less to think – productive laziness – Same as for coding conventions

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

C++ Russia, Moscow, 2015 15

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>(), // Safe GenerateId());}

DRY!

// May throw!

Exactly the same for std::shared_ptr and std::make_shared!

// May leak!

C++ Russia, Moscow, 2015 16

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

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

counter

Object

sp

Two memory allocations

C++ Russia, Moscow, 2015 17

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

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

counter Object

One memory allocation. We Know Where You Live

optimization [Lavavej2012].

sp

C++ Russia, Moscow, 2015 18

Advantages of make functions

• Both:– Exception safe– No type name duplication– Symmetry

• std::make_shared:– Single memory allocation

C++ Russia, Moscow, 2015 19

std::make_shared and std::weak_ptr

counter Objectsp

wp

auto sp = std::make_shared<Object>();std::weak_ptr<Object> wp = sp;sp.reset();

C++ Russia, Moscow, 2015 20

When not to use make functions

• Both:– Cannot specify custom deleters– Cannot pass braced initializer

• std::unique_ptr:– Cannot specify custom allocator (for std::shared_ptr there is

std::allocate_shared function) [see N3588, section IV.4]

• std::shared_ptr:– Potential memory waste for long-living weak pointers– Works poorly with class-specific operators new&delete– Potential false sharing of the object and the reference counting

block [see question on StackOverflow]

• See also “Effective modern C++”, Item 21 [Meyers2014]

C++ Russia, Moscow, 2015 21

Summary

• By default, raw delete is not used in C++ since RAII and standard containers

• By default, raw new is not used in modern C++ within the standard library

• If either is used, situation is not default and requires special handling (and, probably, special attention)

C++ Russia, Moscow, 2015 22

Beyond standard libraryDifferent memory management techniques in C++

C++ Russia, Moscow, 2015 23

Memory management techniques

• No memory management– Explicit new and delete– Not used in good code

• Non-intrusive smart pointers– Applicable for all objects– Standard library

• Intrusive smart pointers– Reference counting objects– Third-party libraries

• Other– Parent to child relationships, coupling with system resources– Qt, MFC

C++ Russia, Moscow, 2015 24

Reference counting objects

• OpenSceneGraph– Open source 3D framework written in C++ and OpenGL– osg::Referenced – base reference counting class

• ref() increments counter• unref() decrements counter and deletes the object when counter

drops to zero

– osg::ref_ptr<T> – smart pointer• Calls ref() on construction• Calls unref() on destruction

C++ Russia, Moscow, 2015 25

OSG: C++ & RAII

• Official code sample from [Wang2010, p. 78]• Perfect fit for upgrade– Can be done automatically

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

C++ Russia, Moscow, 2015 26

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

C++ Russia, Moscow, 2015 27

Other memory management

• MFC (Microsoft Foundation Class)– C++ wrapper classes for Windows API– CWnd – base class for all windows objects

• Represents both a C++ object and an HWND– C++ objects are allocated in the application's heap– HWNDs are allocated in system resources

• A set of rules that prevent system resource or memory leaks

– Requires new and delete this for modeless dialogs (see TN017: Destroying Window Objects)

C++ Russia, Moscow, 2015 28

Modeless dialogs in MFCvoid CMainFrame::OnBnClickedCreate(){ CMyDialog* pDialog = new CMyDialog; pDialog->Create(IDD_MY_DIALOG, this); pDialog->ShowWindow(SW_SHOW);}

class CMyDialog : public CDialog{// ...protected: void CMyDialog::PostNcDestroy() override { CDialog::PostNcDestroy(); delete this; }// ...};

C++ Russia, Moscow, 2015 29

Other memory management

• Qt– Cross-platform application and UI framework– QObject – base class, managing child-parent relationships

• Has a list of children, deletes them on destruction• Has a pointer to parent, which may be null• May change parent during the lifetime

– Requires explicit new and delete by design (see discussion)

C++ Russia, Moscow, 2015 30

Summary

• Same default rules for reference counting objects• May require dedicated wrappers for some libraries,

like in MFC• May not be applicable for specific memory

management techniques, like in Qt

C++ Russia, Moscow, 2015 31

Conclusion

• Good code is simple code• “By default” principle to simplify things• By default, raw delete is not used in C++ since it was

initially standardized • Avoiding raw new works good in standard library;

declare this approach a default – for simplification• If either new or delete is used, we assume that– Situation requires special handling (and attention)– Or specific memory management technique is used

C++ without new and delete

Questions?Можно на русском :)

C++ Russia, Moscow, 2015 33

Bibliography[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.[Meyers2014] Meyers, Scott. Effective Modern C++: 42 Specific Ways to Improve Your Use of C++ 11 and C++ 14. "O'Reilly Media, Inc.", 2014.

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

34

QObject

• Use cases:– Object is created on the stack

• Deleted once went out of scope

– Object is created in the heap with a parent• Deleted by the parent

– Object is created in the heap without a parent• Need to delete manually• Or need to specify a parent manually

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

35

Qt::MakeChild

• Wrapper for safe use case• Now new goes for unsafe case• Drawback: cannot delay parent assignment

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

36

Qt::MakeChildMyWidget::MyWidget(){ // Safe, created on the stack QProgressDialog progress("In progress...", "Cancel", 0, 100);

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

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

// Unsafe, parent is null, need manual delete m_pDialog = new QDialog();}

C++ Russia, Moscow, 2015 37

CModelessDialogclass CModelessDialog : public CDialog {protected: using CDialog::CDialog; void CModelessDialog::PostNcDestroy() override { CDialog::PostNcDestroy(); delete this; }};

class CMyDialog : public CModelessDialog {// ...public: static CMyDialog* CreateModeless(CWnd* pParent = nullptr) { auto* pDialog = new CMyDialog; pDialog->Create(IDD_MY_DIALOG, pParent); pDialog->ShowWindow(SW_SHOW); return pDialog; }protected: CMyDialog() = default;// ...};