在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;
}