2011-01-05 69 views
2

我的代碼旨在管理聯網客戶端和服務器上的操作,因爲兩者之間存在重大的重疊。但是,這裏和那裏有一些功能是專門由客戶端或服務器調用的,並且在服務器上意外地調用客戶端功能(反之亦然)是錯誤的重要來源。如何將代碼自動添加到C++中的衍生函數中

爲了減少這些類型的編程錯誤,我試圖給函數添加標籤,以便在錯誤使用時會引發騷動。我目前的解決方案是在每個函數的開始處有一個簡單的宏,如果客戶端或服務器訪問它們不應該訪問的成員,則調用assert。然而,當有多個派生實例的類時,這會遇到問題,因爲我必須在每個子類中將實現標記爲客戶端或服務器端。

我想要做的就是在基類中的虛擬成員簽名中放置一個標籤,這樣我只需標記一次即可,而不會因爲忘記重複執行而出現錯誤。我曾考慮在基類實現中進行檢查,然後用base :: functionName之類的方式來引用它,但只要需要手動將函數調用添加到每個實現中,就會遇到同樣的問題。理想情況下,我可以像默認構造函數一樣自動調用父級版本。

有人知道如何在C++中實現這樣的東西嗎?我應該考慮另一種方法嗎?

謝謝!

+4

聽起來像你應該有不同的代碼和/或類爲服務器/客戶端 – Falmarri 2011-01-05 01:04:09

+2

爲什麼不把通用代碼分解成基類,並從它們派生一個服務器類和客戶端類? – Puppy 2011-01-05 01:12:09

回答

5

另一種方法可能會覆蓋一個比一個不同的方法,您的呼叫者實際調用:

class Base { 
public: 
    void doit(const Something &); 
protected: 
    virtual void real_doit(const Something &); 
}; 

class Derived: public Base { 
protected: 
    virtual void real_doit(const Something &); 
}; 

Base::doit()實施能做的檢查,以確保它被稱爲在合適的環境,然後調用虛擬real_doit()函數。派生類將覆蓋受保護的虛函數,任何一個類的用戶都將無法調用受保護的函數。

Base::doit()函數是不是虛擬的,所以派生類不會無意中覆蓋錯誤的。 (人們可以嘗試,但希望他們很快會注意到,當它不叫。)

+1

+1:這被稱爲*非虛擬接口習語*,並且只允許執行一次前置條件和後置條件。 – 2011-01-05 07:51:06

+0

謝謝。這似乎是我的目的最乾淨,破壞性最小的方法。此外,感謝Matthieu發佈成語的名稱。雖然我以前見過並使用過它,但我並沒有意識到這是一種標準方法。 – Ian 2011-01-05 19:13:04

3

你提出的是非常複雜的。這聽起來像一個簡單的解決方案將是

class CommonStuff { 
    // all common code that anybody can safely call 
}; 

class ServerBase : public CommonStuff { 
    // only what the server is allowed to call; can safely be overwritten 
}; 

class ClientBase : public CommonStuff { 
    // only what the client is allowed to call; can safely be overwritten 
}; 

編譯時強制執行比任何種類的運行時執行的要好得多。

+0

+1:即使我提出了Greg的答案,因爲NVI是一個很好的習慣用法,但我並不真正理解一個人如何能夠進入這樣一種情況,即一個班級擁有永遠不應該被調用的方法......這是一種代碼味道就我而言。 – 2011-01-05 07:52:27

1

在沒有重新設計類的情況下,你所要求的語言(我知道)沒有辦法。最簡單的解決方案可能是具有不聲明服務器功能的接口(純虛擬)類和不聲明客戶端函數的接口類,並且讓您的合併代碼從兩個接口繼承(公開)。然後在您的客戶端程序中,使用一個參考(或指針)到Client接口,該接口不允許訪問未在Client接口中聲明的任何方法。在服務器上,使用Server接口。

這也允許您使用派生類,如ServerClient

1

我會考慮把這個庫分成三個庫:一個基本的庫,它包含了所有的東西,一個服務器庫和一個客戶端庫。只要客戶端不使用服務器庫,就很好。你可能會添加一些額外的類(類Processor可能分裂成BaseProcessorClientProcessor,並且ServerProcessor,其中每個子類都有基本沒有一個附加功能。)

如果這是行不通的,你能把服務器/客戶端檢查放在類的構造函數中,並在那裏調用斷言? (這隻適用於服務器專用或客戶專用的服務器,而不適用於該方法。)

如果這樣做不起作用,那麼實際編譯不同版本的庫會有什麼意義,基於它是服務器還是客戶端版本?將方法及其聲明與#ifdef SERVERBUILD#ifdef CLIENTBUILD環繞,幷包含一些檢查以確保它們都未定義(#if defined(SERVERBUILD) && defined(CLIENTBUILD)#error Can't define both!)。

0

我投了Greg Hewgill的回答,但它讓我想到了添加「方面」的方法,例如您的要求。我在這裏用自己的命名約定(類Base和方法doit):

class Base { 
protected: 
    class Aspect { 
    public: 
     Aspect(int x) { 
      std::cout << "aspect" << std::endl; 
     } 
    }; 
public: 
    virtual void doit(const Something &arg, const Aspect hook = 0) 
    { 
     std::cout << "doit(" << arg << ")" << std::endl; 
    } 
}; 

呼叫者只能說base.doit(arg)因爲Aspect是默認參數。它的構造函數在doit之前運行,並且它的析構函數(未繪製)在之後運行。可悲的是,我的第一個想法是使默認參數hook = this不被允許。

孩子可以覆蓋doit具有相同的簽名,並獲得相同的效果。

+0

我想我明白你的意思了。因此,如果客戶端試圖調用它,那麼您將擁有一個「ServerOnly」方面類,該類將在其講師中聲明一個斷言? – Ian 2011-01-05 01:54:04

+0

我真的很喜歡這種方法,如果我定義一個像「#define SERVER_ONLY_RESTRICTION const RestrictServerOnly _hook = 0」這樣的宏,它會應用默認參數,給我看起來像「virtual void doit(const Something &arg,SERVER_ONLY_RESTRICTION);「但我應該注意的一點是,該方面需要被保護而不是私人的,否則子類無法從基本功能派生出來! – Ian 2011-01-05 02:32:20

+0

我剛剛意識到Aspect類只能訪問具有全局範圍的信息,當我需要它可以訪問基類的成員時。我想不出一種給予嵌套類訪問的方法,因爲 - 正如你已經說過的 - 「hook = this」不允許作爲默認參數。 – Ian 2011-01-05 02:53:51

相關問題