亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

C++模板元編程技術研究

2019-11-17 05:35:00
字體:
來源:轉載
供稿:網友

  摘要

  本文描述了模板元編程技術的起源、概念和機制,并介紹了模板元編程技術在Blitz++和Loki程序庫中的應用。

  要害字

  編譯期計算 模板元編程 Blitz++ Loki

  導言

  1994年,C++標準委員會在圣迭哥舉行的一次會議期間Erwin Unruh展示了一段可以產生質數的代碼。這段代碼的非凡之處在于質數產生于編譯期而非運行期,在編譯器產生的一系列錯誤信息中間夾雜著從2到某個設定值之間的所有質數:

// PRime number computation by Erwin Unruh
template <int i> strUCt D { D(void*); Operator int(); };

template <int p, int i> struct is_prime {
enum { prim = (p%i) && is_prime<(i > 2 ? p : 0), i -1> :: prim };
};

template < int i > struct Prime_print {
Prime_print<i-1> a;
enum { prim = is_prime<i, i-1>::prim };
void f() { D<i> d = prim; }
};

struct is_prime<0,0> { enum {prim=1}; };
struct is_prime<0,1> { enum {prim=1}; };
struct Prime_print<2> { enum {prim = 1}; void f() { D<2> d = prim; } };
#ifndef LAST
#define LAST 10
#endif
main () {
Prime_print<LAST> a;
}
  類模板D只有一個參數為void*的構造器,而只有0才能被合法轉換為void*。1994年,Erwin Unruh采用Metaware 編譯器編譯出錯信息如下(以及其它一些信息,簡短起見,它們被刪除了):

Type `enum{}′ can′t be converted to tXPe `D<2>′ ("primes.cpp",L2/C25).
Type `enum{}′ can′t be converted to txpe `D<3>′ ("primes.cpp",L2/C25).
Type `enum{}′ can′t be converted to txpe `D<5>′ ("primes.cpp",L2/C25).
Type `enum{}′ can′t be converted to txpe `D<7>′ ("primes.cpp",L2/C25).
  如今,上面的代碼已經不再是合法的C++程序了。以下是Erwin Unruh親手給出的修訂版,可以在今天符合標準的C++編譯器上進行編譯:

// Prime number computation by Erwin Unruh

template <int i> struct D { D(void*); operator int(); };

template <int p, int i> struct is_prime {
enum { prim = (p==2) (p%i) && is_prime<(i>2?p:0), i-1> :: prim };
};

template <int i> struct Prime_print {
Prime_print<i-1> a;
enum { prim = is_prime<i, i-1>::prim };
void f() { D<i> d = prim ? 1 : 0; a.f();}
};

template<> struct is_prime<0,0> { enum {prim=1}; };
template<> struct is_prime<0,1> { enum {prim=1}; };

template<> struct Prime_print<1> {
enum {prim=0};
void f() { D<1> d = prim ? 1 : 0; };
};

#ifndef LAST
#define LAST 18
#endif

main() {
Prime_print<LAST> a;
a.f();
}
  在GNU C++ (MinGW Special) 3.2中編譯這段程序時,編譯器將會給出如下出錯信息(以及其它一些信息,簡短起見,它們被刪除了):

