确定性析构在有指针的环境下的麻烦之处
确定性析构在有指针的环境下的麻烦之处
来自http://rednaxelafx.iteye.com/blog/372310
刚考完大软,心里还在郁闷,随便发点牢骚吧……
昨天同学考C++的时候还有一题,是找出程序错误的题。代码大概是这样:
昨天同学考C++的时候还有一题,是找出程序错误的题。代码大概是这样:
- class A {
- int i;
- };
- class B {
- A* p;
- public:
- B() { p = new A; }
- ~B() { delete p; }
- };
- void foo(B b) {
- // ...
- }
- int main() {
- B b;
- foo(b);
- }
你看出问题在什么地方了么?
============================================================================
其实应该挺明显的,但昨天被同学问到的时候我却没能一眼看出来。郁闷啊。
foo()的参数是将B复制传递过去的。于是foo()里的参数b在离开foo()的作用域时会被析构,里面的成员p指向的A实例就一起被析构了。然后在main()里的b在离开main()时析构就会尝试再次delete掉同一个A的实例,引发错误。
So what am I trying to say? 这普通的C++毫无疑问是有确定性析构的——局部变量在离开其作用域时就会被析构;显式使用delete时也可以完成析构。正是因为这样,无意中造成的浅拷贝会让引起一些意想不到的问题,就像上面的代码那样。
为了应对这些问题,我们才有了烦琐的idioms,例如:
1、delete之前先检查指针是否为null;(然而不小心用错delete与delete[]运算符的话还是很糟糕)
2、尽量不对复合类型对象直接使用值传递给参数——可以用引用传递,也可以传指针,以减少复制(但如果不想让参数的值被改变怎么办呢?我们有const修饰符,也有拷贝构造函数和operator =的重载……自己实现深拷贝吧)
3、为了避免内存泄漏,我们可以采用RAII……呃
……
具有RAII的可能性是好是坏我觉得还可以一议,像C#或者Java就没办法用RAII,有些人也会说很不爽什么的。但是内存管理到底是应该留给 程序员做还是应该让运行时解决掉,这毫无疑问是取决于程序所在的层次:高层的应用应该尽量避免涉及这些细节,一方面不容易出错,另一方面也减轻了程序员的 负担。不然我们就得记下一堆idioms,多到能出一整系列的几本书来描述的量,才能写出正确的程序了……
以上纯属吐槽 =v=
C++是门好语言(嗯
============================================================================
确实容易出问题,记下了。