青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

隨筆-4  評論-40  文章-117  trackbacks-0
(轉載)


Ogre源碼剖析: 任意類型類 Any

 

有些時候我們可能想做這樣一件事:

float f = 1.f;

int n = 2;

std::vector<X> myContainer;           // X是一個虛構的用戶定義類型

myContainer.pushback(X(f));

myContainer.pushback(X(n));

 

我們想在一個容器里保存兩種乃至多種不同的數據類型。

但是,顯然普通的模板參數如std::vector<int>,或者std::vector<float>都無法滿足我們的需求。

 

也可能存在下面這樣的情況:

float fVal = 1.f;

X xMsg = fVal;

PushMessage(xMsg); // PushMessage發(fā)送了一個異步消息

 

// 另一個地方(可能是另一個線程),回調函數被調用:

OnMessageXXX(X xMsg)

{

         float f = static_cast<float>(xMsg);

         // do something with value f

}

 

針對這種需求,我們想要一種類型,它可以接受任意的類型,并在需要的時候把我們放入的真正的類型取出來,它可以被放置到容器中,可以被拷貝,以及串行化(前提要求被置入其中的類型可以被輸出到標準輸出流–std::ostream當中)。

 

如何設計這種類型呢?

 

一種容易想到的方案如下:

 

class Any {

public:

enum {

                  ANYTYPE_INT,

                  ANYTYPE_FLOAT,

                  ANYTYPE_LONG,

                  // … other data types

};

 

union {

                   int nIntData;

                   float fFloatData;

                   long lLongData;

                   // … other data types

};

 

Any(int nData) : nIntData(nData), m_type(ANYTYPE_INT) {}

Any(long lData) : lLongData(lData), m_type(ANYTYPE_LONG) {}

Any(float fData) : fFloatData(fData), m_type(ANYTYPE_FLOAT) {}

// … other constructors

};

 

現在我們可以這樣寫:

Int nData = 3;

Any myVal(nData);

 

為了讓Any可以被賦值,我們需要給Any提供一個operator =。

 

Any& Any::operator = (const Any& rhs)

{

         if (&rhs == this)

                   return *this;

         m_type = rhs.m_type;

         switch(rhs.m_type)

{

case ANYTYPE_INT:

         nIntData = rhs.nIntData;

         break;

case ANYTYPE_LONG:

lLongData = rhs.lLongData;

break;

case ANYTYPE_FLOAT:

         fFloatData = rhs.fFloatData;

         break;

default:

         ASSERT(0);

};

}

 

這樣Any就有了相互之間被賦值的能力了,如下:

 

Any myVal1(1);

Any myVal2(5.6f);

myVal1 = myVal2;

 

然而我們還希望Any可以直接用各種數據類型賦值。因此,針對需要支持的每一種數據類型,都應該重載一個operator =。如下:

 

Any& operator = (int inData)

{

         nIntData = inData;

         m_type = ANYTYPE_INT;

         return *this;

}

 

Any& operator = (long inData)

{

         lLongData = inData;

         m_type = ANYTYPE_LONG;

         return *this;

}

 

等等。

當然如果嫌這種寫法過于冗長,出于節(jié)省代碼的考慮,我們可以使用一個宏來代替:

#define OPEQUAL(type, typeID, varName) \

         Any& operator = (type nData) \

         { \

                   varName = paramName; \

                   m_type = typeID; \

                   return *this;

         }

這樣就可以將上述代碼轉換成:

OPEQUAL(int, ANYTYPE_INT, nIntData);

OPEQUAL(long, ANYTYPE_LONG, lLongData);

OPEQUAL(float, ANYTYPE_FLOAT, fFloatData);

 

此外,作為一種數據的承載,我們還希望在需要的時候把實際的數據取出來,因此我們需要重載一系列獲取函數:

 

operator int() const { return nIntData; }

operator long() const { return lLongData; }

operator float() const { return fFloatData; }

 

