|
|
(12 intermediate revisions by 2 users not shown) |
Line 1: |
Line 1: |
− | This page discusses strategies writing and debugging code in Slicer3 such that you avoid memory leaks and double deletes.
| + | <big>'''Note:''' We are migrating this content to the slicer.org domain - <font color="orange">The newer page is [https://www.slicer.org/wiki/Slicer3:Memory_Management here]</font></big> |
− | | |
− | === What is a memory leak? ===
| |
− | | |
− | Memory leaks occur when a section of code allocates a block of memory that is never reclaimed.
| |
− | | |
− | void MyFunction()
| |
− | {
| |
− | short *buffer = new short[1000]; // this memory is leaked!
| |
− | }
| |
− | | |
− | === How does memory get deleted multiple times? ===
| |
− | | |
− | A frequent problem with passing pointers from routine to routine is that it becomes very easy to loose track of which routine is responsible for deleting the block of memory. APIs rely on their documentation to educate the developers as to whether the developer is responsible for deleting the memory.
| |
− | | |
− | short *buffer = mykit::CreateBuffer(1000);
| |
− | ...
| |
− | myKit::CleanUp();
| |
− | ...
| |
− | delete [] buffer; // did myKit::CleanUp() already delete this memory?
| |
− | | |
− | === Reference Counting ===
| |
− | | |
− | VTK and ITK both use reference counting to keep track of outstanding references to an object and automatically delete an object when it is no longer needed. VTK does with via calls to New()/Delete() and Register()/UnRegister() to increase and decrease the number of references to an object. If a function call returns a pointer to an object, the caller can choose to keep a long term handle to that object by increasing the reference count. The caller is responsible for decreasing the reference count when they no longer need the object.
| |
− | | |
− | <pre>
| |
− | MyClass::Function1()
| |
− | {
| |
− | this->SomeObject = MyOtherClass::GetObject();
| |
− | this->SomeObject->Register( this );
| |
− | }
| |
− | | |
− | MyClass::~MyClass()
| |
− | {
| |
− | if (this->SomeObject)
| |
− | {
| |
− | this->SomeObject->UnRegister();
| |
− | this->SomeObject = 0;
| |
− | }
| |
− | }
| |
− | </pre> | |
− | | |
− | === Stack-based coding ===
| |
− | | |
− | One way to avoid memory leaks is to avoid allocating objects on the heap. Instead objects are allocated directly on the stack and deallocated from the stack using standard scoping rules.
| |
− | | |
− | <pre>
| |
− | MyObject::Function()
| |
− | {
| |
− | MyOtherObject* object1 = new MyOtherObject(); // object1 allocated from the heap. The developer must reclaim this memory later or it is a leak.
| |
− |
| |
− | MyOtherObject object2(); // object2 allocated on the stack. Memory reclaimed automatically when the object goes out of scope (in this case at the end of the function).
| |
− | }
| |
− | | |
− | The Standard Template Library (STL) promotes which type of coding. To use STL safely, all objects stored in STL containers should be default constructible and have proper copy constructors, etc. This is needed so the containers can shuffle objects in memory, reallocate space, etc.
| |
− | When STL containers of objects go out of scope, the destructor of the vector calls the destructor of each object it contains. If these vectors are vectors to pointers to objects, the '''destructors of each object are not called.'''
| |
− | | |
− | <pre>
| |
− | MyObject::Function()
| |
− | {
| |
− | std::vector<MyObject*> vec;
| |
− |
| |
− | vec.push_back( MyOtherClass::GetObject(0) );
| |
− | vec.push_back( MyOtherClass::GetObject(1) );
| |
− | } // destructors are not called on items pointed to by elements of the vector
| |
− | </pre>
| |
− | | |
− | === SmartPointers ===
| |
− | | |
− | ITK has used SmartPointers since its beginning to simply the reference counting semantics. With SmartPointers, the reference count of an object is automatically increased when assigned to a SmartPointer and automatically decreased when ''unassigned'' from a SmartPointer. Objects are unassigned from a SmartPointer whenever that SmartPointer is assigned to another object or to 0. The latter occurs automatically whenever a SmartPointer goes out of scope.
| |
− | | |
− | <pre> | |
− | MyObject::Function()
| |
− | {
| |
− | itk::Image<short, 2>::Pointer smartPointer = itk::Image<short,2>::New(); // creates a new image
| |
− | | |
− | smartPointer = MyOtherClass::GetImage(); // reference count of the image previously pointed to by
| |
− | // smartPointer decremented. Reference count on image returned
| |
− | // by MyOtherClass::GetImage() incremented.
| |
− | | |
− | | |
− | } // smartPointer goes out of scope, reference count of the image returned by MyOtherClass::GetImage() decremented.
| |
− | </pre>
| |
− | | |
− | Because SmartPointers abide by scoping semantics, they can be used with STL containers.
| |
− | | |
− | <pre>
| |
− | std::vector<itk::Image<short, 2>::Pointer> vec;
| |
− | | |
− | vec.push_back( itk::Image<short, 2>::New() );
| |
− | vec.push_back( itk::Image<short, 2>::New() );
| |
− | | |
− | vec = std::vector<itk::Image<short, 2>::Pointer>(); // vec is now a copy of an empty vector
| |
− | // Reference counts to the images pointed to by the elements
| |
− | // of the vector properly managed.
| |
− | </pre> | |
− | | |
− | We like SmartPointers in ITK so much, they were added into VTK. The behavior is the same as in ITK. However, the New() in VTK has always returned a raw pointer whereas the New() method in ITK returns a SmartPointer. So a little more setup is needed to use a SmartPointer in VTK.
| |
− | | |
− | <pre>
| |
− | itk::Image<short, 2>::Pointer itkPointer = itk::Image<short, 2>::New(); // after this line, reference count is 1
| |
− | | |
− | vtkImageData *image = vtkImageData::New(); // after this line, reference count is 1
| |
− | vtkSmartPointer<vtkImageData> vtkPointer = image; // after this line, reference count is 2
| |
− | image->Delete(); // after this line, reference count is 1
| |
− | </pre>
| |
− | | |
− | VTK SmartPointers can be used in STL containers just like ITK SmartPointers.
| |