the beauty of destruction pete isensee microsoft [email protected] game developers conference march...
TRANSCRIPT
The Beauty of Destruction
Pete [email protected] Developers Conference March 2009
C++ Destructor Definition• One• Special• Deterministic• Automatic• Symmetric• Member• Function
• With• A special name• No parameters• No return type
• Designed to• Give last rites• Before object
death
Language ComparisonLanguage
Name Notation
Deterministic
Notes
C n/a n/a n/aC++ Destructor ~T() YesC++/CLI Destructor ~T() YesC++/CLI Finalizer !T() No Called during
GCC# Finalizer ~T() No Called during
GCC# IDisposabl
eDispose()
No
Java Finalizer finalize() No Called during GC
When Destructors Are InvokedScenario Destructor called NotesNamed automatic Scope exitFree store delete operator Prior to memory being freedStatics and globals Program exit Reverse order of constructionArray elements From last element to firstSTL container elements
Unspecified order
Temporary End of expression in which it was created
If bound to reference or named object, when object exits scope
Exception thrown Stack unwindingExplicit dtor p->~T();exit() For global & static objects Reverse order of constructionabort() No; global & static dtors not
called
Order of Destruction• Rule of Thumb: Reverse order of
construction• Specifically:
1. Destructor body2. Data members in reverse order of declaration3. Direct non-virtual base classes in reverse
order4. Virtual base classes in reverse order
Destruction Order Exampleclass Human : Ego, virtual Id {};class Vulcan : Katra, Kolinahr {};class Spock : Human, Vulcan{ Tricorder t; Phaser p; };Spock s;
SpockTricorderPhaserHuman
Ego
Id
VulcanKatra
Kolinahr
61
32
7
4
9
8
5
Implicit Destructors• Not specified by the programmer• Inline by default• Public• Recommended for struct-like POD-only
objects• For everything else, avoid implicit
destructors • Better debugging• Improved perf analysis
Trivial Destructors• Destructors that never ever do anything
• Compiler can optimize away
• Requirements• Implicit• Not virtual• All direct base classes have trivial dtors• All non-static members have trivial dtors
Virtual Destructors• Guarantee that derived classes get cleaned up• Rule of thumb: if class has virtual functions, dtor
should be virtual• If delete on a Base* could ever point to a Derived*
• Perf: Obj with any virtual funcs includes a vtable ptr
• Idiom exceptions: mixin classes• Pure signals abstract class
virtual ~T() = 0 { }
Partial Construction & Destruction• Dtors are only called for fully constructed
objs• If a ctor throws, obj was not fully
constructed• Object dtor will not be called• But fully constructed subobjects will be
destroyed
• Always use RAII within ctors• Resource Acquisition Is Initialization
Virtual Functions in Destructors• Virtual functions are not virtual inside
dtorsclass Helmsman {public: // Red alert! virtual ~Helmsman() { Release(); }protected: virtual void Release() {}};class Sulu : public Helmsman { … };
C++ Exception Handling• Destructors : Exceptions :: Spock : Kirk• Wrap any function that acquires a resource
in a class where dtor releases the resource• Never allow an exception to exit a dtor
• Best: don’t throw in the dtor• OK: wrap throwing code in a try/catch
• Good advice even if you don’t use C++EH
Multithreading• You are responsible for protecting objects
and their contents• Sharing an object across threads
• Use shared_ptr• Or some other reference counting• Or otherwise ensure only one thread can
destroy
• Protect shared memory (global counters, ref counts) in dtors
delete and Destructorsdelete p is a two-step process. Pseudo code:
// T* p = new T(…); ≈T *p = (T*)operator new( sizeof(T) );new (p) T(…); // placement new
// delete p ≈if( p != NULL ) { p->~T(); // explicit destructor operator delete( p );}
Explicit Destructors• Destructors can be called directly• Avoid 99.99% of the time• Very powerful for custom memory
scenarios• Example uses
• w/ placement new• STL allocators
void* p = Alloc(…);Kirk* pk = new (p) Kirk;pk->~Kirk();Free( pk );
std::allocator• Allocators enable custom STL container
memory• Two key destructive functions
void allocator::destroy( T* p ){ ((T*)p)->~T(); } // default impl.
void allocator::deallocate( T* p, size_type ){ operator delete( p ); } // default impl.
Case Study: std::vectortemplate <class T> vector::~vector(){ if( m_pFirst != 0 ) { for( T* p = m_pFirst; p != m_pLast; ++p ) m_Alloc.destroy( p ); m_Alloc.deallocate( m_pFirst ); }}
shared_ptr• Templated non-intrusive deterministically
referenced-counted smart pointer
Object Category Owned By Their Destroyed WhenAutomatic Block Control Leaves BlockData Members Parent Parent diesElements Container Container diesDynamically Allocated shared_ptrs All shared_ptrs die
shared_ptr<T> sp( new T(…) );
Case Study: tr1::shared_ptrtemplate <class T>shared_ptr::~shared_ptr(){ if( m_pRep != NULL ) // if we’re not a NULL ptr { // and we’re the last owner if( InterlockedDecrement( &m_pRep->RefCount ) == 0 ) { // dispose of the shared object delete m_pSharedPtr; // use deleter if specified // if we’re done with the control block if( InterlockedDecrement( &m_pRep->WeakRefCount ) == 0 ) delete m_pRep; // use allocator.destroy if specified } }}
shared_ptr deleters• Deleter: a functor called on the stored raw
pointer when ref count hits zero
template <class T> struct Deleter{ void operator()( T* p ) { Free( p ); } };
Khan* p = Alloc(…);shared_ptr<Khan> sp( p, Free );shared_ptr<Khan> sp( p, Deleter<Khan>() );
Destructor Idioms to Love• delete p• if (p!=NULL) free(p)• CloseHandle()• DeleteCriticalSection()• SetEvent()• pComPtr->Release()• --refCount• InterlockedDecrement(
)• flush()• try { Close() }
catch( … )• closesocket()• assert( … )• { }
Destructor Gotchas: Red Alert• delete a; delete b• if( p != NULL ) delete p• m_Handle = NULL• Cleanup()• m_Member.Cleanup()• for( itr = v.beg(); itr != v.end(); ++itr ) delete *itr
Case Study: Multiple owned resourcesstruct Uhura { X* p; Y* q; };// Red alert: leak potentialUhura::Uhura() : p(new X), q(new Y) { }Uhura::~Uhura() { delete p; delete q; }
struct Uhura { shared_ptr<X> sp; shared_ptr<Y> sq; };// SafeUhura::Uhura() : sp(new X), sq(new Y) { }Uhura::~Uhura() {}
Performance• Destructors are called a LOT• They’re invisible in code• Streamline common dtors• The best dtor is empty• Inlining• Profile
}Lots o’ destruction here
Enterprise::~Enterprise(){}
Case Study: Destructor TuningEnterprise::~Enterprise(){ Unload(); m_ReactorCore.Empty(); if( m_pOrdnance != NULL ) { delete m_pOrdnance; m_pOrdnance = NULL; } if( m_hAlert != NULL ) { CloseHandle(m_hAlert); m_hAlert = NULL; } for( int i = 0; i < m_CrewSize; i++ ) delete m_Crew[i];}
Enterprise::~Enterprise(){ delete m_pOrdnance; if( m_Alert != NULL ) CloseHandle( m_hAlert ); for( int i = 0; i < m_CrewSize; i++ ) delete m_Crew[i];}
• Never own more than a single raw resource
• Never let any exceptions escape a dtor• Never call virtual funcs from a dtor• Avoid clearing data unnecessarily (e.g.
zeroing memory, setting ptrs to NULL)• Avoid implicit dtors for non-PODs• Avoid dtors that call other member funcs
Best Practices
Best Practices: Do This• Embrace RAII: wrap any function that
acquires a resource in a class where the dtor releases
• Embrace shared_ptrs to avoid leaks and deletes
• Virtual dtor iff delete Base* could be a Derived*
• Validate state in dtors: guaranteed choke point
• The best destructor: an empty destructor• Second best: releasing a single resource
© 2009 Microsoft Corporation. All rights reserved.This presentation is for informational purposes only. Microsoft makes no warranties, express or implied, in this summary.
http://www.xna.com
© 2009 Microsoft Corporation. All rights reserved.This presentation is for informational purposes only. Microsoft makes no warranties, express or implied, in this summary.
http://www.xna.com