2013-06-25 104 views
1

我對給定項目的目標是查找和解析特定的串行數據包。好消息是,已經編寫了一個通用的數據包類來處理大部分繁重的工作。但是,我想提高班級的表現,如下所示。請原諒我,如果一些語法稍微偏離,我從來都不是擅長記憶C++從內存中的語法... :(C++繼承/類設計問題

class GenericPacket { 
public: 
    GenericPacket(); // does nothing except initialize member variables 
    ~GenericPacket(); 
    GenericPacket(const GenericPacket& other); 
    GenericPacket& operator=(const GenericPacket& other); 
    GenericPacket(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock); 
    Parse(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock); 
    // "get" functions go here... 
    // ... 

protected: 
    // the functions below are called by Parse() 
    ParseHeader(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock); 
    ParseData(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock); 
    ParseCheckSum(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock); 

private: 
    // member variables go here... 

類的基本功能是在得到了數據流中讀取排隊並將其處理到數據包的各個組成部分中,並檢查它從隊列中剝離出來的數據包的有效性。還有兩個構造函數和相應的「解析」函數,這些函數與調用者未修改的情況相關在構造過程中的隊列以及另一個使用簡單數組而不是隊列的版本,它們都只是上面顯示的Parse()函數調用的包裝器,另外請注意,調用者可以使用默認的構造函數並手動調用Parse ,或者調用將要嘗試的非默認構造函數通過用第一個解析數據包的數據填充成員變量來使對象「有用」。另外請注意,這個類不會對在ParseData調用中找到的數據進行解碼。它只是將原始十六進制存儲在uint8_t數組中。

現在,有了這個背景信息,我有當前的問題尋找一個高度特定的數據包,佔MAYBE所有流量的2%。此外,我希望有能力解碼數據,這將增加更多的成員變量。事情是這樣的:

class HighlySpecificPacket { 
public: 
    HighlySpecificPacket(); 
    // non-default constructor that calls parse 
    HighlySpecificPacket(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock, int* status); 
    ~HighlySpecificPacket(); 
    // copy constructor and the like... 
    // ... 
    Parse(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock, int* status); 
    // ... 
private: 
    double real_data; 
    // etc... 

基本上,HighlySpecificPacket類的parse()函數可以利用許多在GenericPacket的保護功能。它可以使用這些函數,但在ParseHeader()注意到該數據包沒有我特別尋找的數據包的簽名時停止處理。此外,可以使用int *狀態向調用方發回可以有效忽略的字節數(從而避免生產者線程不必要地推入隊列)。另外,(我應該在前面提到過),GenericPacket中的受保護函數應該並且需要從用戶中抽象出來,因爲它們總是被GenericPacket的Parse()調用。

總的來說,我失去了前進的最佳方式。我不希望GenericPacket的所有功能都暴露給客戶端(即非線程安全的構造函數),但由於基類中的受保護函數,我可以通過繼承重用大量代碼。我有能力根據需要修改GenericPacket類。對我來說,這顯然是一種「是 - 一種」關係,但是我失去了如何實現這一點的機制。我想使用私人繼承,但我曾多次被告知,這是不好的實踐,只是一個創可貼的解決方案。

總的來說,我被卡住,有以下幾個問題(請原諒,如果這些是因爲我最後一次主動使用繼承是在我回到了學校noobish ...):

  1. 有什麼最好的方式去做這件事?如果我使用構圖,則無法訪問我想要重用的功能。但使用私有繼承被稱爲hacky,尤其是因爲我將不得不手動編寫包裝函數來公開我希望客戶端使用的基類部分(即「getter」函數)...

  2. 有沒有辦法阻止dynamic_casts,或攔截他們?在這種情況下,從派生類轉換到基類是很有意義的。但是,只有在頭部匹配特定數據包的簽名時,纔會從基類轉換爲派生類。

  3. 包含一個構造函數將基類作爲派生類的參數是否合理?這有沒有特別的名字?這是我在想什麼:DerivedClass & DerivedClass(const BaseClass & base);基本上,我會檢查標頭簽名,然後只有在標頭簽名與特定數據包的情況匹配的情況下才能完成構造。

  4. 既然我通過詢問從基礎到派生的鑄造和構造函數來打開一堆蠕蟲......那麼關於等式/不等式/賦值運算符等呢?我是否必須編寫特定的每個案例來檢查派生與基礎?例如,我可以,如果所有的基類元素做這樣的事情的時候是一樣的返回「真」:

    如果(基地==派生)

    什麼是這樣的:

    derived = base; // take in all elements from base and attempt to construct it as a specific case of the base class 
    
  5. 我是否甚至需要擔心衍生類的複製構造函數/賦值運算符,如果它只有double/ints /等。 (沒有指針/動態內存)?由於動態分配內存,基類具有複製構造函數和賦值運算符。不是默認的派生拷貝構造函數只是調用基礎拷貝構造函數?

感謝您的幫助。我知道我的帖子很有吸引力,所以我很感激耐心。我在想,除了我在學校學到的Shape,Rectangle,Square,Circle等等的例子之外,我還偶然發現了第一個「真實」的案例。再次感謝。

編輯,以增加對淨度:

這就是爲什麼我要訪問ParseHeader()等函數:

在GenericPacket:

Parse(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock) { 
    ParseHeader(data_stream, the_lock); 
    ParseData(data_stream, the_lock); // generic, only an array of hex 
    ParseCRC(data_stream, the_lock); //determines validity 
} 

在HighlySpecificPacket:

Parse(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock, int* status) { 
    ParseHeader(data_stream, the_lock); 
    // do checks here to see if the packet is actually the kind I want 
    if (header_ == WHAT_I_WANT) { 
    ParseData(data_stream, the_lock); 
    ParseCRC(data_stream, the_lock); 
    } else { 
    *status = header_.packet_length_; // number of bytes to ignore. 
    } 
} 
+0

有趣的問題。你確定你需要**指針和引用不會完成這項工作嗎? – 2013-06-25 06:29:13

+0

我的團隊的風格指導不喜歡非const指針,而且我正在搞東西,所以我使用指針作爲結果。 –

+0

「我想使用私有繼承」 - 爲什麼? HighlySpecificPacket是一種GenericPacket並不是什麼祕密。請參閱http://stackoverflow.com/a/656235/544557 –

回答

0
class GenericPacket 
{ 
public: 
    static std::shared_ptr<GenericPacket> fromStream(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock) 
    { 
     // extract the header and return std::make_shared<MySpecificPacket>(...) where you've chosen MySpecificPacket accordingly 
    } 

    bool parse(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock) 
    { 
     return this->parse_(data_stream, the_lock); 
    } 

protected: 
    // override this method for specific implementations of parse, can also be made abstract if there is no default behaviour 
    virtual bool parse_(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock); 
}; 

class MySpecificPacket: public GenericPacket 
{ 
protected: 
    bool parse_(std::queue<uint8_t>* data_stream, pthread_mutex_t* the_lock) 
    { 
     // do something specific, including decoding the data if you need to 
    } 
}; 

和客戶端(生產者)類如何使用這樣的:

std::shared<GenericPacket> packet = GenericPacket::fromStream(stream, lock); 
if (packet->parse(stream, lock)) 
{ 
    // push onto event queue... 
} 
0

由於評論中的精彩討論,以下是一個建議的解決方案我正在考慮實施,僅僅打了我,而我在淋浴(順便說一句,其他人發現,最好的想法是在淋浴做了什麼?):

namespace packets { 
ParseHeader(...); 
ParseData(...); 
ParseCheckSum(...); 

class GenericPacket { 
    // same stuff as before, except the scope of the "protected" functions... 
}; 

class HighlySpecificPacket { 
    // same stuff as before... 
    // new stuff: 
public: 
    // will probably have to add wrappers here to expose 
    // some GenericPacket member variables... 
private: 
    GenericPacket packet; // composition for all the necessary member variables 

我認爲這是一個好辦法只公開我想要的東西給客戶端,除了我現在必須讓他們直接訪問ParseHeader/ParseData/ParseChecksum函數這一事實,這是我希望避免的(這也是爲什麼它們最初是受保護的。另外,如果我公開這些函數,我仍然可以使用合成,但是我的問題仍然是我想讓HighlySpecificPacket訪問這些函數,而不讓HighlySpecificPacket的用戶訪問它們。

這種方法的優點是,它可以減輕dynamic_cast的大量影響,並將一種類型等同於我遇到的另一個問題......