Dynamic_cast Vs Static_cast - when is the former really necessary?

G

GianGuz

Everyone knows about the complex and cpu-expensive procedures taken by
dynamic_cast to find the right function call in a virtual classes
hierarchy.
The question I would to rise is when dynamic_cast is really necessary?
A wise usage of templates and/or non dynamic_cast operators can grant
the compiler with any information it needs to understand at compile
time what is the right method to call.
In the following example I tested the usage of dynamic_cast to call a
sibling method in a simple 4 class hierarchy. I compared it with a
version in wich static_cast is used from the topmost derived class to
pass information to
its super classes. Results were stunnig. Dynamic_cast version perform
at least 14 time slower than the static_cast one.
Let's see the code:

#include <string>
#include <iostream>

extern "C" {

#include <stdlib.h>
#include <time.h>
#include <unistd.h>
}

#define INIT_TIMER long T; time(&T); srand(T);

#define START_TIMER long start0,end0; clock_t start1,end1;
time(&start0); start1 = clock();

#define END_TIMER time(&end0); end1 = clock();

#define PRINT_TIMER cout << "Computation time (sec)" << (end0) -
(start0) << endl; cout << "Computation time (msec)" << (end1) -
(start1) << endl;

#define MAX_VALUE 50000000

using namespace std;

class A;

class BASE {

public:

BASE() { }

virtual int a() = 0;

virtual int b(A* ptr = NULL) = 0;

virtual int c() = 0;

virtual int cS() = 0;

virtual ~BASE() { }
};

class A : virtual public BASE {

public:

A() { }

int a() { return 0; }

virtual ~A() { }
};

class B : virtual public BASE {

public:

B() { }

protected:

int b(A* ptr = NULL) {

if (ptr == NULL) return dynamic_cast<A*>(this)->a() + 1;

else return ptr->a() + 1;
}

virtual ~B() { }
};

class C : virtual public A, virtual public B {

public:

C() { }

int a() { return 2; }

int c() { return b(); }

int cS() { return b(static_cast<A*>(this)); }

~C() { }
};

int main() {

INIT_TIMER

{
cout << "static_cast version " << endl;

BASE* base = new C;

long result = 0;

START_TIMER

for(int i=0; i<MAX_VALUE; i++) {

result += base->cS();
}

END_TIMER

delete base;

cout << "Result " << result << endl;

PRINT_TIMER
}

{
cout << "dynamic_cast version " << endl;

BASE* base = new C;

long result = 0;

START_TIMER

for(int i=0; i<MAX_VALUE; i++) {

result += base->c();
}

END_TIMER

delete base;

cout << "Result " << result << endl;

PRINT_TIMER
}
}

I would like to know comments about that. I also would like to see an
example
in wich dynamic_cast cannot be absolutely removed.
Cheers.

Gianguglielmo
 
J

Jonathan Mcdougall

GianGuz said:
The question I would to rise is when dynamic_cast is really necessary?

When you need to cast a type to another without knowing the validity of
the cast. When using static_cast, you may find yourself with an object
with an invalid type, which will crash your program. dynamic_cast
either returns 0 or throws an exception (for references), alowing you to
detect bad casts.
A wise usage of templates and/or non dynamic_cast operators can

, most of the time,
grant
the compiler with any information it needs to understand at compile
time what is the right method to call.

I agree, with the help of virtual functions.
In the following example I tested the usage of dynamic_cast to call a
sibling method in a simple 4 class hierarchy. I compared it with a
version in wich static_cast is used from the topmost derived class to
pass information to
its super classes. Results were stunnig. Dynamic_cast version perform
at least 14 time slower than the static_cast one.

It is not surprising, since static_cast does (probably) nothing at
run-time, compared with dynamic_cast which has to validate the cast by
checking the class hierarchy.
Let's see the code:

<snip>

Next time, include the results, not the code. Or if you give the code,
indent it correctly and don't use macros.
I would like to know comments about that. I also would like to see an
example
in wich dynamic_cast cannot be absolutely removed.

An example would be the check whether a type can be converted to another
at run-time. That is, by the way, the reason for which dynamic_cast was
introduced.


