深入理解c++構(gòu)造函數(shù), 復(fù)制構(gòu)造函數(shù)和賦值函數(shù)重載(operator=)
注
以下代碼編譯及運行環(huán)境均為 Xcode 6.4, LLVM 6.1 with GNU++11 support, Mac OS X 10.10.2
調(diào)用時機
看例子
//
// 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 是我們創(chuàng)建一個變量的最主要的方法(構(gòu)造序列本文不討論), 其中1,2,3是變量定義, 4是賦值. 因此很明顯:
- 定義會調(diào)用構(gòu)造函數(shù), 賦值會調(diào)用賦值函數(shù)(operator=)
- 復(fù)制構(gòu)造函數(shù)是一種特殊的構(gòu)造函數(shù), 參數(shù)是一個變量實例而已
- 2和3等價, 3不會調(diào)用賦值函數(shù)(手誤) 2不會調(diào)用賦值函數(shù), 出現(xiàn)等號未必就是賦值
- 如果沒有重載以上函數(shù), 3和4效果會一樣, 但會少一次函數(shù)調(diào)用
const來搗亂
那么const又起到什么作用了呢?
繼續(xù)來看例子
//
// 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". 看代碼不難發(fā)現(xiàn)原因, creator函數(shù)返回的是Base類型, 在c++11里面, 這個稱為右值(rvalue), 但是我們的復(fù)制構(gòu)造函數(shù)和賦值函數(shù)的參數(shù)類型都是非const引用類型, 而右值是不允許做這種類型參數(shù)的, 所以就編譯出錯了. 解決方案有兩個:
- 使用const引用類型
- 使用右值類型
如下所示
Base(const Base ©)
{
std::cout<<"copy constructor"<<std::endl;
}
const Base &operator=(Base &©)
{
std::cout<<"operator="<<std::endl;
return *this;
}
其中, const引用類型是最通用的作法, 它可以兼容左值和右值, 也兼容古老的編譯器, 右值類型則是c++11引進的新特性(使用&&表明), 可以針對左值和右值選擇不同的實現(xiàn), 比如使用std::move替代operator=, 從而減少內(nèi)存的申請. 因此, 如果沒有特殊需要, 使用const引用類型作為復(fù)制構(gòu)造函數(shù)與賦值函數(shù)的參數(shù)類型.
至此, 構(gòu)造函數(shù)的坑基本說完了, 因為不牽扯到返回值和函數(shù)類型的問題, 但是賦值函數(shù)(operator=)還有更多的坑來理一理.
const繼續(xù)攪局
在一個類的成員函數(shù)中, const可以出現(xiàn)三個地方: 返回值, 參數(shù), 函數(shù).
const A& operator=(const A& a) const
因此一個函數(shù)可以有8個變種, 但是c++不允許參數(shù)類型相同,返回值類型不同的重載, 因此一個函數(shù)最多有4種實現(xiàn).
我們先考慮返回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;
}
輸出結(jié)果
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]
結(jié)果很明顯, 被賦值變量決定函數(shù), 賦值變量決定參數(shù), a=b 等價于 a.operator(b), 這里沒什么問題.
但是, 有一個很奇怪的地方, a=d 這一句, a是非const的, 調(diào)用了 const A& operator=(const A& a) [const d > a], 返回值是個const類型, 這怎么可以呢? 返回值的const是什么意思呢? 這是非常有迷惑性的. 這個問題的關(guān)鍵點在于:
a是這個函數(shù)的一部分, 并不是返回值的承接者. 因此 a=d 實際上是等價于 const A& ret = a.operator=(d), 也就是說, operator=的返回值類型和被賦值的變量是沒有任何關(guān)系的!
加入以下代碼
const A &m = (a = d); // 1
A &n = (a = d); // 2
2會編譯錯誤, 原因就在于把 const A& 綁定給 A&, 這肯定是錯誤的. 因此再重復(fù)一遍, operator=的返回值和被賦值變量沒有任何關(guān)系.
那么返回值有什么意義呢? 這就和iostream類似了, 是為了進行串聯(lián)賦值, 亦即 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]
可以得出如下結(jié)論:
- 1和3比較可以發(fā)現(xiàn), 賦值的順序是從右往左執(zhí)行的
- 返回值是const類型, 那么再被賦值就會調(diào)用const函數(shù)了
總結(jié)
- 復(fù)制構(gòu)造函數(shù)和賦值函數(shù)出現(xiàn)在兩種不同的場景里, 不是出現(xiàn)等號就會調(diào)用賦值函數(shù)
- 賦值函數(shù)的返回值和被賦值變量是完全獨立的
浙公網(wǎng)安備 33010602011771號