复制构造函数的用法及指针悬挂问题的产生和解决(上)——提出问题
=========================何为复制构造函数的“复制”=========================
复制构造函数利用下面这行语句来复制一个对象:
A (A &a)
从上面这句话可以看出,所有的复制构造函数均只有一个参数,及对同一个类的对象的引用。这句话的意思其实就是,如果我们在程序中写上下面这样一句话:
A b=(a)
或者是:
A b(a)
就表示b其实是对象a的一个拷贝。说穿了,b就是a,a就是b。
=========================复制构造函数的“运行机制”=========================
比如说我们有一个类A,定义如下:
class A { public: A(int i,int j){n=i;m=j;} //构造函数带两个参数,并将参数的值分别赋给两个私有成员 A(A &t); //我们自己定义一个默认构造函数 void print(){cout<<n<<m;}//输出两个成员的值 private: int n; //数据成员n int m; //数据成员m };
在上面这个类的定义中我们定义了一个默认的构造函数(虽然默认构造函数一般是通过编译器自动定义的,但是这里我们模拟一下它的工作过程)。默认构造函数的工作方法应该如下面所示:
A(A &t) { n=t.n;m=t.m; }
在这里我们模拟了一个默认复制构造函数是如何运行的。它通过别名t访问一个对象,并将该对象的成员赋给新对象的成员。这样就完成了复制工作。这样一来,我们如果再程序中定义了:
A a(2,4);
这个对象。这就表示,利用构造函数,对象a的数据成员a.n=2;a.m=4,如果我们利用下面这句话调用一个默认的复制构造函数:
A b(a)
那么它就会调用复制构造函数,即上面我们模拟的复制构造函数中所定义的“n=t.n;m=t.m”。这时对象b的数据成员b.n=2;b.m=4,这与对象a中的数据成员的值应该是一模一样的。
===好了,复制构造函数介绍完了,下面我们来看看为什么利用复制构造函数有时候会产生迷途指针的问题~~~===
迷途指针产生的原因:“浅层复制构造函数”
一般地,编译器提供的默认复制构造函数的功能只是把传递进来的对象的每一个成员变量的值复制给了新对象的成员变量。但是,如果这个老对象是一个指针,那么新对象也是一个指针,且它指向的内存地址和老对象是一样的!这样就会产生2个显而易见的问题:
- 我们可以随意地对一个指针所指向的内存空间进行赋值操作。那么另外一个指针所指向的内存空间由于和前面那个指针式一模一样的,那么它就不可避免地被修改;
- 如果我们在程序中无意将老对象所指向的内存地址释放掉了,那么新对象的指针自然就变成了一个迷途指针。举一个形象的例子来说就是:(这个例子来自我上一篇博文《删除一个指针之后可不要就跟着悲剧了啊……》后面 mrfangzheng的留言):如果B对象持用一个指针指向对象A, 现有一个B对象的拷贝C,那么它也持有一个指针p2指向A. 若某时, B释放了对象A, 但C是无法知晓的, C认为它持有的指针指向的A有效, 之后若C再调用A的方法就报错。
我们用一个程序来演示上面这两种错误:
#include <iostream> using namespace std; class A { public: A(){x=new int;*x=5;} //创建一个对象的同时将成员指针指向的变量保存到 新空间中 ~A(){delete x;x = NULL;}//析构对象的同时删除成员指针指向的内存空间并将指针赋为空 A(A &a) { cout << "复制构造函数执行...\n" <<endl; x = a.x; //将旧对象的成员指针x指向的空间处的数据赋给新对象的成员指针x } void print(){cout<<*x<<endl;} void set(int i){*x=i;} private: int *x; }; int main() { A *a = new A(); //利用指针在堆中创建一个对象 cout<<"a:"; a->print(); cout<<endl; A b=(*a); //这里初始化的对象为指针a所指向的堆中的对象 //调用复制构造函数之后,将对象b变成对象a的一个拷贝,那么对象a和对象b所输出的值应该是一样的 cout<<"b:"; b.print(); cout<<endl; //利用对象a中的成员函数set()将指针x指向的内存区域中的值改成32 a->set(32); cout<<"a:"; a->print(); cout<<"b:"; b.print(); cout<<endl; //利用对象b中的成员函数set()将指针x指向的内存区域中的值改成99 b.set(99); cout<<"a:"; a->print(); cout<<"b:"; b.print(); cout<<endl; delete a; return 0; }
来看一看这个程序的输出:
上面红色框就代表了用a.set(32)来改变a中指针x所指向的内存空间的值,可以看出b的指针x所指向的内存空间的值也跟着变成了32。绿色框代表了用b.set(99)来改变b中指针x所指向的内存空间的值,可以看出a的指针x所指向的内存空间的值也跟着变成了99。而在程序崩溃对话框(白底黑字那个)中指明了程序的第52行引发了一个错误,大意就是那个内存区域是非法的(如上图中蓝色框所示)。出现这个错误的原因其实就是由迷途指针造成的:当main函数结束(右大括号)并析构对象b的时候,由于指针成员x所指向的内存区域已经在第52行被释放了,这样一来,对象b中的指针x就变成了迷途指针了,我们再次释放这块内存空间的时候肯定就会导致程序的崩溃。
未完待续......
(那我们该怎么解决这个问题呢,请看下集~~~)