C++内存管理学习笔记(6) (二)

2014-11-24 00:56:17 · 作者: · 浏览: 4
ing a)是按值传递的,所以,我们相当于执行了这样的代码:函数申请一个临时对象a,然后将a=test2;函数执行完毕后,由于生存周期的缘故,对象a被析构函数删除,这里要注意!从输出结果来看,显示的是“第二个范例。”,看上去是正确的,但是分析程序发现这里有漏洞,程序执行的是默认的复制构造函数,类中使用str指针申请内存的,默认的函数不能动态申请空间,只是将临时对象的str指针指向了test2,即a.str = test2.str,所以这块不能够正确执我们的复制目的。因为此时test2也被破坏了!

这是就需要我们自己重载构造函数了,即定义自己的复制构造函数,

1: String::String(const String& a) 2: { 3: len=a.len; 4: str=new char(len+1); 5: strcpy(str,a.str); 6: }
这里执行的是深拷贝。这个函数的功能是这样的:假设对象A中的str指针指向地址2000,内容为“I am a C++ Boy!”。我们执行代码String B=A时,我们先开辟出一块内存,假设为3000。我们用strcpy函数将地址2000的内容拷贝到地址3000中,再将对象B的str指针指向地址3000。这样,就互不干扰了。

c)还有一段代码

1: String String3; 2: String3=test4;
问题和上面的相似,大家应该猜得到,它同样是执行了浅拷贝,出了同样的毛病。比如,执行了这段代码后,析构函数开始执行。由于这些变量是后进先出的,所以最后的String3变量先被删除:这个字符串将被删除:String:第四个范例。执行正常。最后,删除到test4的时候,问题来了:程序崩溃。原因我不用赘述了。

那怎么修改这个赋值操作呢,当然是自己定义重载啦,

版本一,

1: String& String::operator =(const String &a) 2: { 3: if(this == &a) 4: return *this; 5: delete []str; 6: str = NULL; 7: len=a.len; 8: str = new char[len+1]; 9: strcpy(str,a.str); 10: 11: return *this; 12: } //重载operator=
版本二,

1: String& String::operator =(const String& a) 2: { 3: if(this != &a) 4: { 5: String strTemp(a); 6: 7: len = a.len; 8: char* pTemp = strTemp.str; 9: strTemp.str = str; 10: str = pTemp; 11: } 12: return *this; 13: }


这个重载函数实现时要考虑填补很多的陷阱!限于篇幅,大概说下,返回值须是String类型的引用,形参为const 修饰的Sting引用类型,程序中要首先判断是否为a=a的情形,最后要返回对*this的引用,至于为什么需要利用一个临时strTemp,是考虑到内存不足是会出现new异常的,将改变Srting对象的有效状态,违背C++异常安全性原则,当然这里可以先new,然后在删除原来对象的指针方式来替换使用临时对象赋值。

我们根据上面的要求重新修改程序后,执行程序,结果显示为,从图的右侧可以到,这次执行正确了。


3.2 如何对付内存泄漏
写出那些不会导致任何内存泄漏的代码。很明显,当你的代码中到处充满了new 操作、delete操作和指针运算的话,你将会在某个地方搞晕了头,导致内存泄漏,指针引用错误,以及诸如此类的问题。这和你如何小心地对待内存分配工作其实完全没有关系:代码的复杂性最终总是会超过你能够付出的时间和努力。于是随后产生了一些成功的技巧,它们依赖于将内存分配(allocations)与重新分配(deallocation)工作隐藏在易于管理的类型之后。标准容器(standard containers)是一个优秀的例子。它们不是通过你而是自己为元素管理内存,从而避免了产生糟糕的结果。

如果不考虑vector和Sting使用来写下面的程序,你大脑很会费劲的…..

1: #include 2: #include 3: #include 4: #include 5: 6: using namespace std; 7: 8: int main() // small program messing around with strings 9: { 10: cout<<"enter some whitespace-seperated words:"< v; 12: string s; 13: while (cin>>s) 14: v.push_back(s); 15: sort(v.begin(),v.end()); 16: string cat; 17: typedef vector::const_iterator Iter; 18: for (Iter p = v.begin(); p!=v.end(); ++p) 19: { 20: cat += *p+"+"; 21: std::cout<

运行结果:这个程序利用标准库的string和vector来申请和管理内存,方便简单,若是设想使用new和delete来重新写程序,会头疼的。


注 意,程序中没有出现显式的内存管理,宏,溢出检查,显式的长度限制,以及指针。通过使用函数对象和标准算法(standard algorithm),我可以避免使用指针——例如使用迭代子(iterator),不过对于一个这么小的程序来说有点小题大作了。

  这些技巧并不完美,要系统化地使用它们也并不总是那么容易。但是,应用它们产生了惊人的差异,而且通过减少显式的内存分配与重新分配的次数,你甚至可以使余下的例子更加容易被跟踪。  如果你的程序还没有包含将显式内存管理减少到最小限度的库,那么要让你程序完成和正确运行的话,最快的途径也许就是先建立一个这样的库。模板和标准库实现了容器、资源句柄等等

  如果你实在不能将内存分配/重新分配的操作隐藏到你需要的对象中时,你可以使用资源句柄(resource handle),以将内存泄漏的可能性降至最低。

这里有个例子:需要通过一个函数,在空闲内存中建立一个对象并返回它。这时候可能忘记释放这个对象。毕竟,我们不能说,仅仅关注当这个指针要被释放的时候,谁将负责去做。使用资源句柄,这里用了标准库中的auto_ptr,使需要为之负责的地方变得明确了。

1: #include 2: #include 3: using namespace std; 4: 5: struct S { 6: S() { cout << "make an S"< g() 18: { 19