在C++运算符重载中最麻烦的可能就是"="的重载了,尤其是类的层次很复杂时要涉及到很多成员的初始化问题。
一. operator=的行为
首先我们要明确什么时候会调用operator= :如果对象还没有被创建,使用= 时会调用拷贝构造函数,否则使用赋值operator= 。
下面是operator=的一个简单应用
using namespace std;
class T
{
public :
T( int k)
{
m = k;
cout << " The constructor. " << endl;
}
T( const T & s)
{
m = s.m;
cout << " The copy-constructor. " << endl;
}
T & operator = ( const T & s)
{
if ( & s != this )
{
m = s.m;
cout << " The operator=. " << endl;
}
return * this ;
}
void Print()
{
cout << " m = " << m << endl;
}
private :
int m;
};
int main()
{
T f( 2 );
f.Print();
T d(f);
d.Print();
T k = f;
k.Print();
T h( 4 );
h.Print();
h = d;
h.Print();
}
这里可以清楚的看到什么时候是调用的拷贝构造函数,而什么时候是调用的operator=。还要注意的是
1. 编译器强制operator=为成员函数,所以不能出现非成员函数的=重载。
2. 当准备给两个相同类型的对象赋值时,应该首先检查一个自赋值(self-assignment),这个问题在类
比较简单时还不明显,但应该养成习惯。
3. 同构造函数和拷贝构造函数一样,如果没有定义operator=,那么编译器会自动生成一个,它模仿拷
贝构造函数的行为。不过在类层次比较复杂时,往往不能满足我们的需求。
二. 有层次结构的类中赋值运算符
当一个类的对象做为另一个类的成员时,我们设计operator=就得考虑更多的初始化问题。
#include < iostream >
using namespace std;
class Dog{
private :
string nm;
public :
Dog( const string & name):nm(name)
{
cout << " Creating Dog: " <<* this << endl;
}
Dog( const Dog * dp, const string & msg)
:nm(dp -> nm + msg)
{
cout << " Copied dog " <<* this << " from " <<* dp << endl;
}
~ Dog()
{
cout << " Deleting Dog: " <<* this << endl;
}
void rename( const string & newName)
{
nm = newName;
cout << " Dog renamed to: " <<* this << endl;
}
friend ostream & operator << (ostream & os, const Dog & d)
{
return os << " [ " << d.nm << " ] " ;
}
};
class DogHouse
{
private :
Dog * p;
string houseName;
public :
DogHouse(Dog * dog, const string & house)
:p(dog),houseName(house) {}
DogHouse( const DogHouse & dh)
:p( new Dog(dh.p, " copy-constructed " )),
DogHouse & operator = ( const DogHouse & dh)
{
if ( & dh != this )
{
p = new Dog(dh.p, " assigned " );
houseName = dh.houseName + " assigned " ;
}
return * this ;
}
void renameHouse( const string & newName)
{
houseName = newName;
}
Dog * getDog() const { return p; }
~ DogHouse() { delete p; }
friend ostream & operator << (ostream & os, const DogHouse & dh)
{
return os << " [ " << dh.houseName << " ] contains " <<* dh.p;
}
};
int main()
{
DogHouse fidos( new Dog( " Fido " ), " FidoHouse " );
cout << fidos << endl;
DogHouse fidos2 = fidos;
cout << fidos2 << endl;
fidos2.getDog() -> rename( " Spot " );
fidos2.renameHouse( " SpotHouse " );
cout << fidos2 << endl;
fidos = fidos2;
cout << fidos << endl;
fidos.getDog() -> rename( " Max " );
fidos2.renameHouse( " MaxHouse " );
}
大家可以根据它的打印输出来分析下程序是如何运行的。
三. 引用计数
在内置对象中我们使用=时都是重新在内存中开个区域,然后将内容拷贝过去。但是在用户自定义的类型时,这样做的代价太高了,尤其是对象需要大量的内存或过高的初始化。
解决这个问题的通常方法称为引用计数(reference counting)。可以使一块存储单元具有智能,它知道有多少对象指向它。拷贝构造函数或赋值运算意味着把另外的指针指向现在的存储单元并增加引用计数。消除意味着减小引用计数,如果引用计数为0则销毁这个对象。
但是这同时又有另外一个问题:如果有多个对象指向同一块区域,但有一个对象需要改变某个属性时,其它对象也会同时被改变。经常会使用另外一个技术称为写拷贝(copy-on-write)。在向这块存储单元写之前,应该检查引用计数是否为1,如果大于1,则写之前拷贝这块存储单元,将要改变的对象单独指向一个存储单元。
Java和.NET中的自动垃圾回收功能的原理就是引用计数,不过他们更复杂,还有内存紧缩功能。
下面的程序演示了引用计数和写拷贝
#include < iostream >
using namespace std;
class Dog
{
private :
string nm;
int refcount;
Dog( const string & name)
:nm(name),refcount( 1 )
{
cout << " Creating Dog: " <<* this << endl;
}
Dog & operator = ( const Dog & rv);
public :
static Dog * make( const string & name)
{
return new Dog(name);
}
Dog( const Dog & d)
:nm(d.nm + " copy " ),refcount( 1 )
{
cout << " Dog copy-constructor: " <<* this << endl;
}
~ Dog()
{
cout << " Deleting Dog: " <<* this << endl;
}
void attach()
{
++ refcount;
cout << " Attaching Dog: " <<* this << endl;
}
void detach()
{
cout << " Detaching Dog: " <<* this << endl;
if ( -- refcount == 0 )
delete this ;
}
Dog * unalias()
{
cout << " Unaliasing Dog: " <<* this << endl;
if (refcount == 1 )
return this ;
-- refcount;
return new Dog( * this );
}
void rename( const string & newName)
{
nm = newName;
cout << " Dog renamed to: " <<* this << endl;
}
friend ostream & operator << (ostream & os, const Dog & d)
{
return os << " [ " << d.nm << " ], rc= " << d.refcount;
}
};
class DogHouse
{
private :
Dog * p;
string houseName;
public :
DogHouse(Dog * dog, const string & house)
:p(dog),houseName(house)
{
cout << " Created DogHouse: " <<* this << endl;
}
DogHouse( const DogHouse & dh)
:p(dh.p),houseName( " copy-constructed " + dh.houseName)
{
p -> attach();
cout << " DogHouse copy-constructor: " <<* this << endl;
}
DogHouse & operator = ( const DogHouse & dh)
{
if ( & dh != this )
{
houseName = dh.houseName + " assigned " ;
p -> detach();
p = dh.p;
p -> attach();
}
cout << " DogHouse operator= : " <<* this << endl;
return * this ;
}
~ DogHouse()
{
cout << " DogHouse destructor: " <<* this << endl;
p -> detach();
}
void renameHouse( const string & newName)
{
houseName = newName;
}
void unalias()
{
p = p -> unalias();
}
void renameDog( const string & newName)
{
unalias();
p -> rename(newName);
}
Dog * getDog()
{
unalias();
return p;
}
friend ostream & operator << (ostream & os, const DogHouse & dh)
{
return os << " [ " << dh.houseName << " ] contains " <<* dh.p;
}
};
int main()
{
DogHouse fidos(Dog::make( " Fido " ), " FidoHouse " ),
spots(Dog::make( " Spot " ), " SpotHouse " );
cout << " Entering copy-construction " << endl;
DogHouse bobs(fidos);
cout << " After copy-constructing bobs " << endl;
cout << " fidos: " << fidos << endl;
cout << " spots: " << spots << endl;
cout << " bobs " << bobs << endl;
cout << " Entering spots = fidos " << endl;
spots = fidos;
cout << " After spots = fidos " << endl;
cout << " spots: " << spots << endl;
cout << " Entering self-assignment " << endl;
bobs = bobs;
cout << " After self-assignment " << endl;
cout << " bobs: " << bobs << endl;
cout << " Entering rename("Bob") " << endl;
bobs.getDog() -> rename( " Bob " );
cout << " After rename("Bob") " << endl;
}