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

隨筆-5  評(píng)論-33  文章-0  trackbacks-0
為了更好的討論這個(gè)問題,假設(shè)我們要設(shè)計(jì)一個(gè)關(guān)于people的class,并進(jìn)行如下約定:
1. age 小于 0 的baby不在我們的考慮范圍之內(nèi).
2. age 大于 200 的people我們認(rèn)為是不存在的.
即,關(guān)于people的age,我們考慮的范圍是[0 , 200]
基于以上假設(shè),我們?cè)O(shè)計(jì)如下一個(gè)class,這里為了簡(jiǎn)化代碼,突出問題的主體,將部分成員函數(shù)及成員變量去掉.

class People
{
    
public:
        
explicit People( int iAge )
        : m_iAge( iAge )
        
{
        
        }

}
;

看起來很簡(jiǎn)單,下一步我們可以使用這個(gè)class了.

People janice( 28 );
janice.Sing();

如果類似于上面這樣以合法的參數(shù)構(gòu)造一個(gè)People,那么后續(xù)的操作也在我們的可控范圍之內(nèi),也是理想的情況.
但是如果我們以非法的參數(shù)構(gòu)造People并進(jìn)行相關(guān)的函數(shù)調(diào)用,如下:

People hebe( -1 );
hebe.Sing();

People ella( 
201 );
ella.Dance();


那么這樣實(shí)在是太糟糕了,因?yàn)樵谡{(diào)用Sing()和Dancing()時(shí)我們甚至不知道調(diào)用的對(duì)象的內(nèi)部狀態(tài)是非法的,這也就成為bug的一個(gè)源泉.

總結(jié)一下上面的例子中遇到的問題:在以特定的參數(shù)構(gòu)造一個(gè)對(duì)象時(shí),如果參數(shù)非法或構(gòu)造失敗,我們應(yīng)當(dāng)向調(diào)用者反饋這一信息.

對(duì)于一般的成員函數(shù),如果能夠有返回值,那么我們可以通過返回值來標(biāo)識(shí)傳遞給函數(shù)的參數(shù)非法或內(nèi)部運(yùn)行失敗這種情況.
但是對(duì)于構(gòu)造函數(shù),因?yàn)樗荒苡蟹祷刂担裕覀儽仨毷褂闷渌姆椒▉硐蛘{(diào)用者反饋"傳遞給構(gòu)造函數(shù)的參數(shù)非法或構(gòu)造失敗".

針對(duì)于這一問題有三種解決方案:

第一種方案:在構(gòu)造函數(shù)的參數(shù)中傳遞一個(gè)額外的參數(shù)用于標(biāo)識(shí)構(gòu)造是否成功.
在這種方案的指導(dǎo)下,代碼如下:

class People
{
    
public:
        People( 
int iAge , bool &bInitStatus )
        : m_iAge( iAge )
        
{
            
if( ( iAge < 0 ) || ( iAge > 200 ) )
            
{
                bInitStatus 
= false;
            }

            
else
            
{
                bInitStatus 
= true;
            }

        }

}
;


然后我們可以這樣使用:

bool bInitStatus = false;
People hebe( 
-1 , bInitStatus );
iffalse == bInitStatus )
{
    
// handle error
}

else
{
    hebe.Sing();
}


這種方法是可行的,但是代碼看起來過于丑陋且不夠直觀,這里只是作為一種方案提出并不推薦使用.

第二種方案:使用兩段構(gòu)造的形式.
所謂的兩段構(gòu)造是指一個(gè)對(duì)象的內(nèi)部狀態(tài)的初始化分兩步完成,將構(gòu)造函數(shù)中的部分初始化操作轉(zhuǎn)移到一個(gè)輔助初始化的成員函數(shù)中:
第一步是通過構(gòu)造函數(shù)來完成部分內(nèi)部狀態(tài)的初始化.
第二步是通過類似于 Initialize 之類的成員函數(shù)來完成對(duì)象內(nèi)部狀態(tài)的最終初始化.

兩段構(gòu)造的形式在 MFC 中廣泛使用.在MFC中我們經(jīng)常看到類似于 Initialize , Create 之類的函數(shù).
基于兩段構(gòu)造的形式,代碼如下:

class People
{
    
public:
        People( 
void )
        : m_iAge( INVALID_AGE )
        
{
        
        }

        
        
bool Initialize( int iAge )
        
{
            
if( ( iAge < 0 ) || ( iAge > 200 ) )
            
{
                
return false;
            }

            
else
            
{
                m_iAge 
= iAge;
                
return true;
            }
            
        }

}
;


在這種情況下,我們應(yīng)當(dāng)這樣來使用People:

People hebe;
const bool bStatus = hebe.Initialize( 20 );
iffalse == bInitStatus )
{
    
// handle error
}

else
{
    hebe.Sing();
}


這種方案似乎比第一種方案更優(yōu),但是仍有一個(gè)潛在的問題:對(duì)象是以兩步構(gòu)造完成的.
第一步構(gòu)造是由構(gòu)造函數(shù)來完的,OK,這一點(diǎn)我們不用擔(dān)心,編譯器幫我們保證.
但是第二步是由類似于 Initialize 之類的成員函數(shù)來完成的,如果我們?cè)跇?gòu)造一個(gè)People對(duì)象之后忘記了調(diào)用 Initialize ,
那么這個(gè)對(duì)象的內(nèi)部狀態(tài)仍然是非法的,后續(xù)的操作也將由此引發(fā)bug.這也是"兩段構(gòu)造"這種形式受到詬病的原因之一.
另一方面,"兩段構(gòu)造"的形式與C++的"RAII",Resource Acquisition Is Initialization(資源獲取即初始化),這一原則相違背.
因?yàn)橐?兩段構(gòu)造"這種形式設(shè)計(jì)的class People 在構(gòu)造一個(gè)對(duì)象時(shí),它的內(nèi)部狀態(tài)實(shí)際上并沒有完全初始化,我們需要調(diào)用 Initialize 來輔助完成最終的初始化.
所以,盡管"兩段構(gòu)造"這種方案可以解決我們所遇到的"對(duì)構(gòu)造函數(shù)參數(shù)非法進(jìn)行反饋"這個(gè)問題,但是這種方案并不夠優(yōu)雅.

