Boolan(博覽網(wǎng))——C++面向?qū)ο蟾呒?jí)編程(下)(第五周)

1. 對(duì)象模型(Object Model)

1.1 關(guān)于vptr(虛指針)和vtbl(虛表)

只要有虛函數(shù)(無論多少個(gè))宽气,都會(huì)多出一個(gè)虛指針(指向虛表)意狠。
某個(gè)虛函數(shù)在C中的實(shí)現(xiàn)形式如圖最下端紅字所示局义。其中n代表所調(diào)用的虛函數(shù)排在虛表中的位置(0,1,...)

右下角:為了能存放不同類型惊来,必須為指向父類的指針蝗罗。
最下端:編譯器虛機(jī)制(動(dòng)態(tài)綁定)實(shí)現(xiàn)的3個(gè)條件:

  1. 通過指針
  2. 指針向上轉(zhuǎn)型(指向子類對(duì)象)
  3. 調(diào)用的是虛函數(shù)

1.2 關(guān)于this

在C++中夹纫,所以的成員函數(shù)都有一個(gè)隱藏的第一參數(shù)—— this pointer
所以myDoc在調(diào)用從父類繼承而來的OnFileOpen函數(shù)時(shí)栖忠,隱藏的第一參數(shù)為&myDoc崔挖,即指向自己的 this 指針贸街,因此OnFileOpen函數(shù)中的Serialize函數(shù)在調(diào)用時(shí)形式為:this->Serialize。因其滿足動(dòng)態(tài)綁定(Dynamic Binding)的3個(gè)條件狸相,編譯為如下形式:(*(this->vptr)[n])(this)薛匪,調(diào)用的是子類CMyDoc重新定義的虛函數(shù)Serialize()。然后再繼續(xù)執(zhí)行父類OnFileOpen()函數(shù)余下的語句脓鹃。整個(gè)過程的執(zhí)行順序如圖中箭頭所示逸尖。

1.3 關(guān)于Dynamic Binding(動(dòng)態(tài)綁定)

右圖展示的是匯編語言里左圖中語句的具體實(shí)現(xiàn)過程,調(diào)用a.vfunc1()時(shí)瘸右, call 的是固定的地址(004011a9)娇跟,為靜態(tài)綁定。

此圖展示的是匯編語言里動(dòng)態(tài)綁定的實(shí)現(xiàn)過程太颤,等價(jià)于右圖中的語句苞俘。

2. 談?wù)?const

上圖為const成員函數(shù)(const的作用:說明其不會(huì)修改數(shù)據(jù)成員),任何不會(huì)修改數(shù)據(jù)成員的函數(shù)都應(yīng)該聲明為const 類型龄章。如果在編寫const成員函數(shù)時(shí)吃谣,不慎修改了數(shù)據(jù)成員,或者調(diào)用了其它非const成員函數(shù)做裙,編譯器將指出錯(cuò)誤岗憋,這無疑會(huì)提高程序的健壯性。

表中為基本規(guī)則锚贱,表上為特殊規(guī)則仔戈。
圖右框中,const 是函數(shù)簽名的一部分拧廊,所以這兩個(gè)操作符重載可以并存杂穷。
字符串 string 的設(shè)計(jì)運(yùn)用了 reference counting(引用計(jì)數(shù)設(shè)計(jì)模式):同樣的內(nèi)容,多個(gè)對(duì)象共享卦绣;如果某個(gè)對(duì)象要改內(nèi)容耐量,就單獨(dú)拷貝一份讓它改。

3. 控制內(nèi)存分配[1]

某些應(yīng)用程序?qū)?nèi)存分配有特殊的需求滤港,因此我們無法將標(biāo)準(zhǔn)內(nèi)存管理機(jī)制直接應(yīng)用于這些程序廊蜒。它們常常需要自定義內(nèi)存分配的細(xì)節(jié),比如使用關(guān)鍵字 new 將對(duì)象放置在特定的內(nèi)存空間中溅漾。為了實(shí)現(xiàn)這一目的山叮,應(yīng)用程序需要重載 new 運(yùn)算符和 delete 運(yùn)算符以控制內(nèi)存分配的過程。

3.1 重載 new 和 delete[1]

盡管我們說能夠“重載 new 和 delete”添履,但是實(shí)際上重載這兩個(gè)運(yùn)算符與重載其他運(yùn)算符的過程大不相同屁倔。要想真正掌握重載 new 和 delete 的方法,首先要對(duì) new 表達(dá)式和 delete 表達(dá)式的工作機(jī)理有更多了解暮胧。

