С++ without new and delete
TRANSCRIPT
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 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 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 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++ 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;// ...};