有了這些類型operator之后,當需要從Any中取出我們想要的數據時,即可以通過:

Any myAnyVal(25);

int nIntData = myAnyVal;

這種形式得到想要的值。但是需要注意的是,這里不可以對Any的隱式轉換做正確性的假定。即,我們不能寫下如下代碼:

Any myAnyVal(25);

float fData = myAnyVal;

我們不能指望這里返回正確的值。因為Any中保存的數據實際為int,而當這個賦值發(fā)生時,根據重載決議,編譯器會調用Any的operator float()返回一個float值。而由于float在內存中的放置是基于IEEE浮點格式的,int則是2進制的數據,最后返回的數據就不可能正確了。

基于此,我們要求在使用Any的時候,存放數據的位置和取出數據的位置,都必須由程序員指定對應好的數據類型,并且寄希望于程序員知道自己在做什么。

 

上述以上的實現方法有2個好處:

1、  節(jié)省內存,對于不同大小的數據類型,通過union的形式共用了存儲空間。

2、  速度快,沒有使用動態(tài)分配內存的模式,而是直接將對象放置在union空間中。

 

但是如果我們想讓這個Any支持std::string,就不能像上述實現得那么直接了。

 

因為union中不能支持non-trivial的數據類型(因為對于non-trivial的數據類型,編譯器在為其分配內存空間之后,還要調用其構造函數)。我們必須另外想一種方法。

一種簡單的容易想到的方法是保存指針,如下:

 

union {

         int nIntData;

std::string * ptrStr;

};

 

這樣,我們需要在operator =以及copy constructor中手動管理內存。當Any初始化為String類型時,需要動態(tài)申請內存,而如果構建好的Any中所含的類型在operator =中由String轉為其他類型時,需要將其動態(tài)釋放,在由其他類型轉為String時,則需要動態(tài)申請。如果這里的其他類型是另外的動態(tài)類型,且也是通過指針保存在union結構中的,則也需要做相應的釋放&申請?zhí)幚怼?/p>

 

如下:

Any& operator = (int inData)

{

         switch (m_type)

         {

         case ANYTYPE_STRING:

         delete ptrStr;

nIntData = inData;

                   break;

         // other cases

};

m_type = ANYTYPE_INT;

}

 

Any& operator = (std::string& inData)

{

         switch (m_type)

         {

         case ANYTYPE_INT:

                  ptrStr = new std::string(inData);

                   break;

         // other cases

};

m_type = ANYTYPE_STRING;

}

 

除了以上所述的operator = 中所增加的代碼之外,constructor以及copy constructor針對std::string重載的版本也需要做內存分配。destructor中也需要根據數據的實際類型判斷是否釋放相應的指針。

 

現在,這種做法對于C++內置類型像之前一樣是直接支持的。但對于用戶自定義類型,則需要通過指針的形式,動態(tài)的創(chuàng)建以及釋放。當Any類中包含的數據從基本類型切換到non-trivial類型或者反向切換的時候,需要釋放或者分配內存;但是在基本類型之間切換的時候,不需要做動態(tài)內存分配&釋放。

這種不一致性導致了代碼需要根據類型不同做出不同的處理,隨著Any支持類型的增多,不可避免的代碼膨脹發(fā)生了,而且每增加一種新類型,需要修改所有重載版本的operator =,以及新增一份constructor及copy constructor,并在destructor中增加對應類型的判斷。

最為讓人惱火的是,Any在處理trivial類型和non-trivial類型時的行為不一。這非常容易導致錯誤。

 

下面,我們嘗試把問題簡化一下,通過讓Any始終保存指針來避免行為不統(tǒng)一的問題。無論從何種類型切換到另一種類型,我們都確保必須釋放先前的內存,并為目標類型分配新的內存。

 

新的數據結構可以設置如下:

class Any {

public:

         void * m_pointer;

