[英]C++ - Good or bad practice?
我正面臨着這樣的情況:我發現在基類中存儲對象的類型(作為枚舉)以進一步將指向該基類的指針轉換為子類指針(取決於該類型的值)。
例如 :
class CToken
{
public:
token_type_e eType;
};
class COperatorToken : public CToken
{
public:
// Sub class specific stuff
};
class CLiteralToken : public CToken
{
public:
// Sub class specific stuff
};
接着
vector<CToken *> aTokens;
//...
for( size_t nI = 0, nMaxI = aTokens.size(); nI < nMaxI; ++nI )
{
switch( aTokens[ nI ]->eType )
{
case E_OPERATOR :
// Do something with sub-class specific stuff.
break;
case E_LITERAL :
// Do something with sub-class specific stuff.
break;
}
}
這是一種不好的做法嗎?
謝謝 :)
編輯:
說我正在分析我的令牌列表。 在某些時候,我想要檢查當前令牌是否是一個操作員,正如人們所建議的那樣,使用虛擬函數virtual bool isOperator()
。 現在,如果它是一個運算符,我將要訪問該子類特定的東西,以找出,例如,它是哪種類型的運算符。 在那種情況下,我該怎么辦? 我無法在我的基類中添加方法getOperatorType(),這是沒有意義的。 除了轉換為子類以檢索該子類成員值之外,還有其他方法嗎?
類型字段確實破壞了C ++的面向對象特性。 通常你可以用多態來解決這個問題:
在CToken
定義一個函數virtual doSomething()
,帶有適當的參數和返回類型。
在COperatorToken
和CLiteralToken
實現該功能。
如果你使用aTokens[ nI ]->doSomething();
運行時將調用相應的函數aTokens[ nI ]->doSomething();
聽起來像是在嘗試模擬代數數據類型。 通常,您不會這樣做,而是通過在基類上放置純virtual
函數並在派生類的覆蓋中實現實際行為。
此外,如果您確實需要您提出的模式,語言運行庫會知道類型,因此您無需存儲它:
#include <iostream>
#include <typeinfo>
class Token { };
class Operator : public Token { };
class Literal : public Token { };
int main()
{
Token *tok = new Literal();
if (typeid(*tok) == typeid(Literal)) {
std::cout << "got a literal\n";
}
else if (typeid(*tok) == typeid(Operator)) {
std::cout << "got an operator\n";
}
}
我會問一個不同的問題:你為什么要重新發明輪子而不是使用現有的可能性,即虛擬成員(多態)? 因此,如果沒有一些強有力的理由這樣做,我會稱之為不好的做法。
您可以重載虛擬成員,即使您的指針是基類,您仍然會調用實際(子)類的成員(請注意,您通常也需要一個虛擬析構函數,但我正在跳過此為簡單起見):
class Token {
public:
virtual void somethingSpecial() {
std::cout << "Hello!" << std::endl;
}
}
class Literal : public Token {
public:
virtual void somethingSpecial() {
std::cout << "I'm a literal!" << std::endl;
}
}
class Operator : public Token {
public:
virtual void somethingSpecial() {
std::cout << "I'm an operator!" << std::endl;
}
}
然后,在迭代中,您可以執行以下簡單操作:
std::vector<Token*> tokens;
tokens.push_back(new Literal());
tokens.push_back(new Operator());
tokens.push_back(new Literal());
for (std::vector<Token*>:iterator a = tokens.begin(); a != tokens.end(); ++a)
a->somethingSpecial();
結果將類似於您的代碼。 正在運行的實際代碼將基於子類中的實際實現。 在這種情況下,您最終得到以下輸出:
我是字面意思!
我是運營商!
我是字面意思!
如果一個子類沒有實現虛函數,那么將調用基類的版本(除非你把它作為抽象;然后它將是必須的)。
你可以這樣做:
class CToken {
public:
virtual bool isLiteral(void) const = 0;
//...
};
並在兩個子類上定義它。
然后,如果你想使用運算符或文字的事實,解決方案是聲明一個getValue()
函數,但絕不使用switch(...)
。 事實上,如果你想要去認為Obect導向的做法,你應該creeate在虛擬函數CToken
這允許子類自動解釋自己,運營商為運營商和值值。
你永遠不應該失敗 - 除非你需要。
一個簡單的例子就是在框架周圍傳遞消息。 假設每個消息類型都必須是可序列化的,因此基類接口將提供由被調用的類覆蓋的虛擬“serialise()”方法,並且消息傳遞框架將對所有類型的所有消息進行多態處理。
然而,這些消息可能代表什么,因此它們擁有的屬性和行為可能大不相同 - 從GPS坐標到電子郵件到星歷數據的任何事情,以及嘗試捕獲基類界面中所有多樣性的情況都沒有多大意義。 一旦代理收到消息,它知道它感興趣(根據消息類型)然后(並且只有那時)向下轉換到實際的正確類型才能訪問有意義的消息內容是合適的 - 因為你已經達到了消息不能以純粹抽象的方式處理。
向下傾斜本身並不是一種失敗。 使用RTTI的成本遠遠高於添加一個簡單的枚舉字段,並且經常推薦的“保持動態投射直到某些東西起作用”應該比我想象中發明的更加殘酷的懲罰!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.