當(dāng)我們使用一條 new 表達(dá)式時(shí):

string *sp = new string ("a value"); //分配并初始化一個(gè) string 對(duì)象
string *arr = new string[10];        //分配10個(gè)默認(rèn)初始化的 string 對(duì)象

實(shí)際執(zhí)行了三步操作:

  1. new 表達(dá)式調(diào)用了一個(gè)名為 operator new(或者 operator new[])的標(biāo)準(zhǔn)庫函數(shù)锐借。該函數(shù)分配一塊足夠大的问麸、原始的、未命名的內(nèi)存空間以便存儲(chǔ)特定類型的對(duì)象(或者對(duì)象的數(shù)組)钞翔。
  2. 編譯器運(yùn)行相應(yīng)的構(gòu)造函數(shù)以構(gòu)造這些對(duì)象严卖,并為其傳入初始值。
  3. 對(duì)象被分配了空間并構(gòu)造完成布轿,返回一個(gè)指向該對(duì)象的指針哮笆。

當(dāng)我們使用一條 delete 表達(dá)式刪除一個(gè)動(dòng)態(tài)分配的對(duì)象時(shí):

delete sp;     //銷毀*sp,然后釋放 sp 指向的內(nèi)存空間
delete [] arr; //銷毀數(shù)組中的元素汰扭,然后釋放對(duì)應(yīng)的內(nèi)存空間

實(shí)際執(zhí)行了兩步操作:

  1. 對(duì) sp 所指的對(duì)象或者 arr 所指的數(shù)組中的元素執(zhí)行對(duì)應(yīng)的析構(gòu)函數(shù)稠肘。
  2. 編譯器調(diào)用名為 operator delete(或者 operator delete[])的標(biāo)準(zhǔn)庫函數(shù)釋放內(nèi)存空間。

拓展:關(guān)于 delete 和 delete[] 的區(qū)別萝毛,可以參見這篇博文:delete 和 delete []的真正區(qū)別

如果應(yīng)用程序希望控制內(nèi)存分配的過程启具,則它們需要定義自己的 operator new 函數(shù)和 operator delete 函數(shù)。即使在標(biāo)準(zhǔn)庫中已經(jīng)存在這兩個(gè)函數(shù)的定義珊泳,我們?nèi)耘f可以定義自己的版本鲁冯。編譯器不會(huì)對(duì)這種重復(fù)的定義提出異議,相反色查,編譯器將使用我們自定義的版本替換標(biāo)準(zhǔn)庫定義的版本薯演。

注意:當(dāng)自定義了全局的 operator new 函數(shù)和 operator delete 函數(shù)后,我們就擔(dān)負(fù)起了控制內(nèi)存分配的職責(zé)秧了。這兩個(gè)函數(shù)必須是正確的:因?yàn)樗鼈兪浅绦蛘麄€(gè)處理過程中至關(guān)重要的一部分跨扮。

應(yīng)用程序可以在全局作用域中定義 operator new 函數(shù)和 operator delete 函數(shù),也可以將它們定義為成員函數(shù)验毡。當(dāng)編譯器發(fā)現(xiàn)一條 new 表達(dá)式或 delete 表達(dá)式后衡创,將在程序中查找可供調(diào)用的 operator 函數(shù)。如果被分配(釋放)的對(duì)象是類類型晶通,則編譯器首先在類及其基類的作用域中查找璃氢。此時(shí)如果該類含有 operator new 成員或者 operator delete 成員,則相應(yīng)的表達(dá)式將調(diào)用這些成員狮辽。否則一也,編譯器在全局作用域查找匹配的函數(shù)。此時(shí)如果編譯器找到了用戶自定義的版本喉脖,則使用該版本執(zhí)行 new 或者 delete 椰苟;如果沒找到,則使用標(biāo)準(zhǔn)庫定義的版本树叽。

我們可以使用作用域運(yùn)算符令 new 表達(dá)式或 delete 表達(dá)式忽略定義在類中的函數(shù)舆蝴,直接執(zhí)行全局作用域中的版本。例如, ::new 只在全局作用域中查找匹配的 operator new 函數(shù)洁仗, ::delete 與之類似层皱。


operator new 接口和 operator delete 接口

