Item 36. Memory Management part 2
2007-02-25 10:13
309 查看
Are you thinking about doing your own class-specific memory management, or even replacing C++'s global new and delete? First, try this problem on for size.
The following code shows classes that perform their own memory management. Point out as many memory-related errors as possible, and answer the additional questions.
Consider the following code:
Why do B's operators delete have a second parameter, whereas D's do not? Do you see any way to improve the function declarations?
Continuing with the same piece of code: Which operator delete() is called for each of the following delete expressions? Why, and with what parameters?
Are the following two assignments legal?
Are there any memory-related errors or issues in the following code?
Consider the following code:
Why do B's operators delete have a second parameter, whereas D's do not?
The answer is: It's just preference, that's all. Both are usual deallocation functions, not placement deletes. (For those keeping score at home, see section 3.7.3.2/2 in the C++ standard.)
However, there's also a memory error lurking in this underbrush. Both classes provide an operator delete() and operator delete[](), without providing the corresponding operator new() and operator new[](). This is extremely dangerous, because the default operator new() and operator new[]() are unlikely to do the right thing. (For example, consider what happens if a further-derived class provides its own operator new() or operator new[]() functions.)
Guideline
And the second part of the question: Do you see any way to improve the function declarations?
All flavors of operator new() and operator delete() are always static functions, even if they're not declared static. Although C++ doesn't force you to say "static" explicitly when you declare your own, it's better to do so anyway, because it serves as a reminder to yourself as you're writing the code and as a reminder to the next programmer who has to maintain it.
Guideline
Continuing with the same piece of code: Which operator delete() is called for each of the following delete expressions? Why, and with what parameters?
This calls D::operator delete(void*).
This also calls D::operator delete(void*). Since B's destructor is virtual, of course D's destructor is properly called, but the fact that B's destructor is virtual also implicitly means that D::operator delete() must be called, even though B::operator delete() is not (in fact, cannot be) virtual.
As an aside to those who are interested in how compilers implement these things: The usual method is that the code actually generated for every destructor is given an invisible "when done destroying the object, should I delete it?" flag that is set appropriately (false when destroying an automatic object, true when destroying a dynamic object). The last thing the generated destructor code does is check the flag and, if it's true, call the correct operator delete().[1] This technique automatically ensures the correct behavior, namely that operator delete() appears to act "virtually" even though it is a static function and, therefore, cannot be virtual.
[1] The correct variant techniques for array-delete operations are left as an exercise for the reader.
This calls D::operator delete[](void*).
This is undefined behavior. The language requires that the static type of the pointer that is passed to operator delete[]() must be the same as its dynamic type. For more information on this topic, see also Scott Meyers' section, "Never Treat Arrays Polymorphically" in Meyers99.
Guideline
Guideline
Are the following two assignments legal?
The first assignment is fine; we're simply assigning the address of a member function to a pointer to member function.
The second assignment is illegal because void operator delete( void*, size_t ) throw() is not a nonstatic member function of B, even though as it's written, it may look like one. The trick here is to remember that operator new() and operator delete() are always static members, even if they're not explicitly declared static. It's a good habit to always declare them static, just to make sure that the fact is obvious to all programmers reading through your code.
Guideline
Are there any memory-related errors or issues in the following code?
Short answer: Yes, in every case. Some of them are just a little more subtle than others, that's all.
This invites a memory leak, because no corresponding placement delete exists. Similarly below:
This invites a memory leak, because no operator delete() matches this signature. If an exception is thrown during construction of an object to be located in memory allocated by this function, the memory will not be properly freed. For example, consider the following code:
Further, any memory allocated by this operator new() cannot safely be deleted because the class does not provide a usual operator delete(). This means that a base or derived class's operator delete(), or the global one, will have to try to deal with this deallocation (almost certainly unsuccessfully, unless you also replace all such surrounding operator delete's, which would be onerous and evil).
This Y::operator delete() is useless because it can never be called.
This is a serious error, because the replacement global operator delete() is going to delete memory allocated normally by the default ::operator new(), not by SharedMemory::Allocate(). The best you can hope for is a quick core dump. Evil.
The same comment applies again here, but this time it's slightly more subtle. This replacement operator delete() will be called only if an expression like "new (nothrow) T" fails because T's constructor exits with an exception and will try to deallocate memory not allocated by SharedMemory::Allocate(). Evil and insidious.
Guideline
If you got and understood all of these answers, then you're definitely on your way to becoming an expert in memory-management mechanics.
The following code shows classes that perform their own memory management. Point out as many memory-related errors as possible, and answer the additional questions.
Consider the following code:
class B { public: virtual ~B(); void operator delete ( void*, size_t ) throw(); void operator delete[]( void*, size_t ) throw(); void f( void*, size_t ) throw(); }; class D : public B { public: void operator delete ( void* ) throw(); void operator delete[]( void* ) throw(); };
Why do B's operators delete have a second parameter, whereas D's do not? Do you see any way to improve the function declarations?
Continuing with the same piece of code: Which operator delete() is called for each of the following delete expressions? Why, and with what parameters?
D* pd1 = new D; delete pd1; B* pb1 = new D; delete pb1; D* pd2 = new D[10]; delete[] pd2; B* pb2 = new D[10]; delete[] pb2;
Are the following two assignments legal?
typedef void (B::*PMF)(void*, size_t); PMF p1 = &B::f; PMF p2 = &B::operator delete;
Are there any memory-related errors or issues in the following code?
class X { public: void* operator new( size_t s, int ) throw( bad_alloc ) { return ::operator new( s ); } }; class SharedMemory { public: static void* Allocate( size_t s ) { return OsSpecificSharedMemAllocation( s ); } static void Deallocate( void* p, int i = 0 ) { OsSpecificSharedMemDeallocation( p, i ); } }; class Y { public: void* operator new( size_t s, SharedMemory& m ) throw( bad_alloc ) { return m.Allocate( s ); } void operator delete( void* p, SharedMemory& m, int i ) throw() { m.Deallocate( p, i ); } }; void operator delete( void* p ) throw() { SharedMemory::Deallocate( p ); } void operator delete( void* p, std::nothrow_t& ) throw() { SharedMemory::Deallocate( p ); }
Solution
Let's take the questions one at a time.Consider the following code:
class B { public: virtual ~B(); void operator delete ( void*, size_t ) throw(); void operator delete[]( void*, size_t ) throw(); void f( void*, size_t ) throw(); }; class D : public B { public: void operator delete ( void* ) throw(); void operator delete[]( void* ) throw(); };
Why do B's operators delete have a second parameter, whereas D's do not?
The answer is: It's just preference, that's all. Both are usual deallocation functions, not placement deletes. (For those keeping score at home, see section 3.7.3.2/2 in the C++ standard.)
However, there's also a memory error lurking in this underbrush. Both classes provide an operator delete() and operator delete[](), without providing the corresponding operator new() and operator new[](). This is extremely dangerous, because the default operator new() and operator new[]() are unlikely to do the right thing. (For example, consider what happens if a further-derived class provides its own operator new() or operator new[]() functions.)
Guideline
Always provide both class-specific new (or new[]) and class-specific delete (or delete[]) if you provide either. |
All flavors of operator new() and operator delete() are always static functions, even if they're not declared static. Although C++ doesn't force you to say "static" explicitly when you declare your own, it's better to do so anyway, because it serves as a reminder to yourself as you're writing the code and as a reminder to the next programmer who has to maintain it.
Guideline
Always explicitly declare operator new() and operator delete() as static functions. They are never nonstatic member functions. |
D* pd1 = new D; delete pd1;
This calls D::operator delete(void*).
B* pb1 = new D; delete pb1;
This also calls D::operator delete(void*). Since B's destructor is virtual, of course D's destructor is properly called, but the fact that B's destructor is virtual also implicitly means that D::operator delete() must be called, even though B::operator delete() is not (in fact, cannot be) virtual.
As an aside to those who are interested in how compilers implement these things: The usual method is that the code actually generated for every destructor is given an invisible "when done destroying the object, should I delete it?" flag that is set appropriately (false when destroying an automatic object, true when destroying a dynamic object). The last thing the generated destructor code does is check the flag and, if it's true, call the correct operator delete().[1] This technique automatically ensures the correct behavior, namely that operator delete() appears to act "virtually" even though it is a static function and, therefore, cannot be virtual.
[1] The correct variant techniques for array-delete operations are left as an exercise for the reader.
D* pd2 = new D[10]; delete[] pd2;
This calls D::operator delete[](void*).
B* pb2 = new D[10]; delete[] pb2;
This is undefined behavior. The language requires that the static type of the pointer that is passed to operator delete[]() must be the same as its dynamic type. For more information on this topic, see also Scott Meyers' section, "Never Treat Arrays Polymorphically" in Meyers99.
Guideline
Never treat arrays polymorphically. ca9e |
Prefer using vector<> or deque<> instead of arrays. |
typedef void (B::*PMF)(void*, size_t); PMF p1 = &B::f; PMF p2 = &B::operator delete;
The first assignment is fine; we're simply assigning the address of a member function to a pointer to member function.
The second assignment is illegal because void operator delete( void*, size_t ) throw() is not a nonstatic member function of B, even though as it's written, it may look like one. The trick here is to remember that operator new() and operator delete() are always static members, even if they're not explicitly declared static. It's a good habit to always declare them static, just to make sure that the fact is obvious to all programmers reading through your code.
Guideline
Always explicitly declare operator new() and operator delete() as static functions. They are never nonstatic member functions. |
Short answer: Yes, in every case. Some of them are just a little more subtle than others, that's all.
class X { public: void* operator new( size_t s, int ) throw( bad_alloc ) { return ::operator new( s ); } };
This invites a memory leak, because no corresponding placement delete exists. Similarly below:
class SharedMemory { public: static void* Allocate( size_t s ) { return OsSpecificSharedMemAllocation( s ); } static void Deallocate( void* p, int i ) { OsSpecificSharedMemDeallocation( p, i ); } }; class Y { public: void* operator new(size_t s, SharedMemory&m ) throw( bad_alloc ) { return m.Allocate( s ); }
This invites a memory leak, because no operator delete() matches this signature. If an exception is thrown during construction of an object to be located in memory allocated by this function, the memory will not be properly freed. For example, consider the following code:
SharedMemory shared; ... new (shared) Y; // if Y::Y() throws, memory is leaked
Further, any memory allocated by this operator new() cannot safely be deleted because the class does not provide a usual operator delete(). This means that a base or derived class's operator delete(), or the global one, will have to try to deal with this deallocation (almost certainly unsuccessfully, unless you also replace all such surrounding operator delete's, which would be onerous and evil).
void operator delete( void* p, SharedMemory& m, int i ) throw() { m.Deallocate( p, i ); } };
This Y::operator delete() is useless because it can never be called.
void operator delete( void* p ) throw() { SharedMemory::Deallocate( p ); }
This is a serious error, because the replacement global operator delete() is going to delete memory allocated normally by the default ::operator new(), not by SharedMemory::Allocate(). The best you can hope for is a quick core dump. Evil.
void operator delete( void* p, std::nothrow_t& ) throw() { SharedMemory::Deallocate( p ); }
The same comment applies again here, but this time it's slightly more subtle. This replacement operator delete() will be called only if an expression like "new (nothrow) T" fails because T's constructor exits with an exception and will try to deallocate memory not allocated by SharedMemory::Allocate(). Evil and insidious.
Guideline
Always provide both class-specific new (or new[]) and class-specific delete (or delete[]) if you provide either. |
相关文章推荐
- Effective C++学习笔记(Part One:Item 1-4)
- Part itemtype解读(2): RelationshipType
- Item 27. Minimizing Compile-time Dependencies part 2
- Effective C++学习笔记(Part Four:Item 18-25)
- Effective C++学习笔记(Part One:Item 1-4)
- 【原创】在DataFormWebPart中将列表附件显示为图片(二)[How to display list item attachments as image in DFWP Part 2]
- Customization of SharePoint list menu item – Part 1 add Custom Action Item
- Effective C++学习笔记(Part Five:Item 26-31)
- Part Itemtype解读(3): View
- Item 41. Object Lifetimes part 2
- Item 26. Minimizing Compile-time Dependencies part 1
- 8.python之面相对象part.7(__setitem__,__getitem,__delitem__)
- Sitecore Digital Marketing System, Part 1: Creating personalized, custom content for site visitors(自定义SiteCore中的 Item的Personalize的Condition) -摘自网络
- Effective C++学习笔记(Part Four:Item 18-25)
- Part Itemtype解读(4): Event的响应机制设定
- Item 40. Object Lifetimes part 1
- Item 26. Minimizing Compile-time Dependencies part 1
- SCSF Business Modules: Start Up And The ControlledWorkItem (Introduction To CAB/SCSF Part 20)
- SmartClient Software factory中的Composite UI Application Block(Cab)技术了解(二):WorkItem&SmartPart
- Effective C++学习笔记(Part Four:Item 18-25)