在实现拷贝构造函数的复制运算符重载时,我们需要注意以下几种情况:
1.作为值的拷贝
所谓的值的拷贝就是说每次拷贝的是一个独立的对象,拷贝对象和被拷贝的对象之间相互独立,这种拷贝每次都要重新开辟空间,这样才能使得两者之间独立。但是对于重载操作符而言,我们需要先将需要拷贝的对象临时保存起来,然后释放复制运算符左边的内存空间,最后才将临时变量拷贝到等号左边的对象中。这样可以防止自复制而产生异常。
2.指针值的拷贝。
所谓指针值的拷贝就是拷贝对象和源对象共享相同的地址空间中的值。这种情况下需要引入一个计数器来记录指向内存空间的指针的数量。在进行拷贝和赋值的时候需要传递技术器的指针,以便各个对象共享一个计数器。这种情况下,我们使用析构函数进行释放内存空间时我们只能应该首先判断计数器的值是否为零,如果是的话就释放内存,否则仅仅计数器减一即可。在重载赋值运算符时一定要留意自赋值产生异常的情况,我们应该先将赋值运算符右侧的计数器的值加一后在进行赋值操作,在等号左边的计数器减一的时候应该判断计数器是否为零,如果计数器为零则应该释放对象占用的空间,左后在进行拷贝。
3.交换操作
标准库函数中的交换函数交换两个对象时总是会拷贝一个临时的对象进行交换,这就会造成空间和时间的浪费,因此一般自定义基于指针的交换将会提高交换的性能。案例:
class test
{
public:
friend void swap(test &t1,test &t2);
private:
string* p;
int i;
};
void swap(test& t1, test& t2)
{
using std::swap;
swap(t1.p,t2.p);
swap(t1.i,t2.i);
}
我们自定义的swap函数中调用标准库函数中的swap函数交换test类中的p指针和变量i,因此并不用创建一个新的临时变量的test类的对象来进行交换,采用这种方式可以具有更高的性能。
假如有一个Foo类,该类包含一个test类的对象,则在进行Foo类的对象的交换的时候将会调用test类的为参数的swap函数,而不是便准库函数。
class Foo
{
public:
friend void swap(Foo &f1,Foo &f2);
private:
test t;//test类的对象
};
//test类的对象版本的交换函数
void swap(test& t1, test& t2)
{
using std::swap;
swap(t1.p,t2.p);
swap(t1.i,t2.i);
cout << "swap for test" << endl;
}
//Foo类对象版本的交换函数
void swap(Foo &f1,Foo &f2)
{
using std::swap;
swap(f1.t, f2.t);//调用test类版本的swap函数
cout << "swap for Foo" << endl;
}
int main()
{
Foo f1, f2;
swap(f1,f2);
}
上述案例的输出如图:
显然调用了test类版本的swap函数,因为传入的参数是test类的对象,显然test版本的swap函数比库函数更匹配,这样交换的效率就会更加的高。
3.swap函数用于赋值运算符
用swap函数来实现运算符重载,被称为是拷贝并交换的技术。假设上述test的赋值运算符重载采用拷贝并交换 的技术重载赋值运算符。
test& test::operator=(test t)
{
swap(*this,t);
return *this;
}
这个版本的赋值运算符中,参数并不是一个引用,当我们进行传参的时候,函数的局部区域内将会对传入的test对象的参数进行一个拷贝,当函数调用结束后,这个局部变量指向的内存区域将会被释放。所以进行我们把传入的参数的拷贝的和this所指向的内存区域进行交换(指针交换),然后返回this。当this返回后,传入t指向的区域将会被释放(此时t指向的为this原来指向的地址)。
采用拷贝和交换的技术的赋值运算符是异常安全的,且能正确处理自赋值的问题。