Guru of the Week 条款10:内存管理(下篇)
2001-10-23 20:19
585 查看
GotW#10MemoryManagement-PartII
著者:HerbSutter
翻译:kingofark
[声明]:本文内容取自www.gotw.ca网站上的GuruoftheWeek栏目,其著作权归原著者本人所有。译者kingofark在未经原著者本人同意的情况下翻译本文。本翻译内容仅供自学和参考用,请所有阅读过本文的人不要擅自转载、传播本翻译内容;下载本翻译内容的人请在阅读浏览后,立即删除其备份。译者kingofark对违反上述两条原则的人不负任何责任。特此声明。
Revision1.0
GuruoftheWeek条款10:内存管理(下篇)
难度:6/10
(你在考虑对某个类实现你自己特定的内存管理方案吗?甚或是想干脆替换掉C++的全局操作符new和delete?先试试下面这个问题再说吧!)
[问题]
下面的代码摘自某个程序,这个程序有一些类使用它们自己的内存管理方案。请尽可能的找出与内存有关的错误,并回答其注释中的附加题。
[解答]
附加题答案:这是出于个人喜好(preference)。两种都是正常的回收(译注:作者在这里用了“deallocation(去配)”一词,鄙人一律翻译为“回收”)函数,而不是placementdeletes(译注:关于placementdelete和placementnew,可以阅读StanleyLippman的《InsideTheC++ObjectModel(深度探索C++对象模型)》一书中的6.2节:PlacementOperatorNew的语意)。
另外,这两个类都提供了delete和delete[],却没有提供相对应的new和new[]。这是非常危险的。你可以试想,当更下层的派生类提供了它自己的new和new[]时会发生什么!
这里调用了D::operatordelete(void*)。
这里调用D::operatordelete(void*)。因为B的析构函数(destructor)被声明成virtual,所以D的析构函数(destructor)理所当然会被正常调用,但同时这也意味着,即使B::operatordelete()不声明成virtual,D::operatordelete()也必将被调用。
这里D::operatordelete[](void*)被调用。
这里的行为是不可预料的。C++语言要求,传递给delete的指针之静态类型必须与其之动态类型一样。关于这个问题的进一步谈论,可以参看ScottMeyers在《EffectiveC++》或者《MoreEffectiveC++》中有关“NeverTreatArraysPolymorphically”的部分(译注:这里指的应该是《MoreEffectiveC++》中的条款3:绝对不要以多态方式处理数组)。
第一个赋值语句没问题,但是第二个是不合法的,因为“voidoperatordelete(void*,size_t)throw()”并不是B的成员函数,即使它被写在上面使其看上去很像是。这里有一个小伎俩需要弄清楚,即new和delete总是静态的,即使它们不被显式的声明为static。总是把它们声明为static是个很好的习惯,这可以让所有阅读你代码的程序员们明白无误的认识到这一点。
这会产生内存泄漏,因为没有相应的placementdelete存在。下面的代码也是一样:
这里也产生内存泄漏,因为没有对应的delete。如果在用这个函数分配的内存里放置对象的构造过程中抛出了异常,内存就不会被正常释放。例如:
在这里,内存还无法被安全的删除,因为类中没有提供正常的operatordelete。这意味着,基类或者派生类的operatordelete(或者是那个全局的delete)将会试图处理这个回收操作(这几乎肯定会失败,除非你替换掉周围环境中所有类似的delete——这实在是一件繁琐和可怕的事情)。
这里的这个delete毫无用处,因为它从不会被调用。
这是一个严重的错误,因为它将要删除那些被缺省的::operatornew分配出来的内存,而不是被SharedMemory::Allocate()分配的内存。你顶多只能盼来一个迅速的coredump。真是见鬼!
这里也是一样,只是更为微妙。这里的delete只会在“new(nothrow)T”失败的时候才会被调用,因为T的构造函数(constructor)会带着一个异常来终止,并企图回收那些不是被SharedMemory::Allocate()分配的内存。这真是又邪恶又阴险!
好,如果你回答出了以上所有的问题,那你就肯定是走在成为内存管理机制专家的光明大道上了。
著者:HerbSutter
翻译:kingofark
[声明]:本文内容取自www.gotw.ca网站上的GuruoftheWeek栏目,其著作权归原著者本人所有。译者kingofark在未经原著者本人同意的情况下翻译本文。本翻译内容仅供自学和参考用,请所有阅读过本文的人不要擅自转载、传播本翻译内容;下载本翻译内容的人请在阅读浏览后,立即删除其备份。译者kingofark对违反上述两条原则的人不负任何责任。特此声明。
Revision1.0
GuruoftheWeek条款10:内存管理(下篇)
难度:6/10
(你在考虑对某个类实现你自己特定的内存管理方案吗?甚或是想干脆替换掉C++的全局操作符new和delete?先试试下面这个问题再说吧!)
[问题]
下面的代码摘自某个程序,这个程序有一些类使用它们自己的内存管理方案。请尽可能的找出与内存有关的错误,并回答其注释中的附加题。
//为什么B的delete还有第二个参数?
//为什么D的delete却没有第二个参数?
//
classB{
public:
virtual~B();
voidoperatordelete(void*,size_t)throw();
voidoperatordelete[](void*,size_t)throw();
voidf(void*,size_t)throw();
};
classD:publicB{
public:
voidoperatordelete(void*)throw();
voidoperatordelete[](void*)throw();
};
voidf()
{
//下面各个语句中,到底哪一个delete被调用了?为什么?
//调用时的参数是什么?
//
D*pd1=newD;
deletepd1;
B*pb1=newD;
deletepb1;
D*pd2=newD[10];
delete[]pd2;
B*pb2=newD[10];
delete[]pb2;
//下面两个赋值语句合法吗?
//
Bb;
typedefvoid(B::*PMF)(void*,size_t);
PMFp1=&B::f;
PMFp2=&B::operatordelete;
}
classX{
public:
void*operatornew(size_ts,int)
throw(bad_alloc){
return::operatornew(s);
}
};
classSharedMemory{
public:
staticvoid*Allocate(size_ts){
returnOsSpecificSharedMemAllocation(s);
}
staticvoidDeallocate(void*p,inti){
OsSpecificSharedMemDeallocation(p,i);
}
};
classY{
public:
void*operatornew(size_ts,
SharedMemory&m)throw(bad_alloc){
returnm.Allocate(s);
}
voidoperatordelete(void*p,
SharedMemory&m,
inti)throw(){
m.Deallocate(p,i);
}
};
voidoperatordelete(void*p)throw(){
SharedMemory::Deallocate(p);
}
voidoperatordelete(void*p,
std::nothrow_t&)throw(){
SharedMemory::Deallocate(p);
}
[解答]
//为什么B的delete还有第二个参数?
//为什么D的delete却没有第二个参数?
//
classB{
public:
virtual~B();
voidoperatordelete(void*,size_t)throw();
voidoperatordelete[](void*,size_t)throw();
voidf(void*,size_t)throw();
};
classD:publicB{
public:
voidoperatordelete(void*)throw();
voidoperatordelete[](void*)throw();
};
附加题答案:这是出于个人喜好(preference)。两种都是正常的回收(译注:作者在这里用了“deallocation(去配)”一词,鄙人一律翻译为“回收”)函数,而不是placementdeletes(译注:关于placementdelete和placementnew,可以阅读StanleyLippman的《InsideTheC++ObjectModel(深度探索C++对象模型)》一书中的6.2节:PlacementOperatorNew的语意)。
另外,这两个类都提供了delete和delete[],却没有提供相对应的new和new[]。这是非常危险的。你可以试想,当更下层的派生类提供了它自己的new和new[]时会发生什么!
voidf()
{
//下面各个语句中,到底哪一个delete被调用了?为什么?
//调用时的参数是什么?
//
D*pd1=newD;
deletepd1;
这里调用了D::operatordelete(void*)。
B*pb1=newD;
deletepb1;
这里调用D::operatordelete(void*)。因为B的析构函数(destructor)被声明成virtual,所以D的析构函数(destructor)理所当然会被正常调用,但同时这也意味着,即使B::operatordelete()不声明成virtual,D::operatordelete()也必将被调用。
D*pd2=newD[10];
delete[]pd2;
这里D::operatordelete[](void*)被调用。
B*pb2=newD[10];
delete[]pb2;
这里的行为是不可预料的。C++语言要求,传递给delete的指针之静态类型必须与其之动态类型一样。关于这个问题的进一步谈论,可以参看ScottMeyers在《EffectiveC++》或者《MoreEffectiveC++》中有关“NeverTreatArraysPolymorphically”的部分(译注:这里指的应该是《MoreEffectiveC++》中的条款3:绝对不要以多态方式处理数组)。
//下面两个赋值语句合法吗?
//
Bb;
typedefvoid(B::*PMF)(void*,size_t);
PMFp1=&B::f;
PMFp2=&B::operatordelete;
}
第一个赋值语句没问题,但是第二个是不合法的,因为“voidoperatordelete(void*,size_t)throw()”并不是B的成员函数,即使它被写在上面使其看上去很像是。这里有一个小伎俩需要弄清楚,即new和delete总是静态的,即使它们不被显式的声明为static。总是把它们声明为static是个很好的习惯,这可以让所有阅读你代码的程序员们明白无误的认识到这一点。
classX{
public:
void*operatornew(size_ts,int)
throw(bad_alloc){
return::operatornew(s);
}
};
这会产生内存泄漏,因为没有相应的placementdelete存在。下面的代码也是一样:
classSharedMemory{
public:
staticvoid*Allocate(size_ts){
returnOsSpecificSharedMemAllocation(s);
}
staticvoidDeallocate(void*p,inti){
OsSpecificSharedMemDeallocation(p,i);
}
};
classY{
public:
void*operatornew(size_ts,
SharedMemory&m)throw(bad_alloc){
returnm.Allocate(s);
}
这里也产生内存泄漏,因为没有对应的delete。如果在用这个函数分配的内存里放置对象的构造过程中抛出了异常,内存就不会被正常释放。例如:
SharedMemoryshared;
...
new(shared)T;//ifT::T()throws,memoryisleaked
在这里,内存还无法被安全的删除,因为类中没有提供正常的operatordelete。这意味着,基类或者派生类的operatordelete(或者是那个全局的delete)将会试图处理这个回收操作(这几乎肯定会失败,除非你替换掉周围环境中所有类似的delete——这实在是一件繁琐和可怕的事情)。
voidoperatordelete(void*p,
SharedMemory&m,
inti)throw(){
m.Deallocate(p,i);
}
};
这里的这个delete毫无用处,因为它从不会被调用。
voidoperatordelete(void*p)throw(){
SharedMemory::Deallocate(p);
}
这是一个严重的错误,因为它将要删除那些被缺省的::operatornew分配出来的内存,而不是被SharedMemory::Allocate()分配的内存。你顶多只能盼来一个迅速的coredump。真是见鬼!
voidoperatordelete(void*p,
std::nothrow_t&)throw(){
SharedMemory::Deallocate(p);
}
这里也是一样,只是更为微妙。这里的delete只会在“new(nothrow)T”失败的时候才会被调用,因为T的构造函数(constructor)会带着一个异常来终止,并企图回收那些不是被SharedMemory::Allocate()分配的内存。这真是又邪恶又阴险!
好,如果你回答出了以上所有的问题,那你就肯定是走在成为内存管理机制专家的光明大道上了。
相关文章推荐
- Guru of the Week 条款15:类之间的关系(下篇)
- Guru of the Week 条款15:类之间的关系(下篇)
- Guru of the Week 条款09:内存管理(上篇)
- Guru of the Week 条款11:对象等同(Object Identity)问题
- Guru of the Week 条款19:自动转换
- Guru of the Week 条款04: 类的构造技巧
- Guru of the Week 条款13:面向对象程序设计
- Guru of the Week 条款16:具有最大可复用性的通用Containers
- Guru of the Week 条款18:Iterators(迭代子)
- Guru of the Week 条款19:自动转换
- Guru of the Week 条款05:覆写虚拟函数
- Guru of the Week 条款04: 类的构造技巧
- cheshire cat/pimpl idiom & Guru of the Week 条款28:“Fast Pimpl”技术
- Guru of the Week 条款06:正确使用const
- Guru of the Week 条款12:控制流(Control Flow)
- Guru of the Week 条款07:编译期的依赖性
- Guru of the Week 条款00:kingofark和Guru of the Week
- Guru of the Week 条款16:具有最大可复用性的通用Containers
- Guru of the Week 条款30附录:接口原则
- Guru of the Week 条款20:代码的复杂性(第一部分)