天天看點

指派操作符的異常實作方式

指派操作符的異常實作方式

  在類的定義中,我們通常會重載指派操作符,來替代編譯器合成的版本,實作中會對每個類的成員變量進行具體的操作,比如下面的代碼:

1 class Sales_Item
 2 {
 3 public:
 4     Sales_Item& operator=(const Sales_Item & rhs);
 5 //other mebers and functions
 6 private:
 7     char *pIsbn;
 8     int units_sold;
 9     double revenue;
10 };
11 
12 Sales_Items& Sales_item::operator=(const Sales_Item & rhs)
13 {
14     if(this != &rhs)
15     {
16         if(pIsbn)
17             delete[] pIsbn;
18         pIsbn = new char[strlen(rhs.pIsbn)+1];
19         strcpy(pIsbn, rhs.pIsbn);
20         
21         units_sold = rhs.units_sold;
22         revenue = rhs.revenue
23     }
24     return *this;
25 }      

  需要先判斷是否為同一個對象,再用形參對象中的成員變量對目前對象成員變量進行指派。類的成員變量涉及到記憶體、資源的配置設定時,需要重載指派操作符,避免記憶體、資源的洩露和重複釋放等問題。在某處看到一個重載指派操作符定義如下:

1 T& T::operator = (const T& other)
2 {
3     if(this != &other)
4     {
5         this->~T();
6         new (this) T(other);
7     }
8     return *this;
9 }      

  可以看出這個operator=的定義上很簡單,首先調用T類的析構函數,然後使用placement new在原有的位址上,以other為形參,調用T類的拷貝構造函數。在這種慣用法中,拷貝指派運算符是通過拷貝構造函數實作的,它努力保證T的拷貝指派運算符和拷貝構造函數完成相同的功能,使程式員無需再兩個不同的地方編寫重複代碼。對于Sales_Item類,如果用這個operator=來代替其原有的實作,盡管不會出錯,但這種定義是一種非常不好的程式設計風格,它會帶來很多問題:

  • 它切割了對象。如果T是一個基類,并定義了虛析構函數,那麼"this->~T();new (this) T(other);" 将會出現問題,如果在一個派生類對象上調用這個函數,那麼這些代碼将銷毀派生類對象,并用一個T對象來代替,這幾乎會破壞後面所有試圖使用這個對象的代碼,考慮如下代碼:
    1 //在派生類的指派運算函數中通常會調用基類的指派運算函數
     2 Derived& Derived::operator=(const Derived& other)
     3 {
     4     if(this != &rhs)
     5     {
     6         Base::operator=(other);
     7         //...現在對派生類的成員進行指派...
     8     }
     9 
    10     return *this;
    11 }
    12 
    13 //本執行個體中,我們的代碼是
    14 class U : public T{/*...*/};
    15 U& U::operator=(const U& other)
    16 {
    17     if(this != &rhs)
    18     {
    19         T::operator=(other);
    20         //...對U的成員進行指派...
    21         //...但這已經不再是U的對象了,銷毀派生類對象,并在派生類記憶體建立基類對象
    22     }
    23 
    24     return *this;    //同樣的問題
    25 }      
    在U的operator=中,首先調用父類T的operator=,那麼會調用"this->T::~T();",并且随後再加上對T基類部分進行的placement new操作,對于派生類來說,這隻能保證T基類部分被替換。而更重要的是,在T類型的operator=中,虛函數指針會被指定為T類的版本,無法實作動态調用。如果要實作正确的調用,派生類U的operator=需要定義與父類T的operator=中同樣的實作:
    1 U& operator=(const U& rhs)
     2 {
     3     if(this != &rhs)
     4     {
     5         this->~U();
     6         new(this)U(rhs);
     7     }
     8 
     9     return *this;
    10 }      
  • 它不是異常安全的。在new語句中将調用T的拷貝構造函數。如果在這個構造函數抛出異常,那麼這個函數就不是異常安全的,因為它在最後隻銷毀了舊的對象,而沒有用其他對象來代替。
  • 它改變了正常對象的生存期。根本問題在于,這種慣用法改變了構造函數和析構函數的含義。構造過程和析構過程應該與對象生存期的開始/結束對應,而在通常含義下,此時正是擷取/釋放資源的時刻。構造過程和析構過程并不是用來改變對象的值得。
  • 它将破壞派生類。調用"this->T::~T();",這種方法隻是對派生類對象中"T"部分(T基類子對象)進行了替換。這種方法違背了C++的基本保證:基類子對象的生存期應該完全包含派生類對象的生存期——也就是說,通常基類子對象的構造要早于派生類對象,而析構要晚于派生類對象。特别是,如果派生類并不知道基類部分被修改了,那麼所有負責管理基類狀态的派生類都将失敗。

  測試代碼:

