注
以下代碼編譯及運行環境均為 Xcode 6.4, LLVM 6.1 with GNU++11 support, Mac OS X 10.10.2
調用時機
看例子
// // main.cpp // test // // Created by dabao on 15/9/30. // Copyright (c) 2015年 Peking University. All rights reserved. // #include <iostream> class Base { public: Base() { std::cout<<"constructor"<<std::endl; } Base(Base ©) { std::cout<<"copy constructor"<<std::endl; } const Base &operator=(Base ©) { std::cout<<"operator="<<std::endl; return *this; } }; int main(int argc, const char * argv[]) { Base a; // 1 Base b = a; // 2 Base c(a); // 3 Base d; // 4 d = a; return 0; }
輸出
constructor copy constructor copy constructor constructor operator=
1,2,3,4 是我們創建一個變量的最主要的方法(構造序列本文不討論), 其中1,2,3是變量定義, 4是賦值. 因此很明顯:
- 定義會調用構造函數, 賦值會調用賦值函數(operator=)
- 復制構造函數是一種特殊的構造函數, 參數是一個變量實例而已
- 2和3等價, 3不會調用賦值函數(手誤) 2不會調用賦值函數, 出現等號未必就是賦值
- 如果沒有重載以上函數, 3和4效果會一樣, 但會少一次函數調用
const來搗亂
那么const又起到什么作用了呢?
繼續來看例子
// // main.cpp // test // // Created by dabao on 15/9/30. // Copyright (c) 2015年 Peking University. All rights reserved. // #include <iostream> class Base { public: Base() { std::cout<<"constructor"<<std::endl; } Base(Base ©) { std::cout<<"copy constructor"<<std::endl; } const Base &operator=(Base ©) { std::cout<<"operator="<<std::endl; return *this; } }; Base creator() { Base ret; return ret; } int main(int argc, const char * argv[]) { Base a = creator(); // 1 Base b; b = creator(); // 2 return 0; }
上述代碼都會編譯出錯, 原因是 "No matching constructor". 看代碼不難發現原因, creator函數返回的是Base類型, 在c++11里面, 這個稱為右值(rvalue), 但是我們的復制構造函數和賦值函數的參數類型都是非const引用類型, 而右值是不允許做這種類型參數的, 所以就編譯出錯了. 解決方案有兩個:
- 使用const引用類型
- 使用右值類型
如下所示
Base(const Base ©) { std::cout<<"copy constructor"<<std::endl; } const Base &operator=(Base &©) { std::cout<<"operator="<<std::endl; return *this; }
其中, const引用類型是最通用的作法, 它可以兼容左值和右值, 也兼容古老的編譯器, 右值類型則是c++11引進的新特性(使用&&表明), 可以針對左值和右值選擇不同的實現, 比如使用std::move替代operator=, 從而減少內存的申請. 因此, 如果沒有特殊需要, 使用const引用類型作為復制構造函數與賦值函數的參數類型.
至此, 構造函數的坑基本說完了, 因為不牽扯到返回值和函數類型的問題, 但是賦值函數(operator=)還有更多的坑來理一理.
const繼續攪局
在一個類的成員函數中, const可以出現三個地方: 返回值, 參數, 函數.
const A& operator=(const A& a) const
因此一個函數可以有8個變種, 但是c++不允許參數類型相同,返回值類型不同的重載, 因此一個函數最多有4種實現.
我們先考慮返回const類型的情況
// // main.cpp // test // // Created by dabao on 15/9/30. // Copyright (c) 2015年 Peking University. All rights reserved. // #include <iostream> class A { public: const A& operator=(const A& a) const { std::cout<<"const A& operator=(const A& a) const ["<<a.x<<" > "<<x<<"]"<<std::endl; return *this; } const A& operator=(const A& a) { std::cout<<"const A& operator=(const A& a) ["<<a.x<<" > "<<x<<"]"<<std::endl; return *this; } const A& operator=(A& a) const { std::cout<<"const A& operator=(A& a) const ["<<a.x<<" > "<<x<<"]"<<std::endl; return *this; } const A& operator=(A& a) { std::cout<<"const A& operator=(A& a) ["<<a.x<<" > "<<x<<"]"<<std::endl; return *this; } std::string x; A() : x(""){} A(std::string x_) : x(x_) {} }; int main(int argc, const char * argv[]) { A a("a"), b("b"); const A c("const c"),d("const d"); c = d; c = b; a = d; a = b; return 0; }
輸出結果
const A& operator=(const A& a) const [const d > const c] const A& operator=(A& a) const [b > const c] const A& operator=(const A& a) [const d > a] const A& operator=(A& a) [b > a]
結果很明顯, 被賦值變量決定函數, 賦值變量決定參數, a=b 等價於 a.operator(b), 這里沒什么問題.
但是, 有一個很奇怪的地方, a=d 這一句, a是非const的, 調用了 const A& operator=(const A& a) [const d > a], 返回值是個const類型, 這怎么可以呢? 返回值的const是什么意思呢? 這是非常有迷惑性的. 這個問題的關鍵點在於:
a是這個函數的一部分, 並不是返回值的承接者. 因此 a=d 實際上是等價於 const A& ret = a.operator=(d), 也就是說, operator=的返回值類型和被賦值的變量是沒有任何關系的!
加入以下代碼
const A &m = (a = d); // 1 A &n = (a = d); // 2
2會編譯錯誤, 原因就在於把 const A& 綁定給 A&, 這肯定是錯誤的. 因此再重復一遍, operator=的返回值和被賦值變量沒有任何關系.
那么返回值有什么意義呢? 這就和iostream類似了, 是為了進行串聯賦值, 亦即 a=b=c
來看最后的例子
// // main.cpp // test // // Created by dabao on 15/9/30. // Copyright (c) 2015年 Peking University. All rights reserved. // #include <iostream> class A { public: const A& operator=(const A& a) const { std::cout<<"const A& operator=(const A& a) const ["<<a.x<<" > "<<x<<"]"<<std::endl; return *this; } const A& operator=(const A& a) { std::cout<<"const A& operator=(const A& a) ["<<a.x<<" > "<<x<<"]"<<std::endl; return *this; } const A& operator=(A& a) const { std::cout<<"const A& operator=(A& a) const ["<<a.x<<" > "<<x<<"]"<<std::endl; return *this; } const A& operator=(A& a) { std::cout<<"const A& operator=(A& a) ["<<a.x<<" > "<<x<<"]"<<std::endl; return *this; } std::string x; A() : x(""){} A(std::string x_) : x(x_) {} }; int main(int argc, const char * argv[]) { A a("a"), b("b"); const A c("const c"),d("const d"); (a = b) = c; // 1 (a = c) = b; // 2 a = b = c; // 3 return 0; }
輸出
const A& operator=(A& a) [b > a] const A& operator=(const A& a) const [const c > a] const A& operator=(const A& a) [const c > a] const A& operator=(A& a) const [b > a] const A& operator=(const A& a) [const c > b] const A& operator=(const A& a) [b > a]
可以得出如下結論:
- 1和3比較可以發現, 賦值的順序是從右往左執行的
- 返回值是const類型, 那么再被賦值就會調用const函數了
總結
- 復制構造函數和賦值函數出現在兩種不同的場景里, 不是出現等號就會調用賦值函數
- 賦值函數的返回值和被賦值變量是完全獨立的