這一節中主要講了物件和函式在使用和呼叫程序中一些注意事項,比較重要的是右值參考和最后的move和forward
物件的使用程序中呼叫了哪些方法?
對于以下這個測驗類,列出了十幾種不同的定義方式
class Test {
public:
Test(int a = 4, int b = 10) : ma(a), mb(b) {
cout << "Test()" << endl;
}
~Test() {
cout << "~Test()" << endl;
}
Test(const Test &src) {
ma = src.ma;
mb = src.mb;
cout << "Test(const Test&)" << endl;
}
Test &operator=(const Test &src) {
ma = src.ma;
mb = src.mb;
cout << "operator=(const Test&)" << endl;
return *this;
}
private:
int ma;
int mb;
};
實作結果如下:

有幾個比較值得注意的點:
- 物件賦值的情況會產生臨時物件,臨時物件在陳述句結束后會執行解構式
- 隱式生成的臨時物件,如
t2=60,編譯器會找物件中有無合適的構造方法生成物件 - 用指標保存臨時物件,臨時物件在陳述句結束后會被析構,安全的做法是通過參考指向物件
- (50,50)這種形式的是逗號運算式,賦值的時候只看最后的數字
函式呼叫程序中背后呼叫的方法
函式呼叫的程序中,實參傳遞到形參需要重新初始化,函式的形參物件需要初始化,這個程序中會呼叫物件的拷貝構造方法,
函式體內部回傳的物件也要現在main堆疊幀中拷貝構造一個臨時變數,才能在main作用域中訪問這個物件,
函式體執行完畢后需要先解構式體內構造的物件,然后再析構形參串列構造的物件

三條物件優化的規則
- 函式引數傳遞程序中,物件優先按參考傳遞,不要按值傳遞,
- 函式回傳物件的時候,應該優先回傳一個臨時物件,而不要回傳一個定義過的物件
- 接受回傳值是物件的函式呼叫的時候,優先按初始化的方式接收,不要按賦值的方式接收
上圖中的代碼最后被優化為以下代碼:
Test GetObject(Test &t){
int val=t.getData();
return Test(val);//定義臨時物件 2.Test()
}
int main(){
Test t1;//1.Test()
Test t2=GetObject(t1);//用臨時物件拷貝構造同型別的新物件,編譯器會優化此程序 少了臨時物件在main堆疊幀上的構造和析構
return 0;
}
//3.~Test()
//4.~Test()
優化完只剩下4步構造析構的程序
之前String代碼中的問題
class String {
friend std::ostream &operator<<(std::ostream &os, const String &src);
friend String operator+(const String &l, const String &r);
public:
String(const char *src = https://www.cnblogs.com/woden3702/p/nullptr) {
if (src == nullptr) {
_pstr = new char[1];
*_pstr ='\0';
} else {
_pstr = new char[strlen(src) + 1];
strcpy(_pstr, src);
}
std::cout<<"String(const char *src = https://www.cnblogs.com/woden3702/p/nullptr)"<<std::endl;
}
~String() {
delete[] _pstr;
_pstr = nullptr;
std::cout<<"~String()"<<std::endl;
}
String(const String &src) {
_pstr = new char[strlen(src._pstr) + 1];
strcpy(_pstr, src._pstr);
std::cout<<"String(const String &src)"<<std::endl;
}
bool operator>(const String &str) const {
return strcmp(_pstr, str._pstr) > 0;
}
bool operator<(const String &str) const {
return strcmp(_pstr, str._pstr) < 0;
}
bool operator==(const String &str) const {
return strcmp(_pstr, str._pstr) == 0;
}
int length() const {
return strlen(_pstr);
}
char &operator[](int index) {
return _pstr[index];
}
char *c_str() const {
return _pstr;
}
private:
char *_pstr;
};
String GetString(String& str){
const char* pstr=str.c_str();
String tmpStr(pstr);
return tmpStr;//這一步要在main堆疊幀中拷貝構造一個臨時變數,會重新劃分一塊記憶體
}
int main(){
String s1("assf");
String s2;
s2=GetString(s1);//呼叫賦值多載函式,會洗掉原有記憶體,重新劃分一塊記憶體
cout<<s2.c_str()<<endl;
//這一程序劃分了兩次記憶體,且都是無效的
}
在呼叫中出現了多次臨時物件,產生一個臨時物件就要在堆疊幀上拷貝賦值原來的記憶體,而使用一次就要洗掉,非常耗時