1 class T
  2 {
  3 public:
  4     T(const char *pname, int nage)
  5     {
  6         name = new char[strlen(pname)+1];
  7         strcpy_s(name, strlen(pname)+1, pname);
  8         age = nage;
  9     }
 10     T(const T &rhs)
 11     {
 12         name = new char[strlen(rhs.name)+1];
 13         strcpy_s(name, strlen(rhs.name)+1, rhs.name);
 14         age = rhs.age;
 15     }
 16     T& operator=(const T& rhs)
 17     {
 18         if(this != &rhs)
 19         {
 20             cout<<"T&operator="<<endl;
 21             this->~T();
 22             new(this)T(rhs);
 23         }
 24 
 25         return *this;
 26     }
 27     virtual ~T()
 28     {
 29         if(name!=NULL)
 30             delete[] name;
 31         cout<<"~T()"<<endl;
 32     }
 33     virtual void print(ostream& out)const
 34     {
 35         out<<"name is "<<name<<", age is "<<age;
 36     }
 37 private:
 38     char *name;
 39     int age;
 40 };
 41 
 42 ostream& operator<<(ostream& out, const T&t)
 43 {
 44     t.print(out);
 45     return out;
 46 }
 47 
 48 class U:public T
 49 {
 50 public:
 51     U(const char *pname, int nage, const char *prace, int nchampion):T(pname, nage)
 52     {
 53         race = new char[strlen(prace)+1];
 54         strcpy_s(race, strlen(prace)+1, prace);
 55         champion = nchampion;
 56     }
 57     U(const U &rhs):T(rhs)
 58     {
 59         race = new char[strlen(rhs.race)+1];
 60         strcpy_s(race, strlen(rhs.race)+1, rhs.race);
 61         champion = rhs.champion;
 62     }
 63     U& operator=(const U& rhs)
 64     {
 65         if(this != &rhs)
 66         {
 67         /*    T::operator=(rhs);
 68             race = new char[strlen(rhs.race)+1];
 69             strcpy_s(race, strlen(rhs.race)+1, rhs.race);
 70             champion = rhs.champion;
 71             */
 72             this->~U();
 73             new(this)U(rhs);
 74         }
 75 
 76         return *this;
 77     }
 78     virtual ~U()
 79     {
 80         if(race!=NULL)
 81             delete[] race;
 82         cout<<"~U()"<<endl;
 83     }
 84     virtual void print(ostream& out)const
 85     {
 86         T::print(out);
 87         out<<", race is "<<race<<", champion number is "<<champion<<".";
 88     }
 89 private:
 90     char *race;
 91     int champion;
 92 };
 93 int _tmain(int argc, _TCHAR* argv[])
 94 {
 95     cout<<sizeof(T)<<"  "<<sizeof(U)<<endl;
 96 
 97     U u("Moon", 21, "Night Elf", 0);
 98     U t("Grubby", 21, "Orc", 2);
 99 
100     u = t;
101     cout<<u<<endl;
102 
103     return 0;
104 }      

View Code

   在重載operator=運算符時,另一個值得關注的是,用const來修飾傳回值:

1 class T
 2 {
 3 public:
 4     T(int x=12):value(x){}
 5     const T& operator=(const T & rhs)
 6     {
 7         if(this != &rhs)
 8         {
 9             //implement
10         }
11 
12         return *this;
13     }
14     int getValue()
15     {
16         return value;
17     }
18     void setValue(int x)
19     {
20         value = x;
21     }
22 public:
23     int value;
24 };
25 
26 int main()
27 {
28     T t1;
29     T t2;
30     t2 = t1;
31     t2.setValue(21);
32 
33     return 0;
34 }      

   注意setValue函數改變了t2對象的value值,而line26指派後,t2仍然可以調用setValue函數,這說明“傳回const并不意味着類T本身為const,而隻意味着你不能使用傳回的引用來直接修改它指向的結構”。看看下面這段代碼:

1 int main()
2 {
3     T t1;
4     T t2;
5     (t2=t1).setValue(21);
6 
7     return 0;
8 }      

   這裡直接對t2=t1的傳回結果調用setValue,因為傳回的是const&類型,是以不能調用此setValue函數。

posted on 2014-11-30 11:43 Tourun 閱讀( ...) 評論( ...) 編輯 收藏

轉載于:https://www.cnblogs.com/Tour/p/4132919.html