標(biāo)準(zhǔn)庫定義了 operator new 函數(shù)和 operator delete 函數(shù)的8個(gè)重載版本。其中前4個(gè)版本可能拋出 bad_alloc 異常京痢,后4個(gè)版本則不會(huì)拋出異常:

// 這些版本可能拋出異常
void* operator new(size_t);              //分配一個(gè)對(duì)象
void* operator new[](size_t);            //分配一個(gè)數(shù)組
void* operator delete(void*) noexcept;   //釋放一個(gè)對(duì)象
void* operator delete[](void*) noexcept; //釋放一個(gè)數(shù)組

//這些版本承諾不會(huì)拋出異常
void* operator new(size_t, nothrow_t&) noexcept;
void* operator new[](size_t, nothrow_t&) noexcept;
void* operator delete(void*, nothrow_t&) noexcept;
void* operator delete[](void*, nothrow_t&) noexcept;

類型 nothrow_t 是定義在 new 頭文件中的一個(gè) struct,在這個(gè)類型中不包含任何成員篷店。new 頭文件還定義了一個(gè)名為 nothrow 的 const 對(duì)象祭椰,用戶可以通過這個(gè)對(duì)象請(qǐng)求 new 的非拋出版本。與析構(gòu)函數(shù)類似疲陕, operator delete 也不允許拋出異常方淤。當(dāng)我們重載這些運(yùn)算符時(shí),必須使用 noexcept 異常說明符指定其不拋出異常蹄殃。

應(yīng)用程序可以自定義上面函數(shù)版本中的任意一個(gè)携茂,前提是自定義的版本必須位于全局作用域或者類作用域中。當(dāng)我們將上述運(yùn)算符函數(shù)定義成類的成員時(shí)诅岩,它們是隱式靜態(tài)的讳苦。我們無須顯式地聲明 static,因?yàn)?operator new 用在對(duì)象構(gòu)造之前而 operator delete 用在對(duì)象銷毀之后吩谦,所以這兩個(gè)成員必須是靜態(tài)的鸳谜,而且它們不能操縱類的任何數(shù)據(jù)成員。

對(duì)于 operator new 函數(shù)或者 operator new[] 函數(shù)來說式廷,它的返回類型必須是 void*咐扭,第一個(gè)形參的類型必須是 size_t 且該形參不能含有默認(rèn)實(shí)參。當(dāng)我們?yōu)橐粋€(gè)對(duì)象分配空間時(shí)使用 operator new滑废;為一個(gè)數(shù)組分配空間時(shí)使用 operator new[] 蝗肪。當(dāng)編譯器調(diào)用 operator new 時(shí),把存儲(chǔ)指定類型對(duì)象所需的字節(jié)數(shù)傳給 size_t 形參蠕趁;當(dāng)調(diào)用 operator new[] 時(shí)薛闪,傳入函數(shù)的則是存儲(chǔ)數(shù)組中所以元素所需的空間。

下面這個(gè)函數(shù)無論如何都不能被用戶重載:
void* operator new(size_t, void*); //不允許重新定義這個(gè)版本
這種形式只供標(biāo)準(zhǔn)庫使用俺陋,不能被用戶重新定義逛绵。

對(duì)于 operator delete 函數(shù)或者 operator delet[] 函數(shù)來說,它們的返回類型必須是 void, 第一個(gè)形參的類型必須是 void* 倔韭。執(zhí)行一條 delete 表達(dá)式將調(diào)用相應(yīng)的 operator 函數(shù)术浪,并用指向待釋放內(nèi)存的指針來初始化 void* 形參。

當(dāng)我們將 operator delete 或 operator delete[] 定義成類的成員時(shí)寿酌,該函數(shù)可以包含另外一個(gè)類型為 size_t 的形參胰苏。此時(shí),該形參的初始值是第一個(gè)形參所指對(duì)象的字節(jié)數(shù)醇疼。 size_t 形參可用于刪除繼承體系中的對(duì)象硕并。如果基類中有一個(gè)虛析構(gòu)函數(shù)法焰,則傳遞給 operator delete 的字節(jié)數(shù)將因待刪除指針?biāo)笇?duì)象的動(dòng)態(tài)類型不同而有所區(qū)別。而且倔毙,實(shí)際運(yùn)行的 operator delete 函數(shù)版本也因?qū)ο蟮膭?dòng)態(tài)類型決定埃仪。

術(shù)語: new 表達(dá)式與 operator new 函數(shù)

