• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            ivy-jie

            progress ...

            C++博客 首頁 新隨筆 聯(lián)系 聚合 管理
              9 Posts :: 41 Stories :: 6 Comments :: 0 Trackbacks
            虛繼承與虛基類的本質(zhì)
                虛繼承和虛基類的定義是非常的簡單的,同時(shí)也是非常容易判斷一個(gè)繼承是否是虛繼承
            的,雖然這兩個(gè)概念的定義是非常的簡單明確的,但是在C++語言中虛繼承作為一個(gè)比較生
            僻的但是又是絕對(duì)必要的組成部份而存在著,并且其行為和模型均表現(xiàn)出和一般的繼承體系
            之間的巨大的差異(包括訪問性能上的差異),現(xiàn)在我們就來徹底的從語言、模型、性能和
            應(yīng)用等多個(gè)方面對(duì)虛繼承和虛基類進(jìn)行研究。
                首先還是先給出虛繼承和虛基類的定義。
                虛繼承:在繼承定義中包含了virtual關(guān)鍵字的繼承關(guān)系;
                虛基類:在虛繼承體系中的通過virtual繼承而來的基類,需要注意的是:
                        struct CSubClass : public virtual CBase {}; 其中CBase稱之為CSubClass
                        的虛基類,而不是說CBase就是個(gè)虛基類,因?yàn)镃Base還可以不不是虛繼承體系
                        中的基類。
                有了上面的定義后,就可以開始虛繼承和虛基類的本質(zhì)研究了,下面按照語法、語義、
            模型、性能和應(yīng)用五個(gè)方面進(jìn)行全面的描述。

                1. 語法
                   語法有語言的本身的定義所決定,總體上來說非常的簡單,如下:
                       struct CSubClass : public virtual CBaseClass {};
                   其中可以采用public、protected、private三種不同的繼承關(guān)鍵字進(jìn)行修飾,只要
                   確保包含virtual就可以了,這樣一來就形成了虛繼承體系,同時(shí)CBaseClass就成為
                   了CSubClass的虛基類了。
                   其實(shí)并沒有那么的簡單,如果出現(xiàn)虛繼承體系的進(jìn)一步繼承會(huì)出現(xiàn)什么樣的狀況呢?
                   如下所示:
                        /*
                         * 帶有數(shù)據(jù)成員的基類
                         */
                        struct CBaseClass1
                        {
                            CBaseClass1( size_t i ) : m_val( i ) {}
                        
                            size_t m_val;
                        };
                        /*
                         * 虛擬繼承體系
                         */
                        struct CSubClassV1 : public virtual CBaseClass1
                        {
                            CSubClassV1( size_t i ) : CBaseClass1( i ) {}
                        };           
                        struct CSubClassV2 : public virtual CBaseClass1
                        {
                            CSubClassV2( size_t i ) : CBaseClass1( i ) {}
                        };           
                        struct CDiamondClass1 : public CSubClassV1, public CSubClassV2
                        {
                            CDiamondClass1( size_t i ) : CBaseClass1( i ), CSubClassV1( i ), CSubClassV2( i ) {}
                        };           
                        struct CDiamondSubClass1 : public CDiamondClass1
                        {
                            CDiamondSubClass1( size_t i ) : CBaseClass1( i ), CDiamondClass1( i ) {}
                        };
                   注意上面代碼中的CDiamondClass1和CDiamondSubClass1兩個(gè)類的構(gòu)造函數(shù)初始化列
                   表中的內(nèi)容??梢园l(fā)現(xiàn)其中均包含了虛基類CBaseClass1的初始化工作,如果沒有這
                   個(gè)初始化語句就會(huì)導(dǎo)致編譯時(shí)錯(cuò)誤,為什么會(huì)這樣呢?一般情況下不是只要在
                   CSubClassV1和CSubClassV2中包含初始化就可以了么?要解釋該問題必須要明白虛
                   繼承的語義特征,所以參看下面語義部分的解釋。
                  
                2. 語義
                      在C++中虛函數(shù)必須要通過一種間接的運(yùn)行時(shí)(而不是編譯時(shí))機(jī)制才能夠激活(
                  調(diào)用)的函數(shù),而虛繼承也是必須在運(yùn)行時(shí)才能夠進(jìn)行定位訪問的一種體制。存在,
                  但間接。其中關(guān)鍵就在于存在、間接和共享這三種特征。
                   對(duì)于虛函數(shù)而言,這三個(gè)特征是很好理解的,間接性表明了他必須在運(yùn)行時(shí)根據(jù)實(shí)際
                   的對(duì)象來完成函數(shù)尋址,共享性表象在基類會(huì)共享被子類重載后的虛函數(shù),其實(shí)指向
                   相同的函數(shù)入口。
                   對(duì)于虛繼承而言,這三個(gè)特征如何理解呢?存在即表示虛繼承體系和虛基類確實(shí)存在,
                   間接性表明了在訪問虛基類的成員時(shí)同樣也必須通過某種間接機(jī)制來完成(下面模型
                   中會(huì)講到),共享性表象在虛基類會(huì)在虛繼承體系中被共享,而不會(huì)出現(xiàn)多份拷貝。
                   那現(xiàn)在可以解釋語法小節(jié)中留下來的那個(gè)問題了,“為什么一旦出現(xiàn)了虛基類,就必
                   須在每一個(gè)繼承類中都必須包含虛基類的初始化語句”。由上面的分析可以知道,
                   虛基類是被共享的,也就是在繼承體系中無論被繼承多少次,對(duì)象內(nèi)存模型中均只會(huì)
                   出現(xiàn)一個(gè)虛基類的子對(duì)象(這和多繼承是完全不同的),這樣一來既然是共享的那么
                   每一個(gè)子類都不會(huì)獨(dú)占,但是總還是必須要有一個(gè)類來完成基類的初始化過程(因?yàn)?br>       所有的對(duì)象都必須被初始化,哪怕是默認(rèn)的),同時(shí)還不能夠重復(fù)進(jìn)行初始化,那到
                   底誰應(yīng)該負(fù)責(zé)完成初始化呢?C++標(biāo)準(zhǔn)中(也是很自然的)選擇在每一次繼承子類中
                   都必須書寫初始化語句(因?yàn)槊恳淮卫^承子類可能都會(huì)用來定義對(duì)象),而在最下層
                   繼承子類中實(shí)際執(zhí)行初始化過程。所以上面在每一個(gè)繼承類中都要書寫初始化語句,
                   但是在創(chuàng)建對(duì)象時(shí),而僅僅會(huì)在創(chuàng)建對(duì)象用的類構(gòu)造函數(shù)中實(shí)際的執(zhí)行初始化語句,
                   其他的初始化語句都會(huì)被壓制不調(diào)用。
                  
                3. 模型
                   為了實(shí)現(xiàn)上面所說的三種語義含義,在考慮對(duì)象的實(shí)現(xiàn)模型(也就是內(nèi)存模型)時(shí)就
                   很自然了。在C++中對(duì)象實(shí)際上就是一個(gè)連續(xù)的地址空間的語義代表,我們來分析虛
                   繼承下的內(nèi)存模型。
                   3.1. 存在
                       也就是說在對(duì)象內(nèi)存中必須要包含虛基類的完整子對(duì)象,以便能夠完成通過地址
                       完成對(duì)象的標(biāo)識(shí)。那么至于虛基類的子對(duì)象會(huì)存放在對(duì)象的那個(gè)位置(頭、中間、
                       尾部)則由各個(gè)編譯器選擇,沒有差別。(在VC8中無論虛基類被聲明在什么位置,
                       虛基類的子對(duì)象都會(huì)被放置在對(duì)象內(nèi)存的尾部)
                   3.2. 間接
                       間接性表明了在直接虛基承子類中一定包含了某種指針(偏移或表格)來完成通
                       過子類訪問虛基類子對(duì)象(或成員)的間接手段(因?yàn)樘摶愖訉?duì)象是共享的,
                       沒有確定關(guān)系),至于采用何種手段由編譯器選擇。(在VC8中在子類中放置了
                       一個(gè)虛基類指針vbc,該指針指向虛函數(shù)表中的一個(gè)slot,該slot中存放則虛基
                       類子對(duì)象的偏移量的負(fù)值,實(shí)際上就是個(gè)以補(bǔ)碼表示的int類型的值,在計(jì)算虛
                       基類子對(duì)象首地址時(shí),需要將該偏移量取絕對(duì)值相加,這個(gè)主要是為了和虛表
                       中只能存放虛函數(shù)地址這一要求相區(qū)別,因?yàn)榈刂肥窃a表示的無符號(hào)int類型
                       的值)
                   3.3. 共享
                       共享表明了在對(duì)象的內(nèi)存空間中僅僅能夠包含一份虛基類的子對(duì)象,并且通過
                       某種間接的機(jī)制來完成共享的引用關(guān)系。在介紹完整個(gè)內(nèi)容后會(huì)附上測試代碼,
                       體現(xiàn)這些內(nèi)容。
                4. 性能
                   由于有了間接性和共享性兩個(gè)特征,所以決定了虛繼承體系下的對(duì)象在訪問時(shí)必然
                   會(huì)在時(shí)間和空間上與一般情況有較大不同。
                   4.1. 時(shí)間
                       在通過繼承類對(duì)象訪問虛基類對(duì)象中的成員(包括數(shù)據(jù)成員和函數(shù)成員)時(shí),都
                       必須通過某種間接引用來完成,這樣會(huì)增加引用尋址時(shí)間(就和虛函數(shù)一樣),
                       其實(shí)就是調(diào)整this指針以指向虛基類對(duì)象,只不過這個(gè)調(diào)整是運(yùn)行時(shí)間接完成的。
                       (在VC8中通過打開匯編輸出,可以查看*.cod文件中的內(nèi)容,在訪問虛基類對(duì)象
                       成員時(shí)會(huì)形成三條mov間接尋址語句,而在訪問一般繼承類對(duì)象時(shí)僅僅只有一條mov
                       常量直接尋址語句)
                   4.2. 空間
                       由于共享所以不同在對(duì)象內(nèi)存中保存多份虛基類子對(duì)象的拷貝,這樣較之多繼承
                       節(jié)省空間。
                5. 應(yīng)用
                   談了那么多語言特性和內(nèi)容,那么在什么情況下需要使用虛繼承,而一般應(yīng)該如何使
                   用呢?
                   這個(gè)問題其實(shí)很難有答案,一般情況下如果你確性出現(xiàn)多繼承沒有必要,必須要共享
                   基類子對(duì)象的時(shí)候可以考慮采用虛繼承關(guān)系(C++標(biāo)準(zhǔn)ios體系就是這樣的)。由于每
                   一個(gè)繼承類都必須包含初始化語句而又僅僅只在最底層子類中調(diào)用,這樣可能就會(huì)使
                   得某些上層子類得到的虛基類子對(duì)象的狀態(tài)不是自己所期望的(因?yàn)樽约旱某跏蓟Z
                   句被壓制了),所以一般建議不要在虛基類中包含任何數(shù)據(jù)成員(不要有狀態(tài)),只
                   可以作為接口類來提供。
                           假定通過多個(gè)派生路徑繼承名為X的成員,有下面三種可能性:

                        1)如果在每個(gè)路徑中X表示同一虛基類成員,則沒有二義性,因?yàn)楣蚕碓摮蓡T的單個(gè)實(shí)例;
                        2)如果在某個(gè)路徑中X是虛基類的成員,而在另一路徑中X是后代派生類的成員,也沒有二義性——特定派生類實(shí)例的優(yōu)先級(jí)高于共享虛基類實(shí)例。 
                        3)如果沿每個(gè)繼承路徑X表示后代派生類的不同成員,則該成員的直接訪問是二義性的。




             
             


            附錄:測試代碼
            #include <ctime>
            #include <iostream>

            /*
             * 帶有數(shù)據(jù)成員的基類
             */
            struct CBaseClass1
            {
                CBaseClass1( size_t i ) : m_val( i ) {}

                size_t m_val;
            };
            /*
             * 虛擬繼承體系
             */
            struct CSubClassV1 : public virtual CBaseClass1
            {
                CSubClassV1( size_t i ) : CBaseClass1( i ) {}
            };

            struct CSubClassV2 : public virtual CBaseClass1
            {
                CSubClassV2( size_t i ) : CBaseClass1( i ) {}
            };

            struct CDiamondClass1 : public CSubClassV1, public CSubClassV2
            {
                CDiamondClass1( size_t i ) : CBaseClass1( i ), CSubClassV1( i ), CSubClassV2( i ) {}
            };

            struct CDiamondSubClass1 : public CDiamondClass1
            {
                CDiamondSubClass1( size_t i ) : CBaseClass1( i ), CDiamondClass1( i ) {}
            };
            /*
             * 正常繼承體系
             */
            struct CSubClassN1 : public CBaseClass1
            {
                CSubClassN1( size_t i ) : CBaseClass1( i ) {}
            };
            struct CSubClassN2 : public CBaseClass1
            {
                CSubClassN2( size_t i ) : CBaseClass1( i ) {}
            };
            struct CMultiClass1 : public CSubClassN1, public CSubClassN2
            {
                CMultiClass1( size_t i ) : CSubClassN1( i ), CSubClassN2( i ) {}
            };
            struct CMultiSubClass1 : public CMultiClass1
            {
                CMultiSubClass1( size_t i ) : CMultiClass1( i ) {}
            };
            /*
             * 不帶有數(shù)據(jù)成員的接口基類
             */
            struct CBaseClass2
            {
                virtual void func() {};
                virtual ~CBaseClass2() {}
            };
            /*
             * 虛擬繼承體系
             */
            // struct CBaseClassX { CBaseClassX() {i1 = i2 = 0xFFFFFFFF;} size_t i1, i2;};
            struct CSubClassV3 : public virtual CBaseClass2
            {
            };
            struct CSubClassV4 : public virtual CBaseClass2
            {
            };
            struct CDiamondClass2 : public CSubClassV3, public CSubClassV4
            {
            };
            struct CDiamondSubClass2 : public CDiamondClass2
            {
            };

            /*
             * 正常繼承體系
             */
            struct CSubClassN3 : public CBaseClass2
            {
            };
            struct CSubClassN4 : public CBaseClass2
            {
            };
            struct CMultiClass2 : public CSubClassN3, public CSubClassN4
            {
            };
            struct CMultiSubClass2 : public CMultiClass2
            {
            };

            /*
             * 內(nèi)存布局用類聲明.
             */
            struct CLayoutBase1
            {
                CLayoutBase1() : m_val1( 0 ), m_val2( 1 ) {}

                size_t m_val1, m_val2;
            };
            struct CLayoutBase2
            {
                CLayoutBase2() : m_val1( 3 ) {}

                size_t m_val1;
            };
            struct CLayoutSubClass1 : public virtual CBaseClass1, public CLayoutBase1, public CLayoutBase2
            {
                CLayoutSubClass1() : CBaseClass1( 2 ) {}
            };


            #define MAX_TEST_COUNT 1000 * 1000 * 16
            #define TIME_ELAPSE() ( std::clock() - start * 1.0 ) / CLOCKS_PER_SEC

            int main( int argc, char *argv[] )
            {
                /*
                 * 類體系中的尺寸.
                 */
                std::cout << "================================ sizeof ================================" << std::endl;
                std::cout << "    ----------------------------------------------------------------" << std::endl;
                std::cout << "sizeof( CBaseClass1 )       = " << sizeof( CBaseClass1 ) << std::endl;
                std::cout << std::endl;
                std::cout << "sizeof( CSubClassV1 )       = " << sizeof( CSubClassV1 ) << std::endl;
                std::cout << "sizeof( CSubClassV2 )       = " << sizeof( CSubClassV2 ) << std::endl;
                std::cout << "sizeof( CDiamondClass1 )    = " << sizeof( CDiamondClass1 ) << std::endl;
                std::cout << "sizeof( CDiamondSubClass1 ) = " << sizeof( CDiamondSubClass1 ) << std::endl;
                std::cout << std::endl;
                std::cout << "sizeof( CSubClassN1 )       = " << sizeof( CSubClassN1 ) << std::endl;
                std::cout << "sizeof( CSubClassN2 )       = " << sizeof( CSubClassN2 ) << std::endl;
                std::cout << "sizeof( CMultiClass1 )      = " << sizeof( CMultiClass1 ) << std::endl;
                std::cout << "sizeof( CMultiSubClass1 )   = " << sizeof( CMultiSubClass1 ) << std::endl;

                std::cout << "    ----------------------------------------------------------------" << std::endl;
                std::cout << "sizeof( CBaseClass2 )       = " << sizeof( CBaseClass2 ) << std::endl;
                std::cout << std::endl;
                std::cout << "sizeof( CSubClassV3 )       = " << sizeof( CSubClassV3 ) << std::endl;
                std::cout << "sizeof( CSubClassV4 )       = " << sizeof( CSubClassV4 ) << std::endl;
                std::cout << "sizeof( CDiamondClass2 )    = " << sizeof( CDiamondClass2 ) << std::endl;
                std::cout << "sizeof( CDiamondSubClass2 ) = " << sizeof( CDiamondSubClass2 ) << std::endl;
                std::cout << std::endl;
                std::cout << "sizeof( CSubClassN3 )       = " << sizeof( CSubClassN3 ) << std::endl;
                std::cout << "sizeof( CSubClassN4 )       = " << sizeof( CSubClassN4 ) << std::endl;
                std::cout << "sizeof( CMultiClass2 )      = " << sizeof( CMultiClass2 ) << std::endl;
                std::cout << "sizeof( CMultiSubClass2 )   = " << sizeof( CMultiSubClass2 ) << std::endl;
                /*
                 * 對(duì)象內(nèi)存布局
                 */
                std::cout << "================================ layout ================================" << std::endl;
                std::cout << "    --------------------------------MI------------------------------" << std::endl;
                CLayoutSubClass1 *lsc = new CLayoutSubClass1;
                std::cout << "sizeof( CLayoutSubClass1 )   = " << sizeof( CLayoutSubClass1 ) << std::endl;
                std::cout << "CLayoutBase1 offset of CLayoutSubClass1 is " << (char*)(CLayoutBase1 *)lsc - (char*)lsc << std::endl;
                std::cout << "CBaseClass1  offset of CLayoutSubClass1 is " << (char*)(CBaseClass1  *)lsc - (char*)lsc << std::endl;
                std::cout << "CLayoutBase2 offset of CLayoutSubClass1 is " << (char*)(CLayoutBase2 *)lsc - (char*)lsc << std::endl;

                int *ptr = (int*)lsc;
                std::cout << "vbc in CLayoutSubClass1 is " << *(int*)ptr[3] << std::endl;

                delete lsc;

                std::cout << "    --------------------------------SI------------------------------" << std::endl;
                CSubClassV1 *scv1 = new CSubClassV1( 1 );
                std::cout << "sizeof( CSubClassV1 )   = " << sizeof( CSubClassV1 ) << std::endl;
                std::cout << "CBaseClass1 offset of CSubClassV1 is " << (char*)(CBaseClass1 *)scv1 - (char*)scv1 << std::endl;

                ptr = (int*)scv1;
                std::cout << "vbc in CSubClassV1 is " << *(int*)ptr[0] << std::endl;

                delete scv1;

                /*
                 * 性能測試
                 */
                std::cout << "================================ Performance ================================" << std::endl;
                double times[4];
                size_t idx = 0;

                CSubClassV1 *ptr1 = new CDiamondClass1( 1 );
                std::clock_t start = std::clock();
                {
                    for ( size_t i = 0; i < MAX_TEST_COUNT; ++i )
                        ptr1->m_val = i;
                }
                times[idx++] = TIME_ELAPSE();
                delete static_cast<CDiamondClass1*>( ptr1 );

                CSubClassN1 *ptr2 = new CMultiClass1( 0 );
                start = std::clock();
                {
                    for ( size_t i = 0; i < MAX_TEST_COUNT; ++i )
                        ptr2->m_val = i;
                }
                times[idx++] = TIME_ELAPSE();
                delete static_cast<CMultiClass1*>( ptr2 );

                std::cout << "CSubClassV1::ptr1->m_val " << times[0] << " s" << std::endl;
                std::cout << "CSubClassN1::ptr2->m_val " << times[1] << " s" << std::endl;

                return 0;
            }

            測試環(huán)境:
                軟件環(huán)境:Visual Studio2005 Pro + SP1, boost1.34.0
                硬件環(huán)境:PentiumD 3.0GHz, 4G RAM
            測試數(shù)據(jù):
            ================================ sizeof ================================
                ----------------------------------------------------------------
            sizeof( CBaseClass1 )       = 4

            sizeof( CSubClassV1 )       = 8
            sizeof( CSubClassV2 )       = 8
            sizeof( CDiamondClass1 )    = 12
            sizeof( CDiamondSubClass1 ) = 12

            sizeof( CSubClassN1 )       = 4
            sizeof( CSubClassN2 )       = 4
            sizeof( CMultiClass1 )      = 8
            sizeof( CMultiSubClass1 )   = 8
                ----------------------------------------------------------------
            sizeof( CBaseClass2 )       = 4

            sizeof( CSubClassV3 )       = 8
            sizeof( CSubClassV4 )       = 8
            sizeof( CDiamondClass2 )    = 12
            sizeof( CDiamondSubClass2 ) = 12

            sizeof( CSubClassN3 )       = 4
            sizeof( CSubClassN4 )       = 4
            sizeof( CMultiClass2 )      = 8
            sizeof( CMultiSubClass2 )   = 8
            ================================ layout ================================
                --------------------------------MI------------------------------
            sizeof( CLayoutSubClass1 )   = 20
            CLayoutBase1 offset of CLayoutSubClass1 is 0
            CBaseClass1  offset of CLayoutSubClass1 is 16
            CLayoutBase2 offset of CLayoutSubClass1 is 8
            vbc in CLayoutSubClass1 is -12
                --------------------------------SI------------------------------
            sizeof( CSubClassV1 )   = 8
            CBaseClass1 offset of CSubClassV1 is 4
            vbc in CSubClassV1 is 0
            ================================ Performance ================================
            CSubClassV1::ptr1->m_val 0.062 s
            CSubClassN1::ptr2->m_val 0.016 s

            結(jié)果分析:
                1. 由于虛繼承引入的間接性指針?biāo)詫?dǎo)致了虛繼承類的尺寸會(huì)增加4個(gè)字節(jié);
                2. 由Layout輸出可以看出,虛基類子對(duì)象被放在了對(duì)象的尾部(偏移為16),并且vbc
                   指針必須緊緊的接在虛基類子對(duì)象的前面,所以vbc指針?biāo)赶虻膬?nèi)容為“偏移 - 4”;
                3. 由于VC8將偏移放在了虛函數(shù)表中,所以為了區(qū)分函數(shù)地址和偏移,所以偏移是用補(bǔ)
                   碼int表示的負(fù)值;
                4. 間接性可以通過性能來看出,在虛繼承體系同通過指針訪問成員時(shí)的時(shí)間一般是一般
                   類訪問情況下的4倍左右,符合匯編語言輸出文件中的匯編語句的安排。
            posted on 2009-06-08 22:34 ivy-jie 閱讀(172) 評(píng)論(0)  編輯 收藏 引用 所屬分類: c++
            精品国产91久久久久久久a| 国产免费久久精品丫丫| 久久一区二区三区免费| 波多野结衣中文字幕久久 | 精品国产91久久久久久久| 中文字幕日本人妻久久久免费 | 久久久久亚洲爆乳少妇无| 国产精品一区二区久久| 日本人妻丰满熟妇久久久久久| 久久久久亚洲AV片无码下载蜜桃| 香蕉久久AⅤ一区二区三区| 精品国产乱码久久久久久浪潮| 精品久久久久一区二区三区| 成人a毛片久久免费播放| 99久久精品免费观看国产| 亚洲国产成人久久综合碰碰动漫3d| 国产精品久久午夜夜伦鲁鲁| 69久久精品无码一区二区| 国产精品久久久久久福利69堂| 久久综合国产乱子伦精品免费| 久久精品国产亚洲av麻豆色欲 | 久久久久国产精品人妻| 一本一本久久A久久综合精品| 亚洲va中文字幕无码久久| 无码伊人66久久大杳蕉网站谷歌| 波多野结衣AV无码久久一区| 日本人妻丰满熟妇久久久久久| 国产一区二区三区久久| 久久996热精品xxxx| 中文字幕久久精品| 伊人久久大香线蕉av不变影院| 99国产精品久久久久久久成人热| 久久精品国产一区二区三区日韩| 国产三级观看久久| 日韩中文久久| 国产成人精品免费久久久久| 亚洲精品国产成人99久久| 天堂无码久久综合东京热| 无码AV波多野结衣久久| 99久久精品久久久久久清纯| 久久99久久99精品免视看动漫|