         int m_nType;

 

// supporting types

enum {

ANYTYPE_INT,

ANYTYPE_FLOAT,

ANYTYPE_STRING,

//… other supporting types

};

 

Any(int inData) : m_nType(ANYTYPE_INT), m_pointer(new int(inData)) {}

Any(float inData) : m_nType(ANYTYPE_FLOAT), m_pointer(new float(inData) {}

Any(const std::string& inData) :

m_nType(ANYTYPE_STRING), m_pointer(new std::string(inData)) {}

Any(const Any& rhs) : m_nType(rhs.m_nType) {

         switch (m_nType)

         {

         case ANYTYPE_INT:

                   m_pointer = new int(*((int *)rhs.m_pointer));

                   break;

         case ANYTYPE_FLOAT:

                   m_pointer = new float(*((float *)rhs.m_pointer));

                   break;

         case ANYTYPE_STRING:

                   m_pointer = new std::string(*((std::string *)rhs.m_pointer));

                   break;

}

}

Any& Operator = (int inData) {

if (m_nType == ANYTYPE_INT)

         *(int*)m_pointer = inData;

else {

         switch (m_nType)

         {

         case ANYTYPE_FLOAT:

                   delete (float*)m_pointer;

                   break;

         case ANYTYPE_STRING:

                   delete (std::string*)m_pointer;

                   break;

}

m_pointer = new int(inData);

};

}

// other operator equals…

};

 

現在的情況比之前好很多,我們不用再為Any在對待trivial類型數據與non-trivial數據時的行為不一而頭痛了。

但是問題依舊很麻煩,因為使用void指針消除了存儲時的類型信息,所以當delete指針時,我們需要人為的指定每一處指針所指代的類型,從而使編譯器得以調用正確的析構函數。

從而,每一個重載版本的operator =當中,我們都需要判斷當前的類型是否與傳入參數的類型相符,若不相符,需要根據存儲的類型標識符m_nType對m_pointer轉型,并使用delete operator完成內存釋放的工作,而后再為傳入參數分配新的內存。

 

來回轉型可能令你覺得厭煩。或許你會想到將不同種類的指針放在union當中,這樣就不必為void指針轉型了。但這樣做行不通,原因是我們依舊需要根據m_nType的類型決定使用union中的哪個成員,實際上依舊等價于上面的做法,只不過省卻了轉型操作符(將轉型操作符的工作移交到union的定義當中了)。

 

難道沒有更好的方法嗎?

在思考如何實現Any的過程中,我們發(fā)現了它的兩個特點,譬如:

1、  我們需要保存類型信息:m_nType以及相應的enum定義;

2、  在使用Any時,我們需要明確的指出其contain的數據類型,以此得到正確的數據。(例如,使用int構建的Any,從Any中把數據拿出來時,目標也應當是一個int,而不能是float,否則會調用到錯誤的重載函數,從而得出錯誤結果)

3、  我們需要針對Any支持的所有類型實現constructor & operator = & operator T的重載版本。

 

為什么不利用C++自身的設施完成這種工作呢?

由第1、2點,我們發(fā)現,Any在存數和取數的時候,需要使用對應的數據類型,從而調用匹配正確的構造函數/operator =以及operator T() (這里的T指代各種類型如int, float)的重載版本。而且,Any被賦予一個值之后,再未被再次賦予其他數據類型的值之前,類型信息是始終保存在Any當中的。而當Any保存的數據類型變動時,對應的類型ID也需要更新。在這里,C++的運行時類型信息(runtime type info)正是用武之地。

 

在實現針對不同類型的重載函數時,我們發(fā)現幾乎絕大多數工作都是重復或者類似的,在早期的版本中,甚至可以用宏來節(jié)省代碼編寫的工作量。C++中的模板正暗合了這里的需求。

 

下面,我們開始隨著Ogre::Any的設計思路前行。(Ogre::Any使用了Boost::Any,但在數值Any以及stream操作上做了擴展)

 

如果利用模板,我們就不必再為每一種需要支持的類型寫一個重載版本的constructor & operator = & operator T了。

C++的成員函數模板使我們可以寫下類似下面這樣的代碼:

class Any {

public:

template <typename ValueType>

Any(const ValueType& v);

 

template <typename ValueType>

Any& operator = (const ValueType& v);

 

template <typename ValueType>            

operator ValueType() const;                       // 實際實現中并沒有定義這個

};

 

但是operator ValueType()的約束太過于寬泛了,有了它的存在,現在Any可能被用在任何我們意想不到的地方。所以實際的實現中Ogre::Any并沒有定義轉型操作符,而是使用了名為any_cast的函數,當我們需要在某個地方從Any中取出我們想要的數據時,我們必須清楚這個Any里放著的是什么,并且明確的把它cast出來。

any_cast的聲明如下:

template <typename ValueType>

ValueType* any_cast(Any* anyVal);

 

template <typename ValueType>

const ValueType* any_cast(const Any* anyVal);

 

template <typename ValueType>

ValueType any_cast(const Any& anyVal);

 

這樣我們就可以用像使用static_cast一樣的方法,使用any_cast,如下:

Any anyVal(3246);

int nVal = any_cast<int>(anyVal);

 

OK,模板現在節(jié)約了我們大量的重復勞動,一個模板就涵蓋了所有的可能,我們不必再為以后需要新增加的數據類型而頭痛了。

基于成員函數模板的Any看起來似乎不錯。但是究竟應該如何存儲數據呢?

 

首先,想到的是在Any中置放一個模板成員變量,像這樣:

class Any {

public:

template <typename ValueType>

Any(const ValueType& v);

private:

T m_val;

};

 

但是這樣做顯然行不通,因為這樣一來Any類就必須是一個模板類了。這不符合我們對Any的期望。況且,一旦確定了Any的模板參數,他也就成了一個只能承載確定類型的wrapper了。這不是我們想要的。

 

那么如果不保存模板成員,而是使用模板成員指針是否可行呢?答案依舊是不可行。因為形如T* m_val;的定義依舊需要在編譯期獲知T的準確類型。一旦在編譯期確定了準確的類型,我們就無法在運行期動態(tài)改變他了。

我們需要的是一個運行期可以動態(tài)變化的模板成員。

 

由于前面的分析,直接將一個模板成員存儲于Any當中是行不通的,但是我們卻可以保存一個確定類型的指針,并讓這個指針應該指向實際存儲我們需要的模板數據的實例。

 

通過類似于以下這樣的繼承關系:

class placeholder;

class holder<T> : public placeholder;

我們擁有了承載無窮種數據類型的可能性。

該繼承關系如下圖:

 

有了這樣的數據承載類之后,Any中只需保存一個placeholder接口的指針即可。而在constructor / operator = 的時候,只需刪除此前的數據,并為新的數據類型創(chuàng)建對應的holder<T>實例即可。

 

placeholder需要定義clone接口,用于在copy constructor中生成數據的拷貝。

需要定義getType接口,用于在any_cast中識別當前保存的數據類型與目標類型是否一致。(getType的實現可以采用此前的enum + m_nType的方法,但是這種方法的局限性在于我們需要為每一個可能的類型增加一個標識符,因此更好的做法是使用C++的運行時類型識別信息RTTI:Runtime type info / Runtime type identify)

 

實際的placeholder定義如下:

class placeholder {

public:

virtual ~placeholder() {}                               // 虛析構函數用以保證派生類的析構函數得以調用

virtual placeholder * clone() const = 0;  

virtual const std::type_info& getType() const = 0; // 返回rtti信息

virtual void writeToStream(std::ostream& o) = 0;  // 串行化支持

};

 

真正的承載數據的類模板holder定義很簡單,實現相應的基類接口即可,如下:

template<typename ValueType>

class holder : public placeholder

{

public: // structors

 

holder(const ValueType & value) : held(value) { }

virtual const std::type_info & getType() const { return typeid(ValueType); }