標(biāo)準(zhǔn)庫函數(shù) operator new 和 operator delete 的名字容易讓人誤解。和其他 operator 函數(shù)不同(比如 operator=)陕赃,這兩個(gè)函數(shù)并沒有重載 new 表達(dá)式或 delete 表達(dá)式卵蛉。實(shí)際上,我們根本無法自定義 new 表達(dá)式或 delete 表達(dá)式的行為么库。

一條 new 表達(dá)式的執(zhí)行過程總是先調(diào)用 operator new 函數(shù)以獲取內(nèi)存空間傻丝,然后在得到的內(nèi)存空間中構(gòu)造對(duì)象。與之相反诉儒,一條 delete 表達(dá)式的執(zhí)行過程總是先銷毀對(duì)象葡缰,然后調(diào)用 operator delete 函數(shù)釋放對(duì)象所占的空間。

提供的新的 operator new 函數(shù)和 operator delete 函數(shù)的目的在于改變內(nèi)存分配的方式忱反,但是不管怎樣泛释,我們都不能改變 new 運(yùn)算符和 delete 運(yùn)算符的基本含義。


malloc 函數(shù)與 free 函數(shù)

當(dāng)我們定義了自己的全局 operator new 和 operator delete 后温算,這兩個(gè)函數(shù)必須以某種方式執(zhí)行分配內(nèi)存與釋放內(nèi)存的操作胁澳。也許我們的初衷僅僅是使用一個(gè)特殊定制的內(nèi)存分配器,但是這兩個(gè)函數(shù)還應(yīng)該同時(shí)滿足某些測試的目的米者,即檢驗(yàn)其分配內(nèi)存的方式是否與常規(guī)方式類似韭畸。

為此,我們可以使用名為 mallocfree 的函數(shù)蔓搞,C++ 從 C 語言中繼承了這些函數(shù)胰丁,并將其定義在 cstdlib 頭文件中。

malloc 函數(shù)接受一個(gè)表示待分配字節(jié)數(shù)的 size_t喂分,返回指向分配空間的指針或者返回 0 以表示分配失敗锦庸。 free 函數(shù)接受一個(gè) void*,它是 malloc 返回的指針的副本蒲祈, free 將相關(guān)內(nèi)存返回給系統(tǒng)甘萧。調(diào)用 free(0) 沒有任何意義。

如下所示是編寫 operator new 和 operator delete 的一種簡單方式梆掸,其他版本與之類似:

void* operator new(size_t size){
    if (void* mem = malloc(size))
        return mem;
    else
        throw bad_alloc();
}
void operator delete(void* mem) noexcept { free(mem); }

3.2 定位 new 表達(dá)式[1]

盡管 operator new 函數(shù)和 operator delete 函數(shù)一般用于 new 表達(dá)式扬卷,然而它們畢竟是標(biāo)準(zhǔn)庫的兩個(gè)普通函數(shù),因此普通的代碼也可以直接調(diào)用它們酸钦。

在C++的早期版本中怪得, allocator 類還不是標(biāo)準(zhǔn)庫的一部分。應(yīng)用程序如果想把內(nèi)存分配與初始化分離開來的話,需要調(diào)用 operator new 和 operator delete徒恋。這兩個(gè)函數(shù)的行為與 allocator 的 allocate 成員和 deallocate 成員非常類似蚕断,它們負(fù)責(zé)分配或釋放內(nèi)存空間,但是不會(huì)構(gòu)造或銷毀對(duì)象入挣。

與 allocator 不同的是亿乳,對(duì)于 operator new 分配的內(nèi)存空間來說我們無法使用 construct 函數(shù)構(gòu)造對(duì)象。相反径筏,我們應(yīng)該使用 new 的 定位 new ( placement new )形式構(gòu)造對(duì)象葛假。如我們所知, new 的這種形式為分配函數(shù)提供了額外的信息匠璧。我們可以使用定位 new 傳遞一個(gè)地址桐款,此時(shí)定位 new 的形式如下所示:

new (place_address) type
new (place_address) type (initializers)
new (place_address) type [size]
new (place_address) type [size] { braced initializer list }

其中 place_address 必須是一個(gè)指針咸这,同時(shí)在 initializers 中提供一個(gè)(可能為空的)以逗號(hào)分隔的初始值列表夷恍,該初始值列表將用于構(gòu)造新分配的對(duì)象。

