My program needs to handle different kinds of “notes”: NoteShort, NoteLong… Different kinds of notes should be displayed in the GUI in different ways. I defined a base class of these notes, called NoteBase.
I store these notes in XML; and I have a class which reads from the XML file and store notes’ data in vector<NoteBase *> list. Then I found I cannot get their own types, because they are already converted to NoteBase *!
Though if(dynamic_cast<NoteLong *>(ptr) != NULL) {...} may works, it’s really too ugly. Implementing functions take NoteShort * or NoteLong * as parameter don’t work. So, any good way to deal with this problem?
UPDATE: Thank you guys for replying. I don’t think it should happen neither — but it did happened. I implemented it in another way, and it’s now working. However, as far as I remember, I indeed declared the (pure) virtual function in NoteBase, but forgot to declare it again in headers of the deriving classes. I guess that’s what caused the issue.
UPDATE 2 (IMPORTANT):
I found this quotation from C++ Primer, which may be helpful to others:
What is sometimes a bit more surprising is that the restriction on
converting from base to derived exists even when a base pointer or
reference is actually bound to a derived object:Bulk_item bulk; Item_base *itemP = &bulk; // ok: dynamic type is Bulk_item Bulk_item *bulkP = itemP; // error: can't convert base to derivedThe compiler has no way to know at compile time that a specific
conversion will actually be safe at run time. The compiler looks only
at the static types of the pointer or reference to determine whether a
conversion is legal. In those cases when we know that the conversion
from base to derived is safe, we can use a static_cast (Section
5.12.4, p. 183) to override the compiler. Alternatively, we could request a conversion that is checked at run time by using a
dynamic_cast, which is covered in Section 18.2.1 (p. 773).
There are two significant trains of thought and code here, so shortest first:
You may not need to cast back up. If all
Notes provide a uniform action (sayChime), then you can simply have:and each
NotewillChimeas it should, using internal information (duration and pitch, for example).This is clean, simple, and requires minimal code. It does mean the types all have to provide and inherit from a particular known interface/class, however.
Now the longer and far more involved methods occur when you do need to know the type and cast back up to it. There are two major methods, and a variant (#2) which may be used or combined with #3:
This can be done in the compiler with RTTI (runtime type information), allowing it to safely
dynamic_castwith good knowledge of what is allowed. This only works within a single compiler and perhaps single module (DLL/SO/etc), however. If your compiler supports it and there are no significant downsides of RTTI, it is by far the easiest and takes the least work on your end. It does not, however, allow the type to identify itself (although atypeoffunction may be available).This is done as you have:
To make it entirely independent, adding a virtual method to the base class/interface (for example,
Uuid GetType() const;) allows the object to identify itself at any time. This has a benefit over the third (true-to-COM) method, and a disadvantage: it allows the user of the object to make intelligent and perhaps faster decisions on what to do, but requires a) they cast (which may necessitate and unsafereinterpret_castor C-style cast) and b) the type cannot do any internal conversion or checking.The option which COM uses is to provide a method of the form
RESULT /* success */ CastTo(const Uuid & type, void ** ppDestination);. This allows the type to a) check the safety of the cast internally, b) perform the cast internally at its own discretion (there are rules on what can be done) and c) provide an error if the cast is impossible or fails. However, it a) prevents the user form optimizing well and b) may require multiple calls to find a succesful type.Combining the latter two methods in some fashion (if a 00-00-00-0000 Uuid and
nullptrdestination are passed, fill the Uuid with the type’s own Uuid, for example) may be the most optimal method of both identifying and safely converting types. Both the latter methods, and them combined, are compiler and API independent, and may even achieve language-independence with care (as COM does, in qualified manner).The latter two are particularly useful when the type is almost entirely unknown: the source library, compiler, and even language are not known ahead of time, the only available information is that a given interface is provided. Working with such little data and unable to use highly compiler-specific features such as RTTI, requiring the object to provide basic information about itself is necessary. The user can then ask the object to cast itself as needed, and the object is has full discretion as to how that’s handled. This is typically used with heavily virtual classes or even interfaces (pure virtual), as that may be all the knowledge the user code may have.
This method is probably not useful for you, in your scope, but may be of interest and is certainly important as to how types can identify themselves and be cast back "up" from a base class or interface.