但是為什么MFC會(huì)先擇"兩段構(gòu)造"這種形式呢,因?yàn)樵贑++發(fā)展的初期,當(dāng)異常機(jī)制還不是足夠成熟,沒有得到廣泛的認(rèn)可和使用時(shí),
MFC中選擇兩段構(gòu)造或許也是情理之中的,也許還有其它的原因,類似的類庫還有ACE...

當(dāng)然,在某些情況下,使用兩段構(gòu)造也有其獨(dú)到的好處.
下面所設(shè)計(jì)的場(chǎng)景可能有一些牽強(qiáng),但只是為了力求簡(jiǎn)單并能夠說明問題.(代碼進(jìn)行了大量簡(jiǎn)化)

class Server
{
    
public:
        Server(
void)
        
{
            
// allocate a huge chunk of memory to store data
        }

        
        
~Server( void )
        
{
            
// free all the used resource
        }

}
;


然后在我們的系統(tǒng)中,我們需要使用一個(gè) server pool , 在系統(tǒng)啟動(dòng)時(shí),我們需要 server pool 中有 100 個(gè) Server 可用.

Server serverPool[ 100 ];


在系統(tǒng)負(fù)載最大的時(shí)候,假定100個(gè)Server可以勝任,但是在大多數(shù)情況下,我們只需要少量的Server即可以完成任務(wù).
在這種情況下: Server serverPool[ 100 ]; 將會(huì)消耗大量的資源(而且大部分資源我們并不會(huì)使用),這是我們不愿意接受的.
之所以出現(xiàn)這種情況,因?yàn)槲覀冊(cè)跇?gòu)造函數(shù)中分配了大量資源,這種分配是隨構(gòu)造函數(shù)的調(diào)用而自動(dòng)完成的.


這時(shí),如果我們使用"兩段構(gòu)造"的方法就能在一定的程度上解決這個(gè)問題.

class Server
{
    
public:
        Server(
void)
        
{
            
// do nothing here.
        }

        
        
bool Initialize( void )
        
{
            
// allocate a huge chunk of memory to store data
        }

        
        
~Server( void )
        
{
            
// free all the used resource
        }

}
;


在這種情況下: Server serverPool[ 100 ]; 的開銷就很小了,我們可以很好地控制對(duì)系統(tǒng)資源的使用,而不會(huì)浪費(fèi).
當(dāng)然,當(dāng)我們從 serverPool 中獲取一個(gè) Server 對(duì)象時(shí),我們要調(diào)用 Initialize 進(jìn)行最終的初始化操作.


第三種方案:使用異常
即是當(dāng)用于構(gòu)造 People 對(duì)象的參數(shù)非法時(shí),我們選擇在構(gòu)造函數(shù)中拋出一個(gè)異常來反饋給調(diào)用者"參數(shù)非法,構(gòu)造失敗"的相關(guān)信息.

class People
{
    
public:
        
explicit People( int iAge ) throw( std::invalid_arguement )
        : m_iAge( iAge )
        
{
            
if( ( iAge < 0 ) || ( iAge > 200 ) )
            
{
                
throw std::invalid_arguement( "invalid argument" );
            }

        }

}
;


那么我們可以這樣使用:

try
{
    People hebe( 
20 );
    hebe.Sing();
}

catchconst std::invalid_arguement &refExcept )
{
    
// handle exception here.
}


這種方案似乎是最優(yōu)的:符合RAII原則,也符合B.S等一批老大推行的"現(xiàn)代C++程序設(shè)計(jì)風(fēng)格".

但是很多在開發(fā)一線上的同學(xué)們都反對(duì)在代碼中使用異常,實(shí)際上我也不愿意在代碼中使用異常,
至少不愿意看到類似于java代碼中那樣鋪天蓋地的"throw try catch".
我對(duì)異常的使用也僅僅是局限在類似于那些適合"用異常來代替兩段構(gòu)造"的場(chǎng)景中,對(duì)于其它的情況,
我更愿意用返回錯(cuò)誤碼來標(biāo)識(shí)函數(shù)內(nèi)部的運(yùn)行狀態(tài),而不是通過拋出異常的形式來告知調(diào)用者.


C++規(guī)定:如果執(zhí)行構(gòu)造函數(shù)的過程中產(chǎn)生異常,那么這個(gè)未被成功構(gòu)造的對(duì)象的析構(gòu)函數(shù)將不會(huì)被調(diào)用.
這一點(diǎn)在很大程度上為我們?cè)跇?gòu)造函數(shù)中拋出異常的安全性提供了C++語言級(jí)的保證,當(dāng)然,其它的安全性需要我們自己保證.

對(duì)于"向調(diào)用者反饋構(gòu)造函數(shù)參數(shù)非法或構(gòu)造失敗的相關(guān)信息"這個(gè)問題,"基于異常"和"基于兩段構(gòu)造"這兩種方案我都使用過一段時(shí)間,
目的是確定對(duì)于自己而言到底哪一種方案用起來更舒服更適合自己.最終的結(jié)果是我選擇了"基于異常"這種形式.

對(duì)于"基于異常"和"基于兩段構(gòu)造",沒有哪一種能在所有的情況下都是最優(yōu)的解決方案,印證了那句名言"there is no silver bullet".
如何在不同的場(chǎng)景中選擇其一作為最優(yōu)的解決方案是我們?cè)谠O(shè)計(jì)時(shí)需要權(quán)衡的問題.


個(gè)人愚見,錯(cuò)漏之處還請(qǐng)指正,歡迎大家踴躍發(fā)言:)

posted on 2010-03-04 21:23 luckycat 閱讀(2820) 評(píng)論(18)  編輯 收藏 引用 所屬分類: C++

評(píng)論:
# re: 設(shè)計(jì)的兩難:選擇異常還是兩段構(gòu)造 2010-03-04 22:03 | Corner Zhang
呵呵!如果你要傳統(tǒng)的編程風(fēng)格,就用Initialize() Shutdown()的“兩段構(gòu)造”;若要趨向于捕捉異常的try catch的風(fēng)格,就用構(gòu)造器上的異常處理唄!

具體取舍,看項(xiàng)目人員本身素質(zhì),按我的經(jīng)驗(yàn),傳統(tǒng)風(fēng)格的代碼易于調(diào)試跟蹤,錯(cuò)誤就在附近。  回復(fù)  更多評(píng)論
  