    virtual placeholder * clone() const { return new holder(held); }

         virtual void writeToStream(std::ostream& o) { o << held; }

 

ValueType held;

};

 

這樣一來,Any的定義就自然而生了:

class Any {

public:

  Any() : mContent(0) {}

template<typename ValueType>

explicit Any(const ValueType & value)

: mContent(new holder<ValueType>(value)) { }

Any(const Any & other)

: mContent(other.mContent ? other.mContent->clone() : 0) { }

virtual ~Any() { delete mContent; }

 

Any& swap(Any & rhs)

  {

       std::swap(mContent, rhs.mContent);

    return *this;

  }

template<typename ValueType>

  Any& operator=(const ValueType & rhs)

  {

       Any(rhs).swap(*this);

    return *this;

}

Any & operator=(const Any & rhs)

  {

Any(rhs).swap(*this);

    return *this;

}

 

bool isEmpty() const { return !mContent; }

  const std::type_info& getType() const

  { return mContent ? mContent->getType() : typeid(void); }

 

inline friend std::ostream& operator <<

( std::ostream& o, const Any& v )

{

if (v.mContent) v.mContent->writeToStream(o);

         return o;

}

protected:

placeholder* mContent;

template<typename ValueType>

    friend ValueType * any_cast(Any *);

};

 

以上就是Any的幾乎所有的定義了。此前介紹的holder以及placeholder由于只在Any中被使用到,因此在實做中被定義為Any的嵌套類。

 

any_cast的定義具體如下:

template<typename ValueType>

ValueType * any_cast(Any * operand)

{

return operand && operand->getType() == typeid(ValueType)

             ? &static_cast<Any::holder<ValueType> *>(operand->mContent)->held

        : 0;

}

需要判斷被cast的Any中所貯存的數據的類型是否與目標類型一致,判斷采用了C++的RTTI中的typeid。當類型不一致時,返回的結果為0。這一點的行為與dynamic_cast類似(dynamic_cast在正常的轉型失敗時會返回0)。使用any_cast而不是用類型轉換操作符的原因,一方面在于基于模板的類型轉換操作符過于隨意,另一方面在于any_cast的使用方式與static_cast等幾乎完全一致,符合C++的使用習慣,且當需要查找程序中有多少地方使用了any_cast時,一個grep就可以簡單的給出結果。

 

另外兩個重載版本的any_cast皆是調用上述指針版本的any_cast完成的。

 

至此,關于Any的實現的探討告一段落。

最終Ogre::Any利用C++的模板特性,RTTI特性實現了一個非常具有實用價值的任意類型構件。由于采用了指針存儲數據,如果大量的使用Any,會比基于union以及基本類型的實現方式速度緩慢一些。

然而對于期待高速度的多類型組合結構,在Boost中存在另一個構件可以達到相應目的,即Boost::Variant。

Boost::Variant可以像這樣使用:Boost::Variant<int, float, std::string, vector<int> > myVariant;

具體Boost::Variant的使用以及實現方式,不在本文的探討范圍內。如果對此感興趣,可以參閱Boost的文檔:http://www.boost.org/doc/libs/1_37_0/doc/html/variant.html

 

另外,關于Boost::Any的實現,劉未鵬還撰寫過一篇非常優(yōu)秀的文章:

http://blog.csdn.net/pongba/archive/2004/08/24/82811.aspx

 

 

 

 

附:Ogre中除了使用Boost::Any之外,還對Any在數值上的應用做了擴展:

Ogre在Any的基礎上,實現了+-*/的數學運算操作符,構成了AnyNumberic類。

Any::placeholder接口實現了getType,clone,writeToStream以實現對于實際數據的取類型,拷貝,寫流操作。

在AnyNumberic中,numplaceholder的實現則需附加定義一套加減乘除的操作接口,從而使得AnyNumberic可以實現+-*/等運算符。(Any的結構此前已經分析過了,通過調用placeholder的接口實現對實際數據的操作,而placeholder接口背后的實現,則是一個根據初始化Any的實際類型實例化的模板類,在AnyNumberic中,這一結構相同)

class numplaceholder : public Any::placeholder {