添加帶右值參考引數的拷貝構造和賦值函式
一個右值參考變數本身是一個左值,所以一個定義好的右值參考變數不能賦值給右值參考
帶右值參考引數的拷貝建構式和賦值多載函式會指向臨時物件開辟的記憶體,在整個程序中不會有無效的記憶體釋放和開辟,大幅提高了運行效率
實體代碼如下:
//帶右值參考的拷貝建構式
String(String &&src) noexcept {
std::cout<<"String(String &&)"<<std::endl;
_pstr=src._pstr;
src._pstr= nullptr;
}
//帶右值參考的賦值多載函式
String& operator=(String &&src) noexcept {
std::cout<<"String& operator=(String &&)"<<std::endl;
if(this==&src)
return *this;
delete[] _pstr;
_pstr=src._pstr;
src._pstr= nullptr;
return *this;
}
//如果使用右值參考版本的拷貝多載函式就不需要記憶體的開辟和釋放
輸出結果如下:

帶有左值參考的拷貝多載的物件中一般都有帶右值參考的拷貝多載的版本,
自定義的String類在vector中的應用

在push_back的程序中,如果傳入左值,匹配帶有左值引數的臨時物件,如果傳入臨時物件,會首先呼叫臨時物件的建構式,再呼叫帶右值引數的拷貝建構式,
為什么push_back會呼叫帶有右值參考的拷貝建構式?看下面 \(\Downarrow\)
move移動語意和forward型別完美轉發
move()是將左值轉化為右值
forward()是指型別的完美轉發,能夠識別左值和右值型別
如果在自己定義的vector類里定義支持呼叫右值參考的push_back方法,首先要push_back的引數是一個右值參考的型別
第一種寫法:使用函式多載,分別定義一個引數是左值參考的和一個引數是右值參考的函式
void push_back(T &val) {
if (full()) {
expend();
}
//*_last++ = val;
_alloctor.construct(_last, val);
_last++;
}
void push_back(T &&val) {
if (full()) {
expend();
}
_alloctor.construct(_last, std::move(val));
_last++;
}
在函式鐘呼叫了_alloctor.construct(),該函式傳遞了引數val,所以也需要函式多載接受右值參考和左值參考,
void construct(T *p, const T &val) {//負責物件構造
new(p) T(val);//定位new
}
void construct(T *p, const T &&val) {//負責物件構造
new(p) T(std::move(val));//定位new
}
第二種寫法:使用函式模板的型別推演和參考折疊
首先說明參考折疊是什么意思,如果函式模板推演出的型別是Ty&& + &&(+后面的&&是引數中,屬于必帶的符號),參考折疊后的型別就就是Ty&&,是右值參考;如果函式模板推演出的型別是Ty& + &&,參考折疊后的型別就就是Ty&,是左值參考,使用forward可以識別Ty的左值或者右值的型別,
template<typename Ty>
void push_back(Ty &&val) {//Ty識別傳入引數是左值還是右值,然后進行參考折疊
if (full()) {
expend();
}
_alloctor.construct(_last, std::forward<Ty>(val));//將val轉換為Ty識別到的型別,避免使用函式多載
_last++;
}
template<typename Ty>
void construct(T *p, Ty &&val) {//Ty識別傳入引數是左值還是右值,然后進行參考折疊
new(p) T(std::forward<Ty>(val));//將val轉換為Ty識別到的型別,避免使用函式多載
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/479186.html
標籤:C++
上一篇:如何在程式集中創建一個dumpRegistersPROC?
下一篇:Go編譯程序
