日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

C++ 深复制与浅复制 RVO问题

發布時間:2024/2/28 c/c++ 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++ 深复制与浅复制 RVO问题 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

內容主要是深淺復制、復制構造函數以及賦值運算符的問題。

先從一段簡單的代碼開始:

#include <iostream> #include <string.h> using namespace std;class Student {private:char *name;int number;public:Student(char *na, int n);~Student();setnumber(int n);setname(char *na);void Print(); };Student::Student(char *na, int n) {name = new char[strlen(na)+1];strcpy(name, na);number = n;cout << "Student:" << (int)name << " Number:" << number <<endl; }Student::~Student() {cout << "~Student:" << (int)name << " Number:" << number <<endl;delete name; }Student::setnumber(int n) {number = n; }Student::setname(char *na) {strcpy(name, na); }void Student::Print() {cout << "student:" << name << " " << "Number:" << number <<endl; }int main() {Student s1((char*)"Tom", 123);Student s2(s1);//復制對象cout <<endl;s2.setname((char*)"Bob");s2.setnumber(456);s1.Print();s2.Print();cout <<endl;return 0; }

細細分析一下上述代碼中的 "TOM" 為什么會變成 "Bob"??,可以發現當用s1去初始化s2時,s1的name的地址也復制給了s2。那么自然而然,當修改s2的name時,s1的name也跟著變化。這對Student對象的使用造成了麻煩,而且還會帶來別的問題。

這里先給出解決辦法:

Student::Student(const Student &st) {int len = strlen(st.name);name = new char[len + 1];strcpy(name, st.name); }

至于為什么這么做,我們需要接著看下面的內容。

?

上面的程序只是在拋磚引玉,接下來的內容是《C++ Primer Plus》中給出的程序代碼。通過下面的代碼,能讓我們發現很多潛在的問題。

#include <iostream> #include <cstring> #include <string.h> using namespace std;class StringBad { private:char *str;int len;static int num_strings; public:StringBad(const char *s);StringBad();~StringBad();friend std::ostream & operator << (std::ostream & os, const StringBad & st); };int StringBad::num_strings = 0;StringBad::StringBad(const char *s) {len = strlen(s);str = new char[len + 1];strcpy(str, s);num_strings++;cout << num_strings << ": \"" << str << "\" object created\n"; }StringBad::StringBad() {len = 4;str = new char[4];strcpy(str, "C++");num_strings++;cout << num_strings << ": \"" << str << "\" default object created\n"; }StringBad::~StringBad() {cout << "\"" << str << "\" object deleted, ";--num_strings;cout << num_strings << " left\n";delete []str; }ostream & operator << (ostream & os, const StringBad &st) {os << st.str;return os; }void callme1(StringBad &); void callme2(StringBad);int main() {cout << "String an inner block.\n";StringBad headline1("asd");StringBad headline2("qwe");StringBad sports("zxc123");cout << "headline1: " << headline1 <<endl;cout << "headline2: " << headline2 <<endl;cout << "sports: " << sports <<endl;callme1(headline1);cout << "headline1: " << headline1 <<endl;callme2(headline2);cout << "headline2: " << headline2 <<endl;cout << "Initialize one object to another:\n";StringBad sailor = sports;cout << "sailor: " << sailor <<endl;cout << "Assign one object to another:\n";StringBad knot;knot = headline1;cout << "knot: " << knot <<endl;cout << "Exiting the block.\n";return 0; }void callme1(StringBad &rsb) {cout << "String passed by reference:\n";cout << " \"" << rsb << "\"\n"; }void callme2(StringBad sb) {cout << "String passed by value:\n";cout << " \"" << sb << "\"\n"; }

這里還要說明的一點是:我用CodeBlocks運行上述代碼,沒有達到書本分析的預期結果。具體情況是臨時對象調用了析構函數釋放相應內存后,還可以獲取內存中的值。查閱資料后發現原因是:RVO(return value optimization),被C++進行值返回的優化。有的軟件可以關閉RVO,因為我不太清楚CodeBlocks如何關閉它。所以我將代碼放到了Linux系統下運行。將RVO優化關閉,可以對g++增加選項-fno-elide-constructors,重新編繹之后,執行結果如下:

關閉RVO后,一下子就發現問題了。

這里有一個問題是因為當時忘寫knot = headline1;這一條賦值語句 導致"Exiting the block.下面出現的是 "C++" object deleted, 2 left而不是"asd" object deleted, 2 left

程序第一個問題是輸出中出現的各種非標準字符隨系統而異,另一個問題是對象計數為負。程序開始時還是正常的,但逐漸變得異常,最終導致了災難性結果。可以看出到headline1傳遞給callme1()函數,并在調用后重新顯示headline1。

這一塊代碼運行都是正常的:

String passed by reference:"asd" headline1: asd

但隨后程序將headline2傳遞給了callme2,出現了一個嚴重的問題:

String passed by value:"qwe" "qwe" object deleted, 2 left headline2:

首先,將headline2作為函數參數來傳遞給函數,導致析構函數被調用。其次,雖然按值傳遞可以防止原始參數被修改,但實際上函數已使原字符串無法識別,導致顯示一些非標準字符(顯示的內容取決于內存中所包含的內容)。