# re: 設(shè)計(jì)的兩難:選擇異常還是兩段構(gòu)造 2010-03-04 22:22 | qiaojie
兩種方法都是錯(cuò)誤的用法,這地方應(yīng)該使用斷言而不是異常或者錯(cuò)誤返回值。傳入錯(cuò)誤的參數(shù)值是程序的BUG而不是真正的異常,異常是由于外部運(yùn)行環(huán)境的錯(cuò)誤引起而非程序本身的BUG(例如,內(nèi)存耗盡,網(wǎng)絡(luò)錯(cuò)誤,文件錯(cuò)誤等等),異常處理只應(yīng)該用在真正屬于異常的情況。當(dāng)然,有的時(shí)候People的age參數(shù)來自于用戶的輸入,這個(gè)時(shí)候也不應(yīng)該使用異常,而是在用戶輸入完成,對(duì)話框結(jié)束,構(gòu)造People之前,加入一個(gè)ValidateUserInput()函數(shù)來校驗(yàn)用戶輸入,如果age屬于非法值,彈出一個(gè)錯(cuò)誤對(duì)話框向用戶說明錯(cuò)誤的原因。  回復(fù)  更多評(píng)論
  
# re: 設(shè)計(jì)的兩難:選擇異常還是兩段構(gòu)造 2010-03-04 22:32 | luckycat
@Corner Zhang
關(guān)于這一點(diǎn),我們的觀點(diǎn)還是很相近的,不同的編程風(fēng)格決定了不同的設(shè)計(jì)取舍以及代碼風(fēng)格,
沒有哪一種一直都是最優(yōu)的,選擇一種適合自己的并一直堅(jiān)持下去(當(dāng)然,適當(dāng)?shù)臅r(shí)候還是要變通一下).
實(shí)際上我并不愿意在代碼中大量使用"throw try catch",所以,基本上我不愿意去看java代碼,
就如你所說的,傳統(tǒng)的代碼風(fēng)格易于跟蹤調(diào)試;在我看來,傳統(tǒng)的"基于錯(cuò)誤碼"的代碼比基于"使用異常"的代碼
要緊湊得多,因?yàn)槲覀兛梢栽阱e(cuò)誤發(fā)生的地方立即處理錯(cuò)誤,而不像"基于異常"的代碼中我們要向下跨越N行代碼
來進(jìn)行錯(cuò)誤處理(這一點(diǎn)使得代碼的可讀性很差).
而且,如果try代碼塊中太大,那么在對(duì)應(yīng)的catch塊中盡管我們可以進(jìn)行相應(yīng)的異常處理,但是此時(shí)我們卻失去了
對(duì)發(fā)生錯(cuò)誤的代碼上下文的必要了解.這一點(diǎn)使得我們降低了對(duì)代碼整體運(yùn)行流程的可預(yù)知性,
更重要的是也降低了錯(cuò)誤處理的針對(duì)性,因?yàn)橥环N類型的異常可能由try代碼塊中的多個(gè)地方throw.具體是哪一個(gè)throw的無從了解.
so,我的觀點(diǎn)是:讓構(gòu)造函數(shù)盡量簡(jiǎn)單,減少誤用的可能性,并增加構(gòu)造函數(shù)的安全性(盡量減少構(gòu)造函數(shù)構(gòu)造失敗的可能性).
這樣我們也就能在一定程度上減少對(duì)異常機(jī)制的依賴.至于其它的可帶有返回值的成員函數(shù)都使用"返回錯(cuò)誤碼"來取代"拋出異常".
  回復(fù)  更多評(píng)論
  
# re: 設(shè)計(jì)的兩難:選擇異常還是兩段構(gòu)造 2010-03-04 23:25 | luckycat
@qiaojie
呵呵,可不能一棒子打死啊.
至于說,類似于"用戶輸入非法數(shù)據(jù)"之類的問題到底是算作錯(cuò)誤還是異常情況,這一點(diǎn)依賴每個(gè)人對(duì)同一個(gè)事物的認(rèn)知,
有的人認(rèn)為這是異常情況,有的人認(rèn)為這是錯(cuò)誤,這個(gè)認(rèn)知層面上的問題我們先不討論,尊重每個(gè)人的看法.
實(shí)際上,即使存在上面的認(rèn)知差異也沒有關(guān)系,因?yàn)閱栴}的本質(zhì)是對(duì)"用戶輸入非法數(shù)據(jù)"這種異常也好,錯(cuò)誤也好,
我們?cè)诖a邏輯中應(yīng)該如何處理,你覺得應(yīng)該用類似于"對(duì)話框+ValidateUserInput"之類的方法來處理,
我覺得可以通過返回錯(cuò)誤碼或拋出異常的形式來做處理. 本質(zhì)上都是在處理一種"非正常情況",只是我們的處理方式不同,
你說應(yīng)該用你的方法比較好,我覺得用我的方法處理也是可行的,到底用哪一種呢,
即使在這種情況下,我們還是很難選擇一個(gè)所有人都接受的處理方式. 這里就涉及到設(shè)計(jì)的權(quán)衡和取舍了,有很多種方法都可行,我們尊重每個(gè)人在特定的環(huán)境中所做出的選擇.

/*
"而是在用戶輸入完成,對(duì)話框結(jié)束,構(gòu)造People之前,加入一個(gè)ValidateUserInput()函數(shù)來校驗(yàn)用戶輸入,
如果age屬于非法值,彈出一個(gè)錯(cuò)誤對(duì)話框向用戶說明錯(cuò)誤的原因"
*/
你這里只是對(duì)一種特例的處理,實(shí)際上我們很難在所有的情況都保證傳入構(gòu)造函數(shù)的參數(shù)是合法的,要是我們真的找到了這樣一種方法,
那么"there is a silver bullet !" , 接下來,對(duì)于所有奮斗在開發(fā)一線的同學(xué)們而言,生活就要美好很多,應(yīng)該再也不會(huì)發(fā)生類似于"小貝"的悲劇了:)

在你處理的特例中,既然我們能夠保證傳入構(gòu)造函數(shù)的參數(shù)一定是合法的,那確實(shí)太好了,"使用異常"和"兩段構(gòu)造"都是多余的.
對(duì)于那種我們不能確保傳入構(gòu)造函數(shù)的參數(shù)是一定是合法的情況,我們?cè)撨x擇哪種處理方式呢,這是這篇文章討論的根本問題.
如果因?yàn)闃?gòu)造函數(shù)的參數(shù)不合法,或者因?yàn)槠渌脑驑?gòu)造失敗,最基本的一點(diǎn),我們應(yīng)當(dāng)讓調(diào)用者知道這一情況,
至于調(diào)用者如何處理就不在我們關(guān)心的范圍之內(nèi)了,是"彈出對(duì)話框告知用戶重試","忽略這個(gè)錯(cuò)誤",還是直接"abort",不同的場(chǎng)景下也有不用的選擇.
我們要做的就是在"構(gòu)造一個(gè)對(duì)象發(fā)生異常時(shí)"告知調(diào)用者"發(fā)生了非正常情況".

