Я сталкиваюсь с ситуациями, когда я нахожу удобным хранить тип (как перечисление) объекта в базовом классе для дальнейшего приведения указателя на этот базовый класс в указатель подкласса в зависимости от значения этого типа.
Например :
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();
Похоже, вы пытаетесь эмулировать алгебраический тип данных. Как правило, вы бы не сделали это таким образом, но, поставив чистый 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(...)
для этого. На самом деле, если вы хотите заниматься мысленно-ориентированной практикой, вы должны создать виртуальную функцию в CToken
которые позволяют подклассам автоматически интерпретировать себя, операторы как операторы и значения как значения.
Вам следует никогда Вниз — кроме случаев, когда вам нужно.
Простой пример того, когда это может быть уместным, — это передача сообщений в рамках. Скажем, каждый тип сообщения должен быть сериализуемым, чтобы интерфейс базового класса предоставлял виртуальный метод serialise (), переопределяемый классами Dervied, а все сообщения любого типа полиморфно обрабатываются средой обмена сообщениями.
Однако то, что эти сообщения могут представлять, и поэтому атрибуты и поведение, которыми они обладают, могут сильно отличаться — от координат GPS до электронной почты и эфемеридных данных, и в этом случае попытка охватить все это разнообразие в интерфейсе базового класса не имеет большого смысла. Как только агент получил сообщение, он знает, что он заинтересован (в соответствии с типом сообщения), а затем (и только тогда) понижен до фактического правильного типа для доступа к значимому содержимому сообщения, так как вы достигли точки, когда сообщение не может быть обработано чисто абстрактным способом.
Даункинг сам по себе не провал. Использование RTTI обходится гораздо дороже, чем добавление простого перечисляемого поля, и часто рекомендуется «продолжать динамическое приведение, пока что-то не работает», заслуживает гораздо более жестокого наказания, чем я могу придумать!