         virtual ~numplaceholder() {} // override virtual destructor

         virtual placeholder add(placeholder* rhs) = 0;

         virtual placeholder subtract(placeholder* rhs) = 0;

         virtual placeholder multiply(placeholder* rhs) = 0;

         virtual placeholder multiply(float rhs) = 0; // override version

         virtual placeholder divide(placeholder* rhs) = 0;

};

 

template<typename ValueType>

class numholder : public numplaceholder {

//…

};

利用這一實現,AnyNumberic即可采用如下方式實現operator運算符:

AnyNumeric AnyNumeric::operator+(const AnyNumeric& rhs) const

{

         return AnyNumeric(

                                     static_cast<numplaceholder*>(mContent)->add(rhs.mContent));

}

其中mContent為繼承自Any的數據成員,類型為Any::placeholder的指針。

AnyNumberic沒有定義新的數據成員,僅僅是提供了一些新的接口,并通過派生Any::placeholder在不改動舊有功能的基礎上,提供了新的數學運算的能力。



posted on 2009-06-01 16:16 李陽 閱讀(1367) 評論(0)  編輯 收藏 引用

只有注冊用戶登錄后才能發(fā)表評論。
網站導航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            最新69国产成人精品视频免费| 老司机午夜精品| 久久精品国产精品| 欧美专区中文字幕| 久久精品一区蜜桃臀影院| 亚洲一级片在线看| 亚洲一区二区三区高清| 亚洲精品一区二区三区樱花| 亚洲人成人77777线观看| 在线成人欧美| aa级大片欧美| 亚洲一区日韩| 久久激情久久| 欧美激情在线狂野欧美精品| 欧美国产精品中文字幕| 亚洲激情一区二区三区| 亚洲福利视频网| 一本久久综合亚洲鲁鲁五月天| 一区二区日韩| 久久精品在线| 欧美日韩精品在线观看| 国产欧美日韩视频一区二区三区| 国产精品色午夜在线观看| 国产在线视频不卡二| 影音先锋国产精品| 午夜精品电影| 亚洲精品裸体| 久久精品国产免费| 久久野战av| 这里只有精品电影| 免费成人在线观看视频| 亚洲欧美激情在线视频| 国产精品欧美在线| 午夜精品久久久久久久99水蜜桃 | 欧美日韩性生活视频| 欧美电影在线播放| 激情婷婷久久| 另类天堂av| 久久久综合网站| 国产亚洲一区二区三区在线观看 | 欧美日韩国产精品一卡| 午夜久久福利| 欧美激情第一页xxx| 久久久久国产精品人| 欧美伦理视频网站| 欧美成人精品h版在线观看| 国产精品福利在线| 亚洲人人精品| 亚洲电影下载| 久久精品人人做人人爽电影蜜月| 国产一二三精品| 欧美福利影院| 国产精品久久久久久超碰 | 免播放器亚洲一区| 亚洲日本中文字幕| 日韩视频一区二区三区在线播放免费观看| 欧美日韩免费视频| 久久手机精品视频| 欧美日韩亚洲视频| 欧美91视频| 国产精品国产三级欧美二区| 六月婷婷一区| 国产精品毛片| 99热精品在线| 亚洲乱码国产乱码精品精可以看 | 久久精品官网| 欧美日本一区二区高清播放视频| 裸体女人亚洲精品一区| 欧美日韩一视频区二区| 欧美激情视频一区二区三区在线播放| 国产欧美一区二区三区在线老狼 | 开心色5月久久精品| 国产精品毛片va一区二区三区| 亚洲成人在线视频网站| 99ri日韩精品视频| 男男成人高潮片免费网站| 久久免费国产精品1| 亚洲人午夜精品免费| 国产精品白丝av嫩草影院 | 9色精品在线| 久久综合狠狠| 在线看日韩欧美| 久久美女艺术照精彩视频福利播放| 久久精品国产亚洲精品| 在线观看中文字幕不卡| 米奇777在线欧美播放| 欧美黑人在线播放| 99re这里只有精品6| 欧美日韩精品久久| 亚洲嫩草精品久久| 久久综合一区二区| 一区二区欧美亚洲| 国产在线一区二区三区四区| 一本到高清视频免费精品| 久久综合久久美利坚合众国| 亚洲全部视频| 久久久久久国产精品一区| 影音先锋亚洲视频| 国产精品自在线| 免费精品视频| 老司机精品久久| 久久aⅴ国产欧美74aaa| 亚洲精品欧洲| 最新亚洲一区| 欧美精品一区二区三区在线播放| 亚洲午夜一二三区视频| 国产亚洲精品久久久久久| 国产精品久久久久免费a∨| 欧美激情第六页| 欧美激情91| 欧美成va人片在线观看| 欧美国产先锋| 欧美激情视频在线播放| 欧美承认网站| 欧美18av| 免费在线欧美视频| 老司机一区二区| 欧美凹凸一区二区三区视频| 欧美在线综合视频| 亚洲欧美综合国产精品一区| 亚洲精品国产精品乱码不99| 亚洲已满18点击进入久久| 久久永久免费| 午夜视频一区在线观看| 欧美欧美在线| 亚洲日本激情| 欧美国产日韩精品| 久久久久国产一区二区三区四区 | 国产日韩在线亚洲字幕中文| 国产精品女主播一区二区三区| 国产日韩一区二区三区在线| 在线日韩av片| 亚洲欧美视频一区| 91久久久久久| 久久夜色精品亚洲噜噜国产mv| 欧美金8天国| 夜夜嗨av色综合久久久综合网| 一本色道久久综合亚洲91 | 国产精品99久久久久久宅男 | 一区二区三区视频在线播放| 亚洲影视中文字幕| 欧美激情亚洲| 亚洲第一二三四五区| 久久在线免费观看视频| 一本色道久久综合亚洲精品高清 | 国产精品久久久对白| 在线看片欧美| 亚洲国产精品t66y| 久久精品二区| 亚洲女ⅴideoshd黑人| 亚洲欧洲日本mm| 亚洲视频一区二区在线观看| 欧美日韩国产专区| 欧美专区亚洲专区| 久久精品国产在热久久| 免费国产一区二区| 国产美女搞久久| 欧美电影免费观看大全| 亚洲精品乱码视频| 性做久久久久久免费观看欧美| 亚洲韩日在线| 午夜视频久久久久久| 99精品免费网| 久久在线播放| 久久九九99| 国产伦精品一区二区三区在线观看 | 午夜精品视频网站| 91久久久久久久久| 欧美专区一区二区三区| 日韩亚洲欧美成人| 亚洲国产高清高潮精品美女| 欧美亚洲一区在线| 欧美精品在线一区二区| 亚洲国产专区| 亚洲精品久久嫩草网站秘色 | 欧美丝袜一区二区| 国产精品亚洲аv天堂网| 亚洲性感激情| 欧美国产另类| 欧美高潮视频| 日韩一二三区视频| 模特精品在线| 亚洲狼人综合| 亚洲电影av在线| 国产精品99久久久久久www| 欧美一区二区三区的| 亚洲福利专区| 国产欧美一区二区视频| 欧美久久综合| 美女成人午夜| 欧美一区二区精品久久911| 亚洲精品自在在线观看| 欧美aa国产视频| 久久久久久一区| 欧美一级黄色网| 亚洲一区免费在线观看| 欧美一区二区视频免费观看| 你懂的成人av| 久久国产99| 亚洲综合精品一区二区|