Jonathan
 
G

GianGuz

Jonathan said:
necessary?

When you need to cast a type to another without knowing the validity of
the cast. When using static_cast, you may find yourself with an object
with an invalid type, which will crash your program. dynamic_cast
either returns 0 or throws an exception (for references), alowing you to
detect bad casts.


, most of the time,


I agree, with the help of virtual functions.


It is not surprising, since static_cast does (probably) nothing at
run-time, compared with dynamic_cast which has to validate the cast by
checking the class hierarchy.


<snip>

Next time, include the results, not the code. Or if you give the code,
indent it correctly and don't use macros.

Sorry for the code. I'll do my best next time.
In the meantime here are some results:

CALLING ITERATIONS 50000000

static_cast version
Result 150000000
Computation time (sec)2
Computation time (msec)1530000

dynamic_cast version
Result 150000000
Computation time (sec)24
Computation time (msec)22000000
An example would be the check whether a type can be converted to another
at run-time. That is, by the way, the reason for which dynamic_cast was
introduced.

The problem here is that : "yes, it is a situation in which you require
dynamic_cast"...but can be avoided? When you design a hierarchy you
have the control on what is compile time determined. Why do you have to
check for type conversion at runtime instead of changing the design to
make it possible at compile time. And this is tipically possible. The
question at this point is: "is always possible to change the design in
such way?"
I would like to see an example that clarify that no other design
choices (without dynamic_cast) are possible to reach the same result.
I hope that my opinion was expressed clearly.
Thanks,

Gianguglielmo
 
J

Jonathan Mcdougall

GianGuz said:
The problem here is that : "yes, it is a situation in which you require
dynamic_cast"...but can be avoided? When you design a hierarchy you
have the control on what is compile time determined. Why do you have to
check for type conversion at runtime instead of changing the design to
make it possible at compile time. And this is tipically possible. The
question at this point is: "is always possible to change the design in
such way?"
I would like to see an example that clarify that no other design
choices (without dynamic_cast) are possible to reach the same result.

No feature is absolutly necessary in C++ knowing that you could do the
very same thing in assembly. The thing is, not everybody has the chance
to "change the design" of a framework. dynamic_cast may be a worst case
solution, but when you need it, there is nothing in the language which
can emulate it. For example, if you don't have access to the class
definition, you will have no choice but to use dynamic_cast's if you
want the behavior to change wrt the object's type. It is not always
possible to have the cleanest code.


Jonathan
 
G

GianGuz

That's it. If you don't have access to class definitions the only way
to make 'inference' about the current hierarchy is to check types at
runtime.
Thanks,

Gianguglielmo
 
C

Cy Edmunds

GianGuz said:
Everyone knows about the complex and cpu-expensive procedures taken by
dynamic_cast to find the right function call in a virtual classes
hierarchy.
The question I would to rise is when dynamic_cast is really necessary?

[snip]

dynamic_cast has a bad reputation, mostly from its (often realized)
potential for abuse. However, it allows us C++ programmers to use multiple
abstract interfaces with our objects just as COM and many other object
models use with theirs. For instance:

class IThing
{
public:
virtual void foo() = 0;
virtual ~IThing() {}
};

class ILog
{
public:
virtual void send_to_log(const std::string &) = 0;
virtual ~ILog() {}
};

void yadda(IThing *p)
{
p->foo(); // I know I can call this
// check to see if an ILog interface was included
ILog *p2 = dynamic_cast<ILog*>(p);
if (p2)
p2->send_to_log("yadda here");
}

I don't do this every day in my own code, but I like having the feature in
the language.
 
G

GianGuz

Your usage of dyamic_cast relies on what we said about the need to
determine a type
information at run-time. In the code you shown I can imagine that you
don't need to
check
....
ILog *p2 = dynamic_cast<ILog*>(p);
if (p2)
....
all the time. So dynamic_cast really does not impact on performance
measures.
What i want to say is that we have to point out that using that cast
operator
to repeatly call a particular method without trying to redesign the
class hierarchy
(when this is possible, of course) in favour of a static_cast -
reinterpret_cast version
is big mistake.
Moreover many compilers (for example gcc) supports flags such as
no-rtti that completely
disables the run time type information system of c++ (dynamic_cast and
type_id)
in favour of code size and execution speed. Another feature to take
into account when
we decide to dynamic_cast something...
Cheers,