這篇文章的主題也就是討論"在構(gòu)造發(fā)生非正常情況時(shí)采取何種方式來告知調(diào)用者這一情況".
對(duì)于這個(gè)問題很難有一個(gè)"放之四海而皆準(zhǔn)"的處理方案,因?yàn)檫@涉及到不同的編程風(fēng)格,應(yīng)用場(chǎng)景和設(shè)計(jì)時(shí)的取舍.

不過我們還是可以踴躍地發(fā)表自己的看法,在討論和交流的過程中我們總能發(fā)現(xiàn)思維的閃光點(diǎn).互相學(xué)習(xí):)  回復(fù)  更多評(píng)論
  
# re: 設(shè)計(jì)的兩難:選擇異常還是兩段構(gòu)造 2010-03-05 08:34 | 飯中淹
我一般會(huì)用一種類似工廠的方法。

class CPeople
{

int m_iAge;

CPeople() : m_iAge(-1) {}
~CPeople() {}
bool _Init( int iAge )
{
if( iAge <= 0 || iAge > 200 )return false;
m_iAge = iAge;
return true;
}
public:
static CPeople * Create( int iAge )
{
CPeople * people = new CPeople();
if( people != NULL &&
!people->_Init(iAge) )
{
people->Release();
people = NULL;
}
return people;
}
void Release() { delete this;}
};


私有的構(gòu)造函數(shù)和西狗函數(shù)確保他們不能被單獨(dú)分配和刪除
所有的途徑只有create和release。

  回復(fù)  更多評(píng)論
  
# re: 設(shè)計(jì)的兩難:選擇異常還是兩段構(gòu)造 2010-03-05 09:04 | LOGOS
@飯中淹
agree
這正是我想說的。另外,在一些情形下構(gòu)造函數(shù)不易調(diào)試,而兩段構(gòu)造則能避開這一調(diào)試,選擇更好的調(diào)試的init  回復(fù)  更多評(píng)論
  
# re: 設(shè)計(jì)的兩難:選擇異常還是兩段構(gòu)造 2010-03-05 10:31 | 肥仔
@飯中淹
每個(gè)對(duì)象都要顯示的create和release。那基本上等價(jià)于放棄了C++構(gòu)造函數(shù)和析構(gòu)函數(shù)這兩個(gè)特性。
對(duì)于棧對(duì)象,這樣會(huì)很累,也很容易泄漏。  回復(fù)  更多評(píng)論
  
# re: 設(shè)計(jì)的兩難:選擇異常還是兩段構(gòu)造 2010-03-05 12:02 | luckycat
@飯中淹
將"兩段構(gòu)造"與"Fatcory Pattern"結(jié)合起來確實(shí)是一種巧妙的設(shè)計(jì)!
內(nèi)部實(shí)現(xiàn)上還是"兩段構(gòu)造",但是對(duì)于 class 的用戶而言,class CPeople 展現(xiàn)的卻是一個(gè)單一的"構(gòu)造接口",
用戶一旦調(diào)用這個(gè)接口"構(gòu)造對(duì)象",那么"兩段構(gòu)造"自動(dòng)完成,極大地減少了"兩段構(gòu)造"中因?yàn)橥浾{(diào)用"Initialize"所帶來的問題.
class CPeople 中的 Create 和 Release 所扮演的角色類似于"構(gòu)造函數(shù)和析構(gòu)函數(shù)",都是進(jìn)行資源的分配與回收操作.
單純從"資源管理"的角度來說,肯定是"構(gòu)造函數(shù)和析構(gòu)函數(shù)"相比如"Create 和 Release"更優(yōu)一些,
因?yàn)?quot;構(gòu)造函數(shù)和析構(gòu)函數(shù)"對(duì)于"非動(dòng)態(tài)分配的對(duì)象以及非placement new方式生成的對(duì)象",
構(gòu)造和析構(gòu)都會(huì)由編譯器保證正確自動(dòng)地調(diào)用,大大簡(jiǎn)化了對(duì)資源的管理,或許這也是C++設(shè)計(jì)構(gòu)造和析構(gòu)的出發(fā)點(diǎn)之一.

在"兩段構(gòu)造" & "Fatcory Pattern"這種模式下,所有的CPeople對(duì)象將都由 Create 接口創(chuàng)建,這勢(shì)必需要我們管理大量的動(dòng)態(tài)分配的對(duì)象,
在這種情況下,如果稍有不慎,我們將面臨"resource leak"的問題.這個(gè)時(shí)候如果我們能將動(dòng)態(tài)分配的CPeople對(duì)象用一種更方便安全的方式來管理就更好了,
于是我想到了boost::shared_ptr,不知道大家想到了什么?
類似于下面這樣:

void FreeResource( CPeople *pPeople )
{
if( NULL != pPeople )
{
pPeople -> Release();
}
}

CPeople *pHebe = CPeople::Create( 2 );
if( NULL == pHebe )
{
// handle error
}

boost::shared_ptr< CPeople > pPeople( pHebe , FreeResource );

下面我們就可以使用 pPeople 這個(gè)智能指針"do whatever you want" :) ,而且使用起來直觀方便:
pPeople -> Sing();
也減少了對(duì)動(dòng)態(tài)分配資源進(jìn)行管理的復(fù)雜度.



  回復(fù)  更多評(píng)論
  
# re: 設(shè)計(jì)的兩難:選擇異常還是兩段構(gòu)造 2010-03-05 17:36 | 望見
為了程序的健壯性,多余的操作也是必須的。  回復(fù)  更多評(píng)論
  
# re: 設(shè)計(jì)的兩難:選擇異常還是兩段構(gòu)造 2010-03-05 18:00 | qiaojie
@luckycat
你的根本問題在于你沒理解異常的使用哲學(xué),在錯(cuò)誤的前提下去談什么設(shè)計(jì)的選擇根本就是一件毫無意義的事情。你如果要堅(jiān)持自己的錯(cuò)誤認(rèn)識(shí),去談什么尊重每個(gè)人的選擇,那我只能自認(rèn)是對(duì)牛彈琴了。