Unruh.cpp:12: initializing argument 1 of `D<i>::D(void*) [with int i = 17]'
Unruh.cpp:12: initializing argument 1 of `D<i>::D(void*) [with int i = 13]'
Unruh.cpp:12: initializing argument 1 of `D<i>::D(void*) [with int i = 11]'
Unruh.cpp:12: initializing argument 1 of `D<i>::D(void*) [with int i = 7]'
Unruh.cpp:12: initializing argument 1 of `D<i>::D(void*) [with int i = 5]'
Unruh.cpp:12: initializing argument 1 of `D<i>::D(void*) [with int i = 3]'
Unruh.cpp:12: initializing argument 1 of `D<i>::D(void*) [with int i = 2]'
  這個例子展示了可以利用模板實例化機制于編譯期執行一些計算。這種通過模板實例化而執行的非凡的編譯期計算技術即被稱為模板元編程。

  順便說一句,因為編譯器的出錯信息并未被標準化,所以,假如你在Visual C++、Borland C++等編譯器上看不到這么具體的出錯信息,請不必訝異。
一個可以運行的模板元編程例子

  模板元編程(Template Metaprogramming)更準確的含義應該是“編‘可以編程序的’程序”,而模板元程序(Template Metaprogram)則是“‘可以編程序的’程序”。也就是說,我們給出代碼的產生規則,編譯器在編譯期解釋這些規則并生成新代碼來實現我們預期的功能。

  Erwin Unruh的那段經典代碼并沒有執行,它只是以編譯出錯信息的方式輸出中間計算結果。讓我們來看一個可以運行的模板元編程例子 — 計算給定整數的指定次方:

// xy.h

//原始摸板
template<int Base, int Exponent>
class XY
{
public:
enum { result_ = Base * XY<Base, Exponent-1>::result_ };
};

//用于終結遞歸的局部特化版
template<int Base>
class XY<Base, 0>
{
public:
enum { result_ = 1 };
};
  模板元編程技術之根本在于遞歸模板實例化。第一個模板實現了一般情況下的遞歸規則。當用一對整數<X, Y>來實例化模板時,模板XY<X, Y>需要計算其result_的值,將同一模板中針對<X, Y-1>實例化所得結果乘以X即可。第二個模板是一個局部特化版本,用于終結遞歸。

  讓我們看看使用此模板來計算5^4 (通過實例化XY<5, 4>)時發生了什么:

// xytest.cpp

#include <iostream>
#include "xy.h"

int main()
{
std::cout << "X^Y<5, 4>::result_ = " << XY<5, 4>::result_;
}
  首先,編譯器實例化XY<5, 4>,它的result_為5 * XY<5, 3>::result_,如此一來,又需要針對<5, 3>實例化同樣的模板,后者又實例化XY<5, 2>…… 當實例化到XY<5, 0>的時候,result_的值被計算為1,至此遞歸結束。

  遞歸模板實例化的深度和終結條件

  可以想象,假如我們以非常大的Y值來實例化類模板XY,那肯定會占用大量的編譯器資源甚至會迅速耗盡可用資源(在計算結果溢出之前),因此,在實踐中我們應該有節制地使用模板元編程技術。

  雖然 C++標準建議的最小實例化深度只有17層,然而大多數編譯器都能夠處理至少幾十層,有些編譯器答應實例化至數百層,更有一些可達數千層,直至資源耗盡。

  假如我們拿掉XY模板局部特化版本,情況會如何?

// xy2.h

//原始摸板
template<int Base, int Exponent>
class XY
{
public:
enum { result_ = Base * XY<Base, Exponent-1>::result_ };
};
  測試程序不變:

// xytest2.cpp

#include <iostream>
#include "xy2.h"

int main()
{
std::cout << "X^Y<5, 4>::result_ = " << XY<5, 4>::result_;
}
  執行如下編譯命令:

C:/>g++ -c xytest2.cpp

  你將會看到遞歸實例化將一直進行下去,直到達到編譯器的極限。

  GNU C++ (MinGW Special) 3.2的默認實例化極限深度為500層,你也可以手工調整實例化深度:

C:/>g++ -ftemplate-depth-3400 -c xytest2.cpp

  事實上,就本例而言,g++ 3.2答應的實例化極限深度還可以再大一些(我的測試結果是不超過3450層)。

  因此,在使用模板元編程技術時,我們總是要給出原始模板的特化版(局部特化版或完全特化版或兼而有之),以作為遞歸模板實例化的終結準則。 利用模板元編程技術解開循環

  模板元編程技術最早的實際應用之一是用于數值計算中的解循環。舉個例子,對一個數組進行求和的常見方法是:

// sumarray.h

template <typename T>
inline T sum_array(int Dim, T* a)
{
T result = T();
for (int i = 0; i < Dim; ++i)
{
result += a[i];
}
return result;
}
  這當然可行,但我們也可以利用模板元編程技術來解開循環:

// sumarray2.h

// 原始模板
template <int Dim, typename T>
class Sumarray
{
public:
static T result(T* a)
{
return a[0] + Sumarray<Dim-1, T>::result(a+1);
}
};

// 作為終結準則的局部特化版
template <typename T>
class Sumarray<1, T>
{
public:
static T result(T* a)
{
return a[0];
}
};
  用法如下:


// sumarraytest2.cpp

#include <iostream>
#include "sumarray2.h"

int main()
{
int a[6] = {1, 2, 3, 4, 5, 6};
std::cout << " Sumarray<6>(a) = " << Sumarray<6, int>::result(a);
}
  當我們計算Sumarray<6, int>::result(a)時,實例化過程如下:

Sumarray<6, int>::result(a)
= a[0] + Sumvector<5, int>::result(a+1)
= a[0] + a[1] + Sumvector<4, int>::result(a+2)
= a[0] + a[1] + a[2] + Sumvector<3, int>::result(a+3)
= a[0] + a[1] + a[2] + a[3] + Sumvector<2, int>::result(a+4)
= a[0] + a[1] + a[2] + a[3] + a[4] + Sumvector<1, int>::result(a+5)
= a[0] + a[1] + a[2] + a[3] + a[4] + a[5]
  可見,循環被展開為a[0] + a[1] + a[2] + a[3] + a[4] + a[5]。這種直截了當的展開運算幾乎總是比循環來得更有效率。

  也許拿一個有著600萬個元素的數組來例證循環開解的優勢可能更有說服力。生成這樣的數組很輕易,有愛好,你不妨測試、對比一下。

 ?。ǜ兄x一位不知名的朋友的測試。他說:“據在Visual C++ 2003上實測編譯器應當進行了尾遞歸優化,可以不受上面說的遞歸層次的限制,然而連加的結果在數組個數達到4796之后就不再正確了,程序輸出了空行,已經出錯” — 2003年12月30日補充) 模板元編程在數值計算程序庫中的應用

  Blitz++之所以“快如閃電”(這正是blitz的字面含義),離不開模板元程序的功勞。Blitz++淋漓盡致地使用了元編程技術,你可以到這些文件源代碼中窺探究竟:

dot.h

matassign.h

matmat.h

matvec.h

metaprog.h

product.h

sum.h

vecassign.h

  讓我們看看Blitz++程序庫dot.h文件中的模板元程序:

template<int N, int I>
class _bz_meta_vectorDot {
public:
enum { loopFlag = (I < N-1) ? 1 : 0 };

template<class T_expr1, class T_expr2>
static inline BZ_PROMOTE(_bz_typename T_expr1::T_numtype, _bz_typename T_expr2::T_numtype)
f(const T_expr1& a, const T_expr2& b)
{
return a[I] * b[I] + _bz_meta_vectorDot<loopFlag * N, loopFlag * (I+1)>::f(a,b);
}

template<class T_expr1, class T_expr2>
static inline BZ_PROMOTE(_bz_typename T_expr1::T_numtype, _bz_typename T_expr2::T_numtype)
f_value_ref(T_expr1 a, const T_expr2& b)
{
return a[I] * b[I] + _bz_meta_vectorDot<loopFlag * N, loopFlag * (I+1)>::f(a,b);
}

template<class T_expr1, class T_expr2>
static inline BZ_PROMOTE(_bz_typename T_expr1::T_numtype, _bz_typename T_expr2::T_numtype)
f_ref_value(const T_expr1& a, T_expr2 b)
{
return a[I] * b[I] + _bz_meta_vectorDot<loopFlag * N, loopFlag * (I+1)>::f(a,b);
}

template<class T_expr1, class P_numtype2>
static inline BZ_PROMOTE(_bz_typename T_expr1::T_numtype, P_numtype2)
dotWithArgs(const T_expr1& a, P_numtype2 i1, P_numtype2 i2=0,
P_numtype2 i3=0, P_numtype2 i4=0, P_numtype2 i5=0, P_numtype2 i6=0,
P_numtype2 i7=0, P_numtype2 i8=0, P_numtype2 i9=0, P_numtype2 i10=0)
{
return a[I] * i1 + _bz_meta_vectorDot<loopFlag * N, loopFlag * (I+1)>::dotWithArgs
(a, i2, i3, i4, i5, i6, i7, i8, i9);
}
};

template<>
class _bz_meta_vectorDot<0,0> {
public:
template<class T_expr1, class T_expr2>
static inline _bz_meta_nullOperand f(const T_expr1&, const T_expr2&)
{ return _bz_meta_nullOperand(); }

template<class T_expr1, class P_numtype2>
static inline _bz_meta_nullOperand
dotWithArgs(const T_expr1& a, P_numtype2 i1, P_numtype2 i2=0,
P_numtype2 i3=0, P_numtype2 i4=0, P_numtype2 i5=0, P_numtype2 i6=0,
P_numtype2 i7=0, P_numtype2 i8=0, P_numtype2 i9=0, P_numtype2 i10=0)
{
return _bz_meta_nullOperand();
}
};
  這段代碼遠比它乍看上去的簡單。_bz_meta_vectorDot類模板使用了一個臨時變量loopFlag來存放每一步循環條件的評估結果,并使用了一個完全特化版作為遞歸終結的條件。需要說明的是,和幾乎所有元程序一樣,這個臨時變量作用發揮于編譯期,并將從運行代碼中優化掉。

  Todd是在Blitz++數值數組庫的主要作者。這個程序庫(以及MTL和POOMA等程序庫)例證了模板元程序可以為我們帶來更加高效的數值計算性能。Todd宣稱Blitz++的性能可以和對應的Fortran程序庫媲美。
Loki程序庫:活用模板元編程技術的典范

  模板元編程的價值僅僅在于高性能數值計算嗎?不僅如此。Loki程序庫以對泛型模式的開創性工作聞名于C++社群。它很巧妙地利用了模板元編程技術實現了Typelist組件。Typelist是實現Abstract Factory、Visitor等泛型模式不可或缺的基礎設施。

  就像C++標準庫組件std::list提供對一組數值的操作一樣,Typelist可以用來操縱一組類型,其定義非常簡單(摘自Loki程序庫Typelist.h單元):

template <class T, class U>
struct Typelist
{
typedef T Head;
typedef U Tail;
};
  顯然,Typelist沒有任何狀態,也未定義任何操作,其作用只在于攜帶類型信息,它并未打算被實例化,因此,對于Typelist的任何處理都必然發生于編譯期而非運行期。

  Typelist可以被無限擴展,因為模板參數可以是任何類型(包括該模板的其他具現體)。例如:

  Typelist<char, Typelist<int, Typelist<float, NullType> > >

  就是一個包含有char、int、float三種類型的Typelist。

  按照Loki的約定,每一個Typelist都必須以NullType結尾。NullType的作用類似于傳統C字符串的“/0”,它被聲明于Loki程序庫的NullType.h文件中:

class NullType;

  NullType只有聲明,沒有定義,因為Loki程序庫永遠都不需要創建一個NullType對象。

  讓我們看看IndexOf模板元程序,它可以在一個Typelist中查找給定類型的位置(摘自Loki程序庫的Typelist.h單元):

template <class TList, class T>
struct IndexOf;

template <class T>
struct IndexOf<NullType, T>
{
enum { value = -1 };
};

template <class T, class Tail>
struct IndexOf<Typelist<T, Tail>, T>
{
enum { value = 0 };
};

template <class Head, class Tail, class T>
struct IndexOf<Typelist<Head, Tail>, T>
{
private:
enum { temp = IndexOf<Tail, T>::value };
public:
enum { value = (temp == -1 ? -1 : 1 + temp) };
};

  IndexOf提供了一個原始模板和三個局部特化版。算法非常簡單:假如TList(就是一個Typelist)是一個NullType,則value為-1。假如TList的頭部就是T,則value為0。否則將IndexOf施行于TList的尾部和T,并將評估結果置于一個臨時變量temp中。假如temp為-1,則value為-1,否則value為1 + temp。

  為了加深你對Typelist采用的模板元編程技術的熟悉,我從Loki程序庫剝離出如下代碼,放入一個typelistlite.h文件中:

// typelistlite.h

// 聲明Nulltype
class NullType;

// Typelist的定義
template <class T, class U>
struct Typelist
{
typedef T Head;
typedef U Tail;
};

// IndexOf的定義

// IndexOf原始模板
template <class TList, class T> struct IndexOf;

// 針對NullType的局部特化版
template <class T>
struct IndexOf<NullType, T>
{
enum { value = -1 };
};

// 針對“Tlist頭部就是我們要查找的T”的局部特化版
template <class T, class Tail>
struct IndexOf<Typelist<T, Tail>, T>
{
enum { value = 0 };
};

// 處理Tlist尾部的局部特化版
template <class Head, class Tail, class T>
struct IndexOf<Typelist<Head, Tail>, T>
{
private:
enum { temp = IndexOf<Tail, T>::value };
public:
enum { value = (temp == -1 ? -1 : 1 + temp) };
};

  測試程序如下:

// typelistlite_test.cpp

#include <iostream>
#include "typelistlite.h"

// 自定義類型Royal
class Royal {};

// 定義一個包含有char、int、Royal和float的Typelist
typedef Typelist<char, Typelist<int, Typelist<Royal, Typelist<float, NullType> > > > CIRF;

int main()
{
std::cout << "IndexOf<CIRF, int>::value = " << IndexOf<CIRF, int>::value << "/n";
std::cout << "IndexOf<CIRF, Royal>::value = " << IndexOf<CIRF, Royal>::value << "/n";
std::cout << "IndexOf<CIRF, double>::value = " << IndexOf<CIRF, double>::value << "/n";
}

  程序輸出如下:


IndexOf<CIRF, int>::value = 1
IndexOf<CIRF, Royal>::value = 2
IndexOf<CIRF, double>::value = -1
  結束語

  模板元編程技術并非都是優點,比方說,模板元程序編譯耗時,帶有模板元程序的程序生成的代碼尺寸要比普通程序的大,而且通常這種程序調試起來也比常規程序困難得多。另外,對于一些程序員來說,以類模板的方式描述算法也許有點抽象。

  編譯耗時的代價換來的是卓越的運行期性能。通常來說,一個有意義的程序的運行次數(或服役時間)總是遠遠超過編譯次數(或編譯時間)。為程序的用戶帶來更好的體驗,或者為性能要求嚴格的數值計算換取更高的性能,值得程序員付出這樣的代價。

  很難想象模板元編程技術會成為每一個普通程序員的日常工具,相反,就像Blitz++和Loki那樣,模板元程序幾乎總是應該被封裝在一個程序庫的內部。對于庫的用戶來說,它應該是透明的。模板元程序可以(也應該)用作常規模板代碼的內核,為要害的算法實現更好的性能,或者為非凡的目的實現非凡的效果。

  模板元編程技術首次正式亮相于Todd Veldhuizen的Using C++ Template Metaprograms論文之中。這篇文章首先發表于1995年5月的C++ Report期刊上,后來Stanley Lippman編輯C++ Gems一書時又收錄了它。參考文獻中給出了這篇文章的鏈接,它還描述了許多本文沒有描述到的內容。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产日韩在线免费| 亚洲男人第一av网站| 欧美乱大交做爰xxxⅹ性3| 成人欧美在线视频| 一区二区在线视频| 成人444kkkk在线观看| 欧美自拍大量在线观看| 日本亚洲欧美成人| 97人人做人人爱| 色综合男人天堂| 日韩在线欧美在线国产在线| 亚洲综合精品一区二区| 国产在线拍揄自揄视频不卡99| 欧美日本啪啪无遮挡网站| 中文字幕日韩欧美精品在线观看| 欧美韩日一区二区| 亚洲欧美制服中文字幕| 国产视频亚洲精品| 日韩电影中文字幕一区| 欧美日韩免费观看中文| 亚洲a级在线观看| 国产精品美女www爽爽爽视频| 亚洲一区美女视频在线观看免费| 久久久久久久国产精品视频| 国产91精品久久久久久| 国产精品美女久久久久久免费| 精品国模在线视频| 亚洲人成毛片在线播放| 精品国产欧美一区二区五十路| 欧美孕妇性xx| 久久久亚洲国产| 国产欧美一区二区三区视频| 久久精品91久久香蕉加勒比| 97视频com| xxx成人少妇69| 91av视频在线免费观看| 在线免费观看羞羞视频一区二区| 欧美老女人xx| 91视频免费在线| 97免费在线视频| 69久久夜色精品国产7777| 亚洲二区在线播放视频| 亚洲精品久久久久久久久久久| 欧美午夜丰满在线18影院| 在线观看欧美www| 欧美精品videos性欧美| 欧美精品国产精品日韩精品| 亚洲精品网址在线观看| 91av视频在线免费观看| 97国产真实伦对白精彩视频8| 亚洲女人被黑人巨大进入al| 亚洲欧美制服中文字幕| 国产一区二区三区三区在线观看| 国产精品老牛影院在线观看| 国产在线观看精品一区二区三区| 国产精品第1页| 日韩中文av在线| 亚洲福利视频在线| 国产精品观看在线亚洲人成网| 国产午夜精品视频| 日韩av123| 中文字幕久热精品在线视频| 国产69精品久久久久9999| 91色琪琪电影亚洲精品久久| 精品在线观看国产| 深夜福利日韩在线看| 欧美日韩国产丝袜美女| 国产欧美日韩精品专区| 日韩一中文字幕| 69视频在线免费观看| 欧美一级免费看| 国产亚洲在线播放| xxxxx91麻豆| 青青草成人在线| 国产精品一二三在线| 久久天堂电影网| 中文字幕成人在线| 久久久久久久久91| 亚洲第一男人av| 久久深夜福利免费观看| www.国产精品一二区| 欧美精品久久久久久久久| 欧美人成在线视频| 97视频在线观看网址| 亚洲欧美日韩高清| 日本韩国欧美精品大片卡二| 日韩电影免费观看在线| 在线观看欧美视频| 欧美高清理论片| 成人国产精品一区二区| 国产精品88a∨| 精品magnet| 精品亚洲一区二区| 色妞一区二区三区| 亚洲人成网站色ww在线| 欧美乱大交做爰xxxⅹ性3| 国产精品777| 亚洲国产精品嫩草影院久久| 国产精品久久久久秋霞鲁丝| 日韩有码视频在线| 国产一区二区香蕉| 欧美激情精品久久久久| 国产成人精品免费视频| 亚洲第一免费播放区| 亚洲最大的成人网| 亚洲国模精品一区| 成人免费网站在线| 久久久之久亚州精品露出| 久久精视频免费在线久久完整在线看| 亚洲精品欧美一区二区三区| 黑人欧美xxxx| 国产一区二区三区丝袜| 午夜精品一区二区三区av| 国产欧美在线播放| 91嫩草在线视频| 欧美色xxxx| 91精品国产自产在线老师啪| 中文字幕一区日韩电影| 日本不卡视频在线播放| 色老头一区二区三区| 永久555www成人免费| 欧美性极品少妇精品网站| 伊人青青综合网站| 亚洲天堂免费在线| 97超碰蝌蚪网人人做人人爽| 狠狠躁夜夜躁人人爽天天天天97| 欧美又大粗又爽又黄大片视频| 有码中文亚洲精品| 中文字幕精品一区二区精品| 久久精品国产免费观看| 高清一区二区三区四区五区| 国产精品久久久久77777| 91国产高清在线| 97人人爽人人喊人人模波多| 亚洲免费一在线| 欧美激情久久久久久| 热久久美女精品天天吊色| 日韩av在线免费观看一区| 日韩视频在线一区| 亚洲系列中文字幕| 国产主播欧美精品| 一道本无吗dⅴd在线播放一区| 国产91精品久| www高清在线视频日韩欧美| 高清一区二区三区日本久| 久久99亚洲热视| 综合136福利视频在线| 国产精品第一页在线| 91禁外国网站| 国产日韩精品入口| 亚洲国产精品成人va在线观看| 92版电视剧仙鹤神针在线观看| 亚洲第一男人av| 日韩av不卡在线| 91产国在线观看动作片喷水| 欧美丰满少妇xxxxx做受| 欧美高清视频免费观看| 成人福利视频在线观看| 1769国内精品视频在线播放| 国产精品极品美女在线观看免费| 国产精品国产自产拍高清av水多| 中文字幕欧美日韩va免费视频| 国产精品免费福利| 久久视频中文字幕|