Gianguglielmo
 
T

Tom Widmer

GianGuz said:
The problem here is that : "yes, it is a situation in which you require
dynamic_cast"...but can be avoided? When you design a hierarchy you
have the control on what is compile time determined. Why do you have to
check for type conversion at runtime instead of changing the design to
make it possible at compile time. And this is tipically possible. The
question at this point is: "is always possible to change the design in
such way?"
I would like to see an example that clarify that no other design
choices (without dynamic_cast) are possible to reach the same result.

That's clearly impossible. You can write any program using any small
turing-complet subset of C++ (such as C).

However, dynamic_cast is most sometimes used for input. e.g.

SomeBase* o = read_from_stream(s);
//what was read? Unknown at compile time
if (Derived1* p = dynamic_cast<Derived1*>(o))
{
//it was a derived1 object
}
else if (etc....

Of course you can use object IDs instead of dynamic_cast, or use, e.g.
the Visitor pattern, but dynamic_cast is a non-intrusive approach.
Speaking of the visitor pattern, dynamic_cast can be used to implement
an acyclic visitor. It is also sometimes used in "multimethod"
implementations.

On a final note, although dynamic_cast is slow on Microsoft compilers
(it does string comparisons due to the potential for dynamically loaded
DLLs), it is faster on most other platforms (which just compare numeric
IDs).

Tom
 
G

GianGuz

Tom said:
result.

That's clearly impossible. You can write any program using any small
turing-complet subset of C++ (such as C).

Obviously I'm not interest in such low level rewriting.
However, dynamic_cast is most sometimes used for input. e.g.

SomeBase* o = read_from_stream(s);
//what was read? Unknown at compile time
if (Derived1* p = dynamic_cast<Derived1*>(o))
{
//it was a derived1 object
}
else if (etc....

This is the case I need. IMHO we have to clarify WHERE the dynamic_cast
operator HAVE TO BE used and where it HAVE TO BE AVOIDED. Most of the
time simply knowing that it does a lot of work for us seems to be
enough to say:
"ok let's use it" without taking care of alternatives.
I think that having nice features doesn't mean we have to stop
thinking about other, possible better, solutions.
I'm also interested in cases when dynamic_cast checks can be immediatly
substituted by an equivalent use of type_id.
Of course you can use object IDs instead of dynamic_cast, or use, e.g.
the Visitor pattern, but dynamic_cast is a non-intrusive approach.
Speaking of the visitor pattern, dynamic_cast can be used to implement
an acyclic visitor. It is also sometimes used in "multimethod"
implementations.

On a final note, although dynamic_cast is slow on Microsoft compilers
(it does string comparisons due to the potential for dynamically loaded
DLLs), it is faster on most other platforms (which just compare numeric
IDs).

Tom
I didn't know about that. Well I know about that know!;)

Gianguglielmo
 
J

Jonathan Mcdougall

GianGuz wrote:

Please quote the posts you are answering to.
Your usage of dyamic_cast relies on what we said about the need to
determine a type
information at run-time.

And what could dynamic_cast do except "determine a type information at
run-time"?
In the code you shown I can imagine that you
don't need to
check
....
ILog *p2 = dynamic_cast<ILog*>(p);
if (p2)
....
all the time. So dynamic_cast really does not impact on performance
measures.

But of course dynamic_cast has an impact on performance! The least you
use it, the better it is, we all agree on that. What's your point?
What i want to say is that we have to point out that using that cast
operator
to repeatly call a particular method without trying to redesign the
class hierarchy
(when this is possible, of course) in favour of a static_cast -
reinterpret_cast version
is big mistake.

That's nonsense. The first thing you learn about casts is to avoid
them. Type-switchs are covered in any good c++ textbook and are never
recommended. What I said in my other post is that you may find yourself
before some code you cannot modify. In this case, writing a wrapper or
something will be better than calling dynamic_cast many times. I did
not advocate repeated usage of dynamic_cast. It's just a "handy
feature", like everything else. Used correctly, it can be a powerful
feature, but used incorrectly, it can clutter the code, make
compilations take hours and be terribly "inefficient (tm)".
Moreover many compilers (for example gcc) supports flags such as
no-rtti that completely
disables the run time type information system of c++ (dynamic_cast and
type_id)
in favour of code size and execution speed. Another feature to take
into account when
we decide to dynamic_cast something...

That's irrelevant and nonsense. Because rtti may be deactivated on some
compilers does not make its usage a "big mistake". Did you know
exceptions, warnings and the scope of for loops can also be deactivated
on most compilers? Does that make them a "big mistake" also?


Jonathan
 
G

GianGuz

Yes I know about exceptions,warning and scope of for loops,
but I think that are not so related to our case. The usage
of only two operators: dynamic_cast and type_id force
the program to integrate a support that really could be
avoided in a lot of situations. I considered a big mistake
simply the fact that someone could ignore these problems.
Obviously you are able to choice to use the operator you
prefer since you decide that in your particular application
gains are greater than penalties.

Gianguglielmo
 
J

Jonathan Mcdougall

GianGuz said:
Yes I know about exceptions,warning and scope of for loops,
but I think that are not so related to our case. The usage
of only two operators: dynamic_cast and type_id force
the program to integrate a support that really could be
avoided in a lot of situations.

If you are saying dynamic_cast can/is misused, then I agree.
I considered a big mistake
simply the fact that someone could ignore these problems.
Obviously you are able to choice to use the operator you
prefer since you decide that in your particular application
gains are greater than penalties.

That's a programmer's job.


Jonathan
 
T

Tom Widmer

GianGuz said:
Yes I know about exceptions,warning and scope of for loops,
but I think that are not so related to our case. The usage
of only two operators: dynamic_cast and type_id force
the program to integrate a support that really could be
avoided in a lot of situations.

Some implementations of exception handling use the same infrastructure
to do the matching of throws with catch clauses. So the space isn't
wasted in that case.

In any case, we're not talking about a lot of overhead, at least not in
the usual single inheritence case.

I considered a big mistake
simply the fact that someone could ignore these problems.

The overhead of RTTI if you don't use it is just a couple of extra
entries in vtables, so a small code side overhead. A clever compiler
could remove the overhead for all hierarchies that don't have
dynamic_cast/typeid used on them.
Obviously you are able to choice to use the operator you
prefer since you decide that in your particular application
gains are greater than penalties.

If you are basically inventing your own RTTI system, then that is
definitely worse than using the one available, unless the built in RTTI
has proved too slow in profiling.

Tom
 
P

Paavo Helde

Yes I know about exceptions,warning and scope of for loops,
but I think that are not so related to our case. The usage
of only two operators: dynamic_cast and type_id force
the program to integrate a support that really could be
avoided in a lot of situations. I considered a big mistake
simply the fact that someone could ignore these problems.

I believe that pros and contras were considered carefully *before* adding
these features to the language, and I'm quite sure no ignorance can be
contributed to Bjarne Stroustrup.

In C++ you can shoot your leg in any moment. Dynamic_cast is no
different. If a programmer uses it in a tight time-critical loop then
this programmer is ignorant indeed.

C++ follows the zero-overhead policy, i.e. both features you mention are
such that if they are not used they do not influence significantly the
program performance. Yes they have some impact, for example in the realm
of shared libraries dynamic_cast is finally resorting to string
comparisons, which means the executable code must have the class names
stored somewhere. Ditto for typeid. Hence the compiled program is larger
than it should be. On the other hand I'm sure that if the compiler and
linker can prove that the program does not use RTTI and does not load
shared libraries, they could strip this code from the final executable
(don't know or care if any do that).

The executable size usually impacts only embedded processors or otherwise
memory-critical applications. In a common desktop system the unused parts
of the code never get paged in, so the loss is marginal. Only in order to
support memory-critical applications most compilers have options to
switch RTTI off.

Paavo
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,764
Messages
2,569,567
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top