再來看為什么我說要用ValidateUserInput而不是其他什么方法,因?yàn)轵?yàn)證用戶的輸入通常來說都是一個(gè)復(fù)雜的且經(jīng)常可變的需求,我們假設(shè)Person的構(gòu)造參數(shù)里有一個(gè)UserName,通常UserName的驗(yàn)證會(huì)比較復(fù)雜,有長(zhǎng)度限制,非法字符限制,重名限制等等,對(duì)于重名限制往往還要去用戶管理器或者后臺(tái)數(shù)據(jù)庫查詢一下,現(xiàn)在來看看把這些驗(yàn)證邏輯都寫到Person的構(gòu)造函數(shù)里是多么傻X的做法啊,首先,Person的構(gòu)造函數(shù)需要依賴用戶管理器或者后臺(tái)數(shù)據(jù)庫才能驗(yàn)證UserName的合法性。其次,當(dāng)你的構(gòu)造函數(shù)返回了錯(cuò)誤值或者異常的時(shí)候,外部的處理代碼卻根本不知道為什么這個(gè)用戶名是非法的,所以要么還得再寫一遍驗(yàn)證邏輯來檢查哪里非法,要么直接告訴用戶輸入非法,讓用戶摸不著頭腦。
  回復(fù)  更多評(píng)論
  
# re: 設(shè)計(jì)的兩難:選擇異常還是兩段構(gòu)造 2010-03-05 19:33 | luckycat
@qiaojie
也許你的異常哲學(xué)是正確的并值得大家學(xué)習(xí),還請(qǐng)你發(fā)文一篇讓大家有一個(gè)學(xué)習(xí)的機(jī)會(huì),
如果從你的文章中我確實(shí)發(fā)現(xiàn)了自己的錯(cuò)誤,也會(huì)從中有所改正,當(dāng)然,你也不需要"對(duì)牛彈琴"這個(gè)詞語.
我這篇文章中的"People"只是一個(gè)用于作為討論基礎(chǔ)的例子,根本的問題是對(duì)于"構(gòu)造函數(shù)的參數(shù)非法或是構(gòu)造失敗"時(shí),我們應(yīng)當(dāng)如果告知調(diào)用者.
我并沒有說一定要把參數(shù)的合法性全部放在構(gòu)造函數(shù)中完成,但是在構(gòu)造函數(shù)中檢查參數(shù)的合法性是應(yīng)該的,
就像上面的同學(xué)說的"為了程序的健壯性,多余的操作也是必須的"。
在這個(gè)例子中,你可以往自己熟悉的GUI方向進(jìn)行特化,所以你可以使用"對(duì)話框"之類的工具來進(jìn)行傳入構(gòu)造函數(shù)之前的參數(shù)合法性檢驗(yàn)以及進(jìn)行相關(guān)的錯(cuò)誤處理,
但是在那些"非GUI"的領(lǐng)域,在那些"我們不能確保傳入構(gòu)造函數(shù)的參數(shù)一定是合法的,不能保證構(gòu)造函數(shù)一定會(huì)構(gòu)造成功"的情況下,我們到底該如何處理,
我考慮到可以使用"基于異常"或"基于兩段構(gòu)造的形式".
C++提供的異常機(jī)制是一種工具,可以作為"函數(shù)內(nèi)部向函數(shù)的調(diào)用者傳遞函數(shù)內(nèi)部非正常運(yùn)行狀態(tài)"的一種方法.
就如同你說的"內(nèi)存耗盡,網(wǎng)絡(luò)錯(cuò)誤,文件錯(cuò)誤"這種情況下是異常,也許這種情況下我們應(yīng)當(dāng)使用"異常機(jī)制"(希望沒有理解錯(cuò)).
但是如果一個(gè)函數(shù)內(nèi)部可能出現(xiàn)"內(nèi)存耗盡"也會(huì)出現(xiàn)"參數(shù)非法的問題"(再重申一遍,我們不能永遠(yuǎn)都保證傳入每一個(gè)函數(shù)的參數(shù)都是合法的).
"內(nèi)存耗盡"這種情況我們使用異常,但是"參數(shù)非法問題"我們使用什么呢,
按照你的看法,"參數(shù)非法"不屬于異常的范圍之內(nèi),我們不應(yīng)該使用"異常的形式",但我們還是要告知用戶"參數(shù)非法"的信息,
假定這里我們"無法使用類似于彈出對(duì)話框的形式來告知用戶參數(shù)非法",那么我可以想到的告知調(diào)用者這一信息的方式是"使用錯(cuò)誤碼",
當(dāng)然,我們還可以選擇"errno"的形式.
這樣一來,我們就面臨一個(gè)問題"一個(gè)函數(shù)會(huì)以異常和錯(cuò)誤碼兩種方式來告知調(diào)用者相關(guān)的非正常運(yùn)行信息",
接下來,調(diào)用者就要同時(shí)使用"try catch"和檢查函數(shù)的錯(cuò)誤碼兩種方式來檢查函數(shù)的運(yùn)行狀態(tài),
我覺得如果真的這樣設(shè)計(jì)函數(shù)的話,這就是一種很糟糕的設(shè)計(jì),不知道你怎么認(rèn)為.

在告知調(diào)用者一個(gè)函數(shù)內(nèi)部的"非正常狀態(tài)"時(shí),我只會(huì)擇優(yōu)使用"錯(cuò)誤碼"或"異常這兩種形式"之一,不會(huì)同時(shí)使用.
基于這一點(diǎn),如果我選擇"以錯(cuò)誤碼的形式"來反饋給調(diào)用者,那么在函數(shù)內(nèi)部"網(wǎng)絡(luò)錯(cuò)誤"時(shí)我也會(huì)使用錯(cuò)誤碼來告知調(diào)用者(按你的看法,這種情況應(yīng)該使用異常),
如果我選擇"基于異常"的形式,那對(duì)"參數(shù)非法"的信息我也會(huì)拋出"std::invalid_arguement".這是設(shè)計(jì)上的取舍產(chǎn)生的必然選擇.