在為每一個創建的對象自動調用析構函數時,情況更糟糕:

上面的計數異常是一條線索,因為每個對象被構造和析構一次,因此調用構造函數的次數應當與析構函數的調用次數相同。對象計數遞減的次數比遞增的次數多2,這表明使用了不將num_string遞增的構造函數創建了兩個對象。此時使用的不是默認的構造函數,也不是參數為const char *的構造函數,而是復制構造函數

?

復制構造函數用于將一個對象復制到新創建的對象中。也就是說它用于初始化過程中,而不是常規的賦值過程中。

類的復制構造函數原型通常如下:

Class_name(const Class_name &);

它接受一個指向類對象的常量引用作為參數。例如:

class StringBad { private:char *str;int len;static int num_strings; public:StringBad(const char *s);StringBad();StringBad(const StringBad &); //復制構造函數~StringBad();friend std::ostream & operator << (std::ostream & os, const StringBad & st); };

新建一個對象并將其初始化為同類現有的對象時,復制構造函數都將被調用。假設motto是一個StringBad對象,則下面4種聲明將調用復制構造函數:

StringBad ditto(motto); StringBad metoo = mott; StringBad also = StringBad (motto); StringBad * pStringBad = new StringBad(motto);

其中中間的2種聲明可能會使用復制構造函數直接創造metoo和also,也可能使用復制構造函數生成一個臨時對象,然后將臨時對象的內容賦值給metoo和also,這取決于具體實現。每當程序生成了對象副本時,編譯器都將使用復制構造函數。具體地說,當函數按值傳遞對象或函數返回對象時,都將使用復制構造函數。

調用復制構造函數總結:

(1)用對象去初始化另一個對象。

(2)函數的參數是類對象(值傳遞)。

(3)返回值是類對象。

?

上述代碼存在的問題

(1)默認的復制構造函數不說明其行為(逐個賦值非靜態成員,只是復制成員的值,成員復制也稱為淺復制),因為它不指出創建過程,也不增加計數器num_strings的值。但析構函數更新了計數,并且在任何對象過期時都將被調用,而不管對象是如何被創建的。這就導致了程序無法準確地記錄對象的個數。

(2)就像開頭的小程序一樣,程序復制的不是字符串,而是一個指向字符串的指針。也就是說,將sailor初始化為sports后,會有兩個指向同一個字符串的指針。當析構函數被調用時,str指針所指向的內存將被釋放。此時,另一個對象再用str指針去訪問這塊區域,必然導致不確定的、可能有害的后果。

(3)最后一個問題是,試圖釋放內存兩次可能導致程序異常終止(不同系統提供的信息不同)。

?

定義一個顯式復制構造函數以解決問題

解決類設計中的這種問題的方法是進行深度復制(deep copy)。也就是說,復制構造函數應當復制字符串并將副本的地址賦值給str成員,而不是僅僅復制字符串地址。

StringBad::StringBad(const StringBad & st) {num_strings++;len = st.len;str = new char[len + 1];strcpy(str, st.str);cout << num_strings << ": \"" << str << "\" object created\n"; }

這時程序可以打印出headline2的值、字符串不亂碼以及計數恢復正常。但是最后一條的字符串不應該為空,這又是一個問題。

?

StringBad的其他問題:賦值運算符

(1)賦值運算符的功能以及何時使用它

StringBad headline1(“asd”); ... StringBad knot; knot = headline1; 初始化對象時,不一定會使用賦值運算符 StringBad metoo = knot; ??//使用復制構造函數

與復制構造函數相似,賦值運算符的隱式實現也對成員進行逐個復制。如果成員本身就是類對象,則程序將使用為這個類定義的賦值運算符來復制該成員,但靜態數據成員不受影響。

?

(2)賦值的問題

上述程序中將headline1賦值給了knot:

knot = headline1;

為knot調用析構函數時,knot.str所指向的區域將被釋放。那么就會出現和隱式復制構造函數一樣的結果:數據受損。以及試圖刪除已經刪除的數據導致的結果是不確定的,因此可能會改變內存中的內容,導致程序異常終止。

?

(3)解決賦值問題

對于由與默認賦值運算符不合適而導致的問題,解決辦法是提供賦值運算符(進行深度復制)定義。

StringBad & StringBad::operator = (const StringBad & st) {if (this == &st)return *this;delete []str;len = st.len;str = new char[len + 1];strcpy(str, st.str);return *this; }

代碼首先檢查自我復制,這是通過查看賦值運算符右邊的地址是否與接受對象的地址相同來完成的。如果相同,程序返回*this,然后結束。如果地址不同,函數將釋放str指向的內存,這是因為稍后將把一個新字符串的地址賦給str。賦值操作并不創建新的對象,因此不需要調整靜態數據成員num_strings的值。通過修改程序,上述代碼存在的3個問題都得到了解決。最終的運行結果如下:

總結:深復制開辟新的空間,而淺復制沒有。

總結

以上是生活随笔為你收集整理的C++ 深复制与浅复制 RVO问题的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。