當(dāng)僅通過一個(gè)地址值調(diào)用時(shí)媳维,定位 new 使用 operator new(size_t, void*) “分配”它的內(nèi)存酿雪。這是一個(gè)我們無法自定義的 operator new 版本。該函數(shù)不分配任何內(nèi)存侄刽,它只是簡單地返回指針實(shí)參指黎;然后由 new 表達(dá)式負(fù)責(zé)在指定的地址初始化對(duì)象以完成整個(gè)工作。事實(shí)上州丹,定位 new 允許我們?cè)谝粋€(gè)特定的醋安、預(yù)先分配的內(nèi)存地址上構(gòu)造對(duì)象。

注意:當(dāng)只傳入一個(gè)指針類型的實(shí)參時(shí)墓毒,定位 new 表達(dá)式構(gòu)造對(duì)象但是不分配內(nèi)存吓揪。

盡管在很多時(shí)候使用定位 new 與 allocator 的 construct 成員非常相似,但在它們之間也有一個(gè)重要的區(qū)別所计。我們傳給 cpnstruct 的指針必須指向同一個(gè) allocator 對(duì)象分配的空間柠辞,但是傳給定位 new 的指針無須指向 operator new 分配的內(nèi)存。實(shí)際上主胧,傳給定位 new 表達(dá)式的指針甚至不需要指向動(dòng)態(tài)內(nèi)存叭首。

顯式的析構(gòu)函數(shù)調(diào)用

就像定位 new 與使用 allocate 類似一樣,對(duì)析構(gòu)函數(shù)的顯式調(diào)用也與使用 destroy 很類似踪栋。我們既可以通過對(duì)象調(diào)用析構(gòu)函數(shù)焙格,也可以通過對(duì)象的指針或引用調(diào)用析構(gòu)函數(shù),這與調(diào)用其他成員函數(shù)沒什么區(qū)別:

string *sp = new string("a value"); //分配并初始化一個(gè) string 對(duì)象
sp->~string();

在這里我們自己調(diào)用了一個(gè)析構(gòu)函數(shù)夷都。箭頭運(yùn)算符解引用指針 sp 以獲得 sp 所指的對(duì)象间螟,然后我們調(diào)用析構(gòu)函數(shù),析構(gòu)函數(shù)的形式是波浪線(~)加上類型的名字。

和調(diào)用 destrory 類似厢破,調(diào)用析構(gòu)函數(shù)可以清除給定的對(duì)象但是不會(huì)釋放該對(duì)象所在的空間荣瑟。如果需要的話,我們可以重新使用該空間摩泪。

調(diào)用析構(gòu)函數(shù)會(huì)銷毀對(duì)象笆焰,但是不會(huì)釋放內(nèi)存。


  1. StanleyB.Lippman等. "C++ Primer中文版." (2013). ? ? ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末见坑,一起剝皮案震驚了整個(gè)濱河市嚷掠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌荞驴,老刑警劉巖不皆,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異熊楼,居然都是意外死亡霹娄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門鲫骗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來犬耻,“玉大人,你說我怎么就攤上這事执泰≌泶牛” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵术吝,是天一觀的道長计济。 經(jīng)常有香客問我,道長排苍,這世上最難降的妖魔是什么沦寂? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮纪岁,結(jié)果婚禮上凑队,老公的妹妹穿的比我還像新娘。我一直安慰自己幔翰,他們只是感情好漩氨,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著遗增,像睡著了一般叫惊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上做修,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天霍狰,我揣著相機(jī)與錄音抡草,去河邊找鬼。 笑死蔗坯,一個(gè)胖子當(dāng)著我的面吹牛康震,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播宾濒,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼腿短,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了绘梦?” 一聲冷哼從身側(cè)響起橘忱,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎卸奉,沒想到半個(gè)月后钝诚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡榄棵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年凝颇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秉继。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡祈噪,死狀恐怖泽铛,靈堂內(nèi)的尸體忽然破棺而出尚辑,到底是詐尸還是另有隱情,我是刑警寧澤盔腔,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布杠茬,位于F島的核電站,受9級(jí)特大地震影響弛随,放射性物質(zhì)發(fā)生泄漏瓢喉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一舀透、第九天 我趴在偏房一處隱蔽的房頂上張望栓票。 院中可真熱鬧,春花似錦愕够、人聲如沸走贪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坠狡。三九已至,卻和暖如春遂跟,著一層夾襖步出監(jiān)牢的瞬間逃沿,已是汗流浹背婴渡。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凯亮,地道東北人边臼。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像假消,于是被迫代替她去往敵國和親硼瓣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容