說到這里,不知道你對(duì)于作為std異常類型之一的"std::invalid_arguement"這個(gè)詞語有什么感想,
我覺得你應(yīng)該向標(biāo)準(zhǔn)委員會(huì)指明"std::invalid_arguement"這個(gè)詞語,
"從使用異常的哲學(xué)上的角度上來看這個(gè)概念是錯(cuò)誤的,因?yàn)閰?shù)非法根本就不是異常,我們又怎么能因?yàn)閰?shù)的非法而throw std::invalid_arguement,
這是在誤導(dǎo)廣大的std用戶,所以必須去掉".  回復(fù)  更多評(píng)論
  
# re: 設(shè)計(jì)的兩難:選擇異常還是兩段構(gòu)造 2010-03-05 21:56 | qiaojie
@luckycat
保證參數(shù)的正確性是調(diào)用者的責(zé)任,而不是被調(diào)用的函數(shù)的責(zé)任,如果你對(duì)此有疑問的話可以去閱讀一下契約式編程。當(dāng)然,我并沒有說不需要去檢查參數(shù)的合法性,而是強(qiáng)調(diào)應(yīng)該用assert(0 < age && age < 200);這樣的斷言來檢查參數(shù)合法性,這個(gè)為什么強(qiáng)調(diào)不要用異常或者錯(cuò)誤返回值,是因?yàn)槊つ康拇罅渴褂眠@類錯(cuò)誤處理機(jī)制會(huì)導(dǎo)致整個(gè)項(xiàng)目變得混亂,如果任意一個(gè)帶參數(shù)的函數(shù)都會(huì)因?yàn)閰?shù)非法而拋異常,那么我在外面接到異常的時(shí)候會(huì)非常困惑,到底該如何正確處理那么多可能出現(xiàn)的異常?如果是用錯(cuò)誤返回值的話就更麻煩,每個(gè)函數(shù)調(diào)用都要進(jìn)行檢查,代碼及其繁瑣。錯(cuò)誤處理機(jī)制是整個(gè)軟件設(shè)計(jì)的重要環(huán)節(jié),他不像接口設(shè)計(jì)那么顯而易見,所以在設(shè)計(jì)上更應(yīng)該小心規(guī)劃合理使用,在
哪里會(huì)出異常,哪里該接異常應(yīng)該做到心中有數(shù)正確處理,否則就會(huì)陷入混亂。
當(dāng)然,凡事無絕對(duì),我說的這種使用方式并不適用于組件級(jí)或者系統(tǒng)級(jí)程序,系統(tǒng)級(jí)程序必須要采用防御性編程策略來檢測(cè)參數(shù)的合法性并向調(diào)用者報(bào)告錯(cuò)誤(因?yàn)闊o法預(yù)期調(diào)用者如何調(diào)用函數(shù)),這常需要付出代價(jià),常導(dǎo)致系統(tǒng)級(jí)提供的API接口跟應(yīng)用程序之間阻抗適配,需要在應(yīng)用程序內(nèi)進(jìn)行適當(dāng)?shù)姆庋b。而你的例子顯然不屬于這類程序。

另外你拿std::invalid_arguement出來我覺得毫無意義,C++標(biāo)準(zhǔn)里不成熟不完善甚至很爛的東西多了去,說上三天三夜也說不完。C++的異常機(jī)制尤其不完善,備受爭(zhēng)議。像std::invalid_arguement基本沒人會(huì)去用。  回復(fù)  更多評(píng)論
  
# re: 設(shè)計(jì)的兩難:選擇異常還是兩段構(gòu)造 2010-03-05 22:40 | luckycat
@qiaojie
謝謝賜教!

"像std::invalid_arguement基本沒人會(huì)去用"這句話說得有點(diǎn)絕對(duì)了,用的人應(yīng)該還是有一些的,可能我們沒有接觸到.
另外,我們總是被"天朝"代表,想不到這次被 qiaojie 代表了:)

你說"保證參數(shù)的正確性是調(diào)用者的責(zé)任,而不是被調(diào)用的函數(shù)的責(zé)任",
這一點(diǎn)我也同意,不過我覺得作為函數(shù)的設(shè)計(jì)者,我們不應(yīng)當(dāng)對(duì)用戶所傳遞的參數(shù)有太多理想化的假設(shè),所以我們應(yīng)當(dāng)在函數(shù)中進(jìn)行參數(shù)合法性的檢查,
一方面,可以在函數(shù)的入口處盡早發(fā)現(xiàn)非法參數(shù)的問題,這樣就不至于后續(xù)會(huì)使用錯(cuò)誤的參數(shù)在函數(shù)中進(jìn)行一些無意義的操作.
另一方面,在函數(shù)入口處檢查參數(shù)的合法性,可以增強(qiáng)函數(shù)的健壯性,進(jìn)一步增強(qiáng)系統(tǒng)的健壯性.
舉個(gè)例子,如果傳遞給函數(shù)的實(shí)參不應(yīng)該是NULL指針,用戶卻以NULL作為實(shí)參調(diào)用函數(shù),假設(shè)我們沒有進(jìn)行對(duì)應(yīng)參數(shù)合法性檢查,
那么后續(xù)基于這個(gè)NULL實(shí)參的操作可能會(huì)導(dǎo)致系統(tǒng)"coredump".

對(duì)于參數(shù)的合法性檢查,在debug版本和release版本下應(yīng)該都需要進(jìn)行,類似于"assert(0 < age && age < 200);"這種檢測(cè)參數(shù)的合法性的代碼只在debug版本下可以起作用,
在release版本下就不起用了,也就不能在release版本下作為參數(shù)合法性檢查的工具.
在debug版本下,如果assert斷言失敗,那么我們可以看到對(duì)應(yīng)的abort信息,然后程序異常退出.
實(shí)際上這樣做可能有的時(shí)候并不合適,因?yàn)樵谝恍┣闆r下,僅僅是參數(shù)非法,我們可以進(jìn)行相應(yīng)的處理而不需要系統(tǒng)因此而退出運(yùn)行.

"強(qiáng)調(diào)不要用異常或者錯(cuò)誤返回值,是因?yàn)槊つ康拇罅渴褂眠@類錯(cuò)誤處理機(jī)制會(huì)導(dǎo)致整個(gè)項(xiàng)目變得混亂"
這句話如果僅僅是理論上來探討"如何讓系統(tǒng)設(shè)計(jì)的更優(yōu)雅",那么這無疑可以作為一個(gè)"系統(tǒng)設(shè)計(jì)準(zhǔn)則",
但是在實(shí)際的開發(fā)過程中,有的時(shí)候一個(gè)函數(shù)內(nèi)部出現(xiàn)"非正常情況"的可能性實(shí)在是太多了,我們必須要進(jìn)行相應(yīng)的處理.
如果我們既不使用"異常"也不使用"返回錯(cuò)誤碼"的形式來告知調(diào)用者,
那么在反饋給調(diào)用者"函數(shù)內(nèi)部出現(xiàn)非正常情況"這一點(diǎn)上我們將"無能為力",但我們又必須在這一點(diǎn)有所作為.

在大多數(shù)情況下,"異常"和"錯(cuò)誤碼"可能是我們僅有的兩個(gè)選擇方案,如何選擇其一作為最終的處理方案,
甚至如何在不使用"異常"和"錯(cuò)誤碼"的前提下也達(dá)到相同的效果,這是一件很"糾結(jié)"的事情.


追求系統(tǒng)在架構(gòu)和代碼設(shè)計(jì)上的完美是開發(fā)者的一個(gè)方向,但是有時(shí)我們需要考慮"追求完美的代價(jià)",
在時(shí)間,人力以及成本的多重影響下,很多時(shí)候我們必須放棄對(duì)最優(yōu)方案的探索,而選擇一種"不那么完美但是可行,可以很好解決問題"的方案.
也許這個(gè)時(shí)候作為函數(shù)調(diào)用狀態(tài)反饋的"異常"和"錯(cuò)誤碼"機(jī)制會(huì)在我們的思考和運(yùn)用范圍之內(nèi).


  回復(fù)  更多評(píng)論
  
# re: 設(shè)計(jì)的兩難:選擇異常還是兩段構(gòu)造 2010-03-06 08:55 | chentan
qiaojie 討論很精彩
我的習(xí)慣是 模塊邊界會(huì)檢查參數(shù)合法性,并報(bào)告給調(diào)用者
模塊內(nèi)部的代碼盡量多的用assert  回復(fù)  更多評(píng)論
  
# re: 設(shè)計(jì)的兩難:選擇異常還是兩段構(gòu)造 2010-03-06 21:06 | 陳梓瀚(vczh)
對(duì)于類庫的設(shè)計(jì)者,其實(shí)有一個(gè)很簡(jiǎn)單的判斷標(biāo)準(zhǔn)。

1:如果一個(gè)輸入錯(cuò)誤,調(diào)用者必須知道并處理,那么就采用“不處理就崩潰”方法,迫使調(diào)用者處理。在C++里面的唯一方法就是使用異常了。舉個(gè)例子,對(duì)一個(gè)容器的下標(biāo)訪問越界,這個(gè)時(shí)候operator[]不可能返回一個(gè)可用的返回值,而且也不能用錯(cuò)誤信息去污染返回值,譬如返回pair<bool, T>,因此直接拋出異常。

2:如果一個(gè)構(gòu)造函數(shù)發(fā)生錯(cuò)誤之后,這個(gè)對(duì)象是絕對(duì)不能被碰的,那么采用異常。因?yàn)樵趖ry-catch結(jié)構(gòu)里面,構(gòu)造函數(shù)完蛋了,那么那個(gè)正在被構(gòu)造的對(duì)象你是沒有辦法獲取的。

3:異常可以用來在大量函數(shù)互相遞歸的時(shí)候(譬如說語法分析器)迅速跳到最外層,此處作為控制代碼流程而使用。

這里我也接受一個(gè)例外,譬如說2,如果構(gòu)造函數(shù)發(fā)生錯(cuò)誤但是我并不想立刻拋出異常(因?yàn)橛行顟B(tài)在構(gòu)造函數(shù)里面想知道也比較麻煩),那么除了需要一個(gè)類似bool IsAvailable()const函數(shù)來告訴你以外,所有該類成員的非法操作(譬如說在錯(cuò)誤的構(gòu)造狀態(tài)下調(diào)用一個(gè)成員函數(shù))都必須拋出異常,或者【絕對(duì)不產(chǎn)生任何副作用】的同時(shí)給一個(gè)返回值說明調(diào)用失敗。  回復(fù)  更多評(píng)論
  
# re: 設(shè)計(jì)的兩難:選擇異常還是兩段構(gòu)造 2010-03-06 21:27 | luckycat
@陳梓瀚(vczh)
你列舉的判斷標(biāo)準(zhǔn)都值得借鑒,不過你后續(xù)補(bǔ)充的對(duì)"例外情況"的處理方式不敢茍同:
因?yàn)闃?gòu)造可能沒有成功,那么我們需要調(diào)用IsAvailable之類的函數(shù),甚至于后續(xù)需要因?yàn)榕袛嘀皹?gòu)造函數(shù)的狀態(tài)來
對(duì)調(diào)用的每個(gè)成員函數(shù)進(jìn)行"try catch"或者還要從"每個(gè)成員函數(shù)的返回值中來判斷之前的構(gòu)造操作是否成功".
這種設(shè)計(jì)是可行的,但對(duì)類的使用者來說太復(fù)雜.
這種情況下我覺得使用"兩段構(gòu)造"可能更好一些,我們只需要判斷"兩段構(gòu)造"是否成功即可,如果構(gòu)造成功,在后續(xù)的成員函數(shù)調(diào)用過程中,
就再也不用為了確認(rèn)構(gòu)造函數(shù)的狀態(tài)來對(duì)每個(gè)被調(diào)用的成員函數(shù)進(jìn)行"try catch"或檢查返回值的操作,這樣的設(shè)計(jì)應(yīng)該更簡(jiǎn)潔一些.  回復(fù)  更多評(píng)論
  
# re: 設(shè)計(jì)的兩難:選擇異常還是兩段構(gòu)造 2010-03-08 01:28 | 陳梓瀚(vczh)
@luckycat
兩段構(gòu)造的話,你的類的其他成員函數(shù)也應(yīng)該在構(gòu)造不成功的時(shí)候拋出異常,并沒有任何區(qū)別,我的例外還減少了別人可能忘記第二段構(gòu)造的概率。

當(dāng)然這是對(duì)一個(gè)庫的要求,應(yīng)用程序不一定要如此嚴(yán)格。  回復(fù)  更多評(píng)論
  
# re: 設(shè)計(jì)的兩難:選擇異常還是兩段構(gòu)造 2015-01-27 23:09 | tsgsz
拋異常不析構(gòu)可以令所有的成員變量都是 std::unique_ptr  回復(fù)  更多評(píng)論
  

只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。
網(wǎng)站導(dǎo)航: 博客園   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>
            欧美国产精品日韩| 午夜一区在线| 亚洲大胆av| 欧美一区二区三区四区在线观看地址 | 欧美视频专区一二在线观看| 亚洲私人影吧| 欧美影院在线播放| 夜夜嗨网站十八久久| 亚洲欧美国产va在线影院| 黄色成人精品网站| 中文欧美在线视频| 一区二区免费看| 久久久久网址| 日韩一级二级三级| 午夜在线精品| 99精品国产福利在线观看免费 | 女人天堂亚洲aⅴ在线观看| 欧美国产一区二区三区激情无套| 亚洲欧美国产三级| 欧美凹凸一区二区三区视频| 久久99在线观看| 欧美日韩在线不卡一区| 美女尤物久久精品| 国产婷婷成人久久av免费高清 | 噜噜噜在线观看免费视频日韩| 欧美人在线观看| 欧美国产视频在线观看| 亚洲欧美综合v| 美女性感视频久久久| 久久精品国产精品亚洲精品| 欧美日韩另类字幕中文| 欧美黄色成人网| 伊人久久成人| 久久午夜精品一区二区| 久久综合福利| 影视先锋久久| 久久久久久久久久久久久9999| 亚洲欧美中文另类| 国产精品区一区二区三区| 一区二区三区不卡视频在线观看 | 欧美日韩精品系列| 欧美激情免费观看| 亚洲精美视频| 欧美日韩国产麻豆| 艳女tv在线观看国产一区| 99精品视频免费观看视频| 欧美日韩精品一区视频| 亚洲一区二区三区激情| 欧美一区二区三区喷汁尤物| 国产精品综合视频| 久久九九国产| 亚洲人成毛片在线播放女女| 99精品国产福利在线观看免费 | 黄网站色欧美视频| 欧美jjzz| 一区二区三区免费在线观看| 国产精品www994| 久久综合狠狠综合久久激情| 在线高清一区| 欧美日韩在线三级| 久久九九国产精品怡红院| 亚洲电影欧美电影有声小说| 国产精品99久久久久久www| 国产一区深夜福利| 欧美日韩国产精品 | 麻豆免费精品视频| 亚洲在线视频观看| 亚洲日本久久| 国内自拍亚洲| 国产精品剧情在线亚洲| 欧美成人69av| 久久国产精品99精品国产| 亚洲精品中文字| 久久一二三国产| 午夜精品网站| 亚洲图片欧洲图片日韩av| 1024亚洲| 国产日产精品一区二区三区四区的观看方式| 久久久久久91香蕉国产| 亚洲综合日韩中文字幕v在线| 亚洲电影在线播放| 欧美顶级艳妇交换群宴| 欧美在线网址| 午夜在线a亚洲v天堂网2018| 一本久久青青| 一本色道久久精品| 亚洲视频电影图片偷拍一区| 91久久久久久| 亚洲精品久久久久中文字幕欢迎你 | 日韩视频中午一区| 亚洲经典一区| 91久久一区二区| 亚洲人成网站在线播| 有码中文亚洲精品| 亚洲国产日韩一区| 一本色道88久久加勒比精品| 日韩午夜精品| 亚洲一区日韩在线| 午夜视频在线观看一区二区| 欧美一区二区大片| 久久亚洲一区二区三区四区| 久久综合色天天久久综合图片| 免费成人性网站| 亚洲国产美女| 亚洲一区久久久| 久久九九国产精品| 欧美精品亚洲一区二区在线播放| 欧美日产一区二区三区在线观看 | 国产一区二区三区黄| 激情欧美一区二区| 欧美日韩二区三区| 久热这里只精品99re8久| 久久久久久久综合| 欧美精品二区| 国产精品永久免费视频| 影音先锋一区| 欧美视频一区二区三区四区| 国产日韩精品电影| 久久精品视频亚洲| 欧美成人在线免费观看| 一区二区三区国产精华| 久久精品在这里| 欧美视频在线观看一区二区| 一区精品久久| 亚洲欧美一区二区三区在线| 美女精品一区| 午夜欧美电影在线观看| 免费在线播放第一区高清av| 国产精品毛片| 亚洲日本成人| 亚洲欧美国产精品va在线观看| 欧美大片在线影院| 欧美一级黄色网| 国产精品日本精品| 一区二区久久| 亚洲激情成人| 免费成人性网站| 国语自产精品视频在线看8查询8| 午夜在线一区二区| 亚洲视频一区二区在线观看| 欧美久久久久久久久久| 亚洲精品久久久久| 亚洲第一毛片| 欧美大片18| 99精品视频免费全部在线| 亚洲国产综合在线| 欧美极品色图| 亚洲无亚洲人成网站77777| 亚洲日本成人女熟在线观看| 美女视频网站黄色亚洲| 亚洲精品免费电影| 亚洲人屁股眼子交8| 欧美日韩久久精品| 午夜视频在线观看一区二区| 亚洲主播在线| 韩国精品主播一区二区在线观看| 久久视频免费观看| 久久综合中文| 99re热精品| 亚洲一区二区少妇| 黄色国产精品| 亚洲日本中文字幕免费在线不卡| 欧美日韩1080p| 性色一区二区三区| 久久久亚洲精品一区二区三区| 亚洲国产精品va在看黑人| 亚洲三级观看| 国产一区二区三区不卡在线观看 | 中文一区字幕| 久久爱另类一区二区小说| 91久久精品国产91久久性色tv| 亚洲免费电影在线| 亚洲区在线播放| 亚洲第一在线视频| 欧美99在线视频观看| 狠狠久久婷婷| 日韩视频在线免费| 欧美激情久久久久| 国产一区二区无遮挡| 免费久久99精品国产| 国产精品大全| 亚洲国产欧美一区二区三区久久| 国产精品你懂的在线欣赏| 欧美黑人在线播放| 国产日韩欧美综合精品| 日韩系列在线| 亚洲精品视频免费观看| 欧美一区永久视频免费观看| 亚洲欧美成人一区二区三区| 欧美在线黄色| 欧美在线中文字幕| 国产精品日韩欧美大师| 亚洲美女中文字幕| 99视频精品| 欧美国产日韩a欧美在线观看| 麻豆成人小视频| 在线观看欧美日韩| 久久青草久久| 欧美99在线视频观看| 禁久久精品乱码|