Skip to main content
GameDev.net gamedev.net
🔒 Locked

template question..

Started by gowron67 Aug 1, 2006 at 12:36 PM 7 replies 1.1k views
Original Post
gowron67
gowron67


class Base
{
...
   std::string name;
};

template<N>
class Derived : public Base
{

   void MyDerivedFunction();
...
   float data[ N ];
};

Base* array[ 512 ];


Say I create a few of class Base, and push them into the Base* array... Say then I create a few Derived< 3 >, a few Derived< 10 > etc.. and push them into the Base* array.... Say I then loop through my array


//do something with the Derived* objects...
for( int i = 0; i < whatever; ++i )
{
    Derived* ptr = dynamic_cast< Derived* >( array[ i ] );
    if( ptr )
    {
       ....
    }
}


This code won't compile because I need to specify the type of Derived explicitly i.e. Derived< 3 >*....that's fine - fair enough, but my question is since I don't necessarily know the exact types of some of the Derived* objects in the array, is it safe to use an arbitrary one to call MyDerivedFunction()...like this..


#define ARBITRARY_NUMBER 5
//do something with the Derived* objects...
for( int i = 0; i < whatever; ++i )
{
    Derived<ARBITRARY_NUMBER>* ptr = dynamic_cast< Derived<ARBITRARY_NUMBER>* >( array[ i ] );
    if( ptr )
    {
       //do something
       ptr->MyDerivedFunction();
    }
}


This code works but I'm wondering if it's safe to do this, will 'nasty' things happen somewhere because I'm using a slightly different type i.e. Derived<5> instead of for example Derived<3>. I realise that the code above may well be very badly designed but it's just an illustration of some more complex code I have. I'm not asking if what I'm doing is good design, I'm just asking if it's safe to do this kind of thing.... thanks.
ToohrVyk
ToohrVyk
You code's behavior is perfectly defined, so you have nothing to fear. However, it will only recognize Derived<5> — dynamic casting will return null for any other type, including Derived<3>.

Also, make sure to include a virtual destructor in Base.

EDIT: possible solution:

class Base;class DerivedRoot : public Base {  virtual void DerivedFunction() = 0;}template<int N> class Derived : DerivedRoot {  virtual void DerivedFunction() { }}


Then cast to DerivedRoot.
Xai
Xai
as posted above, the behavior you are looking for is the definition of polymorphism - so the ideal c++ solution is to use virtual functions in a common base class.

If Base exists for a preexisting reason and you can't extend it, simply make another class "Intermediate" or something that adds the virtual function ... make Interediate derive from Base and add the function(s) you want ... then make the Derived<> template derive from Intermediate. Then you use dynamic_cast to Intermediate to get what you want ... or better yet, make the list be of type Intermediate* already ...
gowron67
gowron67
Thanks guys....I hadn't thought of doing the intermediate class....

Toorvyk - just out of interest...if dynamic_cast returns NULL for a Derived< 5 > if the pointer is of a Derived< 3 > type for example, what would a C-style cast do in the same situation ? Would it still be safe to use the pointer ?
Zahlman
Zahlman
Quote:
Original post by Xai
as posted above, the behavior you are looking for is the definition of runtime (dynamic) polymorphism - so the ideal c++ solution is to use virtual functions in a common base class.


Fixed. :) The templating implements static (compile-time) polymorphism, but the whole point is that we *don't* know the size at compile-time. Therefore we have to defer the decision about what to do to run-time. Templating and inheritance do different things, and they usually don't mix very well.

Anyway, if you don't have any other Derived classes, but are only "templating on" the array size, then you can do things *much* more simply:

class Derived : public Base{   void MyDerivedFunction();...   std::vector<float> data;};std::vector<Derived> derived_things; // we don't care that they're derived from Base, because we're doing Derived-// specific things to them, i.e. using the Derived interface.for(std::vector<Derived>::iterator it = derived_things.begin();    it != derived_things.end(); ++it) {  // no dynamic casting or dereferencing nonsense; just use *it  // Better yet, set up to use std::for_each().}
gowron67
gowron67
Thanks Zahlman but as I said in my original post, I wasn't asking about the design of the system. I know it's not ideal. It turns out I don't have the option / time to change it, hence my original question.

The pseudo-code I gave is also exactly that - the real system is a lot more complex( and doesn't involve dynamic_cast ( that was contrived to get my point across ) ).

The simplest solution to the problem I have is the one ToohrVyk and Xai suggested but thanks anyway.
ToohrVyk
ToohrVyk
Quote:
Original post by gowron67
Thanks guys....I hadn't thought of doing the intermediate class....

Toorvyk - just out of interest...if dynamic_cast returns NULL for a Derived< 5 > if the pointer is of a Derived< 3 > type for example, what would a C-style cast do in the same situation ? Would it still be safe to use the pointer ?


With C-syle casts, you can only do two kinds of casts safely on classes with virtual methods:


  • Perform an already-possible cast that does not require explicit casting (such as casting from derived to base).

  • Cast from a type to void* and from void* back to the same type.



Any other cast causes undefined behavior, which may work, or may format the user's computer, depending on the alignment of planets at the time of execution.

You can also cast from an entity to another entity of smaller alignment restrictions (for instance, to char*) and back. Because of the possibility of a vtable, this cannot be done if you have virtual methods in your class. Also, this is a break of abstraction if you use it on something other than PODs (that is, data types which use the default constructor, copy constructor and assignment operator, and have no virtual methods).
Xai
Xai
it is my understanding that a c-style cast will do the same thing as a static_cast, which in the case of pointers is going to be identical to a reinterpret_cast (aka a no-op) in cases where multiple inheiritance isn't involved.

If the compiler being uses generates an identical object layout and v-table map for the 2 classes, then this will of course work perfectly. But as mentioned, this is not defined, and their are cases where it will fail. In my experience on x86 platforms using Visual Studio, the binrary layout is deterministic and IF USING PURE SINGLE INHEIRITANCE, the op is safe. The reason is really because to support Microsoft's COM spec the compiler pretty much has to generate a certain binary layout based on the inheiritance and member order, so that object code compiled into different projects can be compatible. This is of course not guaranteed and would not be true across compilers.

For the record - I do not recommend the static / reinterpret cast option. It is much too risky for such an easy to fix problem.
snk_kid
snk_kid
C style-casts can do either static_/reinterpret_/const_cast but never dynamic_cast.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.