Здесь есть два важных направления мысли и кода, поэтому самый короткий сначала:
Возможно, вам не нужно будет подбрасывать. Если все Note
обеспечивают единообразное действие (скажем, Chime
), тогда вы можете просто иметь:
class INote
{
virtual void Chime() = 0;
};
...
for_each(INote * note in m_Notes)
{
note->Chime();
}
и каждый Note
будет Chime
, как и должно быть, используя внутреннюю информацию (например, длительность и высоту тона).
Это чисто, просто и требует минимального кода. Однако это означает, что все типы должны предоставлять и наследовать от определенного известного интерфейса / класса.
Теперь более длинные и гораздо более сложные методы возникают, когда вам do нужно знать тип и приводить обратно к нему. Существует два основных метода и вариант (# 2), который можно использовать или комбинировать с # 3:
Это можно сделать в компиляторе с RTTI (информация о типе среды выполнения), что позволяет ему безопасно dynamic_cast
с хорошим знанием того, что разрешено. Однако это работает только внутри одного компилятора и, возможно, одного модуля (DLL / SO / и т. Д.). Если ваш компилятор поддерживает его, и RTTI не имеет существенных недостатков, он, безусловно, самый простой и требует меньше всего усилий с вашей стороны. Однако он не позволяет типу идентифицировать себя (хотя может быть доступна функция typeof
).
Это сделано, как у вас:
NewType * obj = dynamic_cast<NewType*>(obj_oldType);
Чтобы сделать его полностью независимым, добавление виртуального метода в базовый класс / интерфейс (например, Uuid GetType() const;
) позволяет объекту идентифицировать себя в любое время. Это имеет преимущество перед третьим (true-to-COM) методом и недостатком: оно позволяет пользователю объекта принимать разумные и, возможно, более быстрые решения о том, что делать, но требует a) их наложения (что может потребовать и небезопасное reinterpret_cast
или приведение в стиле C) и b) тип не может выполнять какое-либо внутреннее преобразование или проверку.
ClassID id = obj->GetType();
if (id == ID_Note_Long)
NoteLong * note = (NoteLong*)obj;
...
Опция, которую использует COM, заключается в предоставлении метода вида RESULT /* success */ CastTo(const Uuid & type, void ** ppDestination);
. Это позволяет типу: а) проверять безопасность приведения внутри, б) выполнять приведение внутренне по своему усмотрению (существуют правила относительно того, что можно сделать) и в) выдавать ошибку, если приведение невозможно или не удается. Тем не менее, это а) предотвращает оптимизацию пользовательской формы и б) может потребоваться несколько вызовов для поиска успешного типа.
NoteLong * note = nullptr;
if (obj->GetAs(ID_Note_Long, ¬e))
...
Комбинация последних двух методов некоторым способом (если пропущены Uuid 00-00-00-0000 и пункт назначения nullptr
, заполните Uuid, например, собственным Uuid типа), может быть наиболее оптимальным методом из обоих выявление и безопасное преобразование типов. Оба последних метода и их сочетания независимы от компилятора и API и могут даже с осторожностью достичь независимости от языка (как это делает COM, квалифицированным образом).
ClassID id = ClassID::Null;
obj->GetAs(id, nullptr);
if (id == ID_Note_Long)
NoteLong * note;
obj->GetAs(ID_Note_Long, ¬e);
...
Последние два особенно полезны, когда тип почти полностью неизвестен: исходная библиотека, компилятор и даже язык не известны заранее, единственная доступная информация - это предоставление заданного интерфейса. Работа с такими небольшими данными и неспособность использовать функции, специфичные для компилятора, такие как RTTI, требуют, чтобы объект предоставлял основную информацию о себе. Затем пользователь может попросить объект привести себя по мере необходимости, и объект имеет полное усмотрение относительно того, как он обрабатывается. Это обычно используется с сильно виртуальными классами или даже интерфейсами (чисто виртуальными), поскольку это может быть все знание, которое может иметь пользовательский код.
Этот метод, вероятно, бесполезен для вас в вашей области, но может представлять интерес и, безусловно, важен в отношении того, как типы могут идентифицировать себя и возвращаться "вверх" из базового класса или интерфейса.