thinking in c++ by B. Eckel, typeid and g++ problem.

A

Adam Zimny

This is fragment of code from Bruce Eckel's Thinking in c++ ( last 3 couts
are mine to show what happened ). The question is: is Bruce Eckel wrong or
g++ ( my version is 3.2.3 ) is buggy ?

//: C15:StaticHierarchyNavigation.cpp
// Navigating class hierarchies with static_cast
#include <iostream>
#include <typeinfo>
using namespace std;

class Shape { public: virtual ~Shape() {}; };
class Circle : public Shape {};
class Square : public Shape {};
class Other {};

int main() {
Circle c;
Shape* s = &c; // Upcast: normal and OK
// More explicit but unnecessary:
s = static_cast<Shape*>(&c);
// (Since upcasting is such a safe and common
// operation, the cast becomes cluttering)
Circle* cp = 0;
Square* sp = 0;


// Static Navigation of class hierarchies
// requires extra type information:
if(typeid(s) == typeid(cp)) // C++ RTTI
cp = static_cast<Circle*>(s);
if(typeid(s) == typeid(sp))
sp = static_cast<Square*>(s);
if(cp != 0)
cout << "It's a circle!" << endl;
if(sp != 0)
cout << "It's a square!" << endl;
// Static navigation is ONLY an efficiency hack;
// dynamic_cast is always safer. However:
// Other* op = static_cast<Other*>(s);
// Conveniently gives an error message, while
Other* op2 = (Other*)s;
// does not


// my additional lines - shows what has happend
cout << "typeid(c).name: " << typeid(c).name();
cout << "\ntypeid(s).name: " << typeid(s).name() << "\ntypeid(cp).name: " <<
typeid(cp).name() << "\ntypeid(sp).name: " << typeid(sp).name() <<endl;
cout << "typeid(*s).name: " << typeid(*s).name() <<endl;

} ///:~
 
V

Victor Bazarov

Adam said:
This is fragment of code from Bruce Eckel's Thinking in c++ ( last 3 couts
are mine to show what happened ). The question is: is Bruce Eckel wrong or
g++ ( my version is 3.2.3 ) is buggy ?

Care to post what you expected versus what you're getting? Why are you
presuming we are all mind-readers here?
//: C15:StaticHierarchyNavigation.cpp
// Navigating class hierarchies with static_cast
#include <iostream>
#include <typeinfo>
using namespace std;

class Shape { public: virtual ~Shape() {}; };
class Circle : public Shape {};
class Square : public Shape {};
class Other {};

int main() {
Circle c;
Shape* s = &c; // Upcast: normal and OK
// More explicit but unnecessary:
s = static_cast<Shape*>(&c);
// (Since upcasting is such a safe and common
// operation, the cast becomes cluttering)
Circle* cp = 0;
Square* sp = 0;


// Static Navigation of class hierarchies
// requires extra type information:
if(typeid(s) == typeid(cp)) // C++ RTTI
cp = static_cast<Circle*>(s);
if(typeid(s) == typeid(sp))
sp = static_cast<Square*>(s);
if(cp != 0)
cout << "It's a circle!" << endl;
if(sp != 0)
cout << "It's a square!" << endl;
// Static navigation is ONLY an efficiency hack;
// dynamic_cast is always safer. However:
// Other* op = static_cast<Other*>(s);
// Conveniently gives an error message, while
Other* op2 = (Other*)s;
// does not


// my additional lines - shows what has happend
cout << "typeid(c).name: " << typeid(c).name();
cout << "\ntypeid(s).name: " << typeid(s).name() << "\ntypeid(cp).name: " <<
typeid(cp).name() << "\ntypeid(sp).name: " << typeid(sp).name() <<endl;
cout << "typeid(*s).name: " << typeid(*s).name() <<endl;

} ///:~

When built with VC++ v8b2, this program outputs (between dashes)
----------------
typeid(c).name: class Circle
typeid(s).name: class Shape *
typeid(cp).name: class Circle *
typeid(sp).name: class Square *
typeid(*s).name: class Circle
 
A

Adam Zimny

I have just tried code from previous post on g++ version 2.95.3. The same
(bad?) effect.
 
A

Alf P. Steinbach

* Adam Zimny:
This is fragment of code from Bruce Eckel's Thinking in c++ ( last 3 couts
are mine to show what happened ). The question is: is Bruce Eckel wrong
Yes.


or g++ ( my version is 3.2.3 ) is buggy ?

Don't know.

//: C15:StaticHierarchyNavigation.cpp
// Navigating class hierarchies with static_cast
#include <iostream>
#include <typeinfo>
using namespace std;

class Shape { public: virtual ~Shape() {}; };
class Circle : public Shape {};
class Square : public Shape {};
class Other {};

int main() {
Circle c;
Shape* s = &c; // Upcast: normal and OK
// More explicit but unnecessary:
s = static_cast<Shape*>(&c);
// (Since upcasting is such a safe and common
// operation, the cast becomes cluttering)
Circle* cp = 0;
Square* sp = 0;


// Static Navigation of class hierarchies
// requires extra type information:
if(typeid(s) == typeid(cp)) // C++ RTTI
cp = static_cast<Circle*>(s);
if(typeid(s) == typeid(sp))
sp = static_cast<Square*>(s);

Should be

if( typeid( *s ) == typeid( Circle ) ) // C++ RTTI
cp = static_cast<Circle*>( s );
if( typeid( *s ) == typeid( Square ) )
sp = static_cast<Square*>( s );

However, the same can be done more efficiently with dynamic_cast.
 
A

Adam Zimny

Victor said:
Care to post what you expected versus what you're getting? Why are you
presuming we are all mind-readers here?

Sorry,
I don't know what should typeid(xxx).name() return but i expected that
program writes:
"It's a circle"
I believe that this code was designed to demonstrate how typeid works and
how can we downcast in secure and fast way - above code compiled on g++
shows nothing.
 
A

Adam Zimny

Adam said:
Sorry,
I don't know what should typeid(xxx).name() return but i expected that
program writes:
"It's a circle"
I believe that this code was designed to demonstrate how typeid works and
how can we downcast in secure and fast way - above code compiled on g++
shows nothing.

again sorry, I forgot to write what i get.
I get only:
typeid(c).name: 6Circle
typeid(s).name: P5Shape
typeid(cp).name: P6Circle
typeid(sp).name: P6Square
typeid(*s).name: 6Circle
 
A

Adam Zimny

Alf said:
* Adam Zimny:

Don't know.




Should be

if( typeid( *s ) == typeid( Circle ) ) // C++ RTTI
cp = static_cast<Circle*>( s );
if( typeid( *s ) == typeid( Square ) )
sp = static_cast<Square*>( s );

However, the same can be done more efficiently with dynamic_cast.
thanks,
and about more efficient dynamic_cast than above version - Bruce claims that
version with typeid is efficiency trick and is faster than dynamic_cast.
But maybe he is wrong again...
 
A

Alf P. Steinbach

* Adam Zimny:
and about more efficient dynamic_cast than above version - Bruce claims that
version with typeid is efficiency trick and is faster than dynamic_cast.

I fail to see that he has claimed that.

But maybe he is wrong again...

The statement doesn't make sense, but as mentioned, I fail to see that Bruce
Eckel has claimed something like that. Think about it. What the code does
is to implement the dynamic_cast's effect in user code, and that can in
practice not be more efficient than the compiler's own implementation.
 
A

Adam Zimny

Alf said:
* Adam Zimny:

I fail to see that he has claimed that.



The statement doesn't make sense, but as mentioned, I fail to see that
Bruce
Eckel has claimed something like that. Think about it. What the code
does is to implement the dynamic_cast's effect in user code, and that can
in practice not be more efficient than the compiler's own implementation.

He claims it in code comment, here is the fragment:
if(sp != 0)
cout << "It's a square!" << endl;
// Static navigation is ONLY an efficiency hack;
// dynamic_cast is always safer.
 
A

Alf P. Steinbach

* Adam Zimny:
He claims it in code comment, here is the fragment:
if(sp != 0)
cout << "It's a square!" << endl;
// Static navigation is ONLY an efficiency hack;
// dynamic_cast is always safer.

That's a very different claim, about static_cast, not about typeid, and it's
one that is correct (for polymorphic type).

Quoting Bruce's explanation of the code, emphasis added:
"notice that in this design the process is effectively the same as using
dynamic_cast, and the client programmer must do some testing to discover the
cast that was actually successful. You’ll typically want a situation that’s
_more deterministic_ than in the example above before using static_cast
rather than dynamic_cast (and, again, you want to carefully examine your
design before using dynamic_cast)"

In other words, Bruce used dynamic_cast just as a simplest possible example
of obtaining the type information, and the idea is that _if_ that type
determination can be done much more efficiently, by other means, then
static_cast can be more efficient than dynamic_cast, but not safer.
 
A

Adam Zimny

Alf said:
* Adam Zimny:

That's a very different claim, about static_cast, not about typeid, and
it's one that is correct (for polymorphic type).

Quoting Bruce's explanation of the code, emphasis added:
"notice that in this design the process is effectively the same as using
dynamic_cast, and the client programmer must do some testing to discover
the cast that was actually successful. You’ll typically want a situation
that’s _more deterministic_ than in the example above before using
static_cast rather than dynamic_cast (and, again, you want to carefully
examine your design before using dynamic_cast)"

In other words, Bruce used dynamic_cast just as a simplest possible
example of obtaining the type information, and the idea is that _if_ that
type determination can be done much more efficiently, by other means, then
static_cast can be more efficient than dynamic_cast, but not safer.
Ok, lets forget for a moment what Bruce ment, lets make a test :)

foo1:
#include <iostream>
#include <typeinfo>
using namespace std;

class Shape { public: virtual ~Shape() {}; };
class Circle : public Shape {};
class Square : public Shape {};
class Other {};

int main() {
Circle c;
Shape* s = &c; // Upcast: normal and OK
// More explicit but unnecessary:
s = static_cast<Shape*>(&c);
// (Since upcasting is such a safe and common
// operation, the cast becomes cluttering)
Circle* cp = 0;
Square* sp = 0;

int foo;
for(int i=0; i<100000000; i++){
if(typeid(s) == typeid(cp)) // C++ RTTI
cp = static_cast<Circle*>(s);
if(typeid(s) == typeid(sp))
sp = static_cast<Square*>(s);
if(cp != 0) foo=1;
if(sp != 0) foo=2;
}

} ///:~

//----------------------------------------------

foo2:
#include <iostream>
#include <typeinfo>
using namespace std;

class Shape { public: virtual ~Shape() {}; };
class Circle : public Shape {};
class Square : public Shape {};
class Other {};

int main() {
Circle c;
Shape* s = &c; // Upcast: normal and OK
// More explicit but unnecessary:
s = static_cast<Shape*>(&c);
// (Since upcasting is such a safe and common
// operation, the cast becomes cluttering)
Circle* cp = 0;
Square* sp = 0;

int foo;
for(int i=0; i<100000000; i++){
cp = dynamic_cast<Circle*>(s);
sp = dynamic_cast<Square*>(s);
if(cp != 0) foo=1;
if(sp != 0) foo=2;
}

} ///:~

g++ -o foo1 foo1.cpp
g++ -o foo2 foo2.cpp
time foo1

real 0m2.343s
user 0m2.313s
sys 0m0.000s

time foo2

real 0m12.205s
user 0m11.974s
sys 0m0.005s
 
A

Adam Zimny

in foo I used buggy Eckels version, good version with:
for(int i=0; i<100000000; i++){
if(typeid(*s) == typeid(Circle)) // C++ RTTI
cp = static_cast<Circle*>(s);
if(typeid(*s) == typeid(Square))
sp = static_cast<Square*>(s);
if(cp != 0) foo=1;
if(sp != 0) foo=2;
}
gives:
time foo3

real 0m3.136s
user 0m3.110s
sys 0m0.003s
 
A

Alf P. Steinbach

* Adam Zimny:
Ok, lets forget for a moment what Bruce ment, lets make a test :)

Here are my results with MSVC 7.1:

dynamic_cast:
$ time vc_project.exe

real 0m15.812s
user 0m0.015s
sys 0m0.015s

typeid + static_cast:
$ time vc_project.exe

real 0m20.656s
user 0m0.031s
sys 0m0.015s


Here are my results with g++ 3.4.2:

dynamic_cast:
$ time a

real 0m0.219s
user 0m0.015s
sys 0m0.031s

typeid + static_cast:
$ time a

real 0m4.047s
user 0m0.015s
sys 0m0.046s
 
A

Adam Zimny

Alf said:
* Adam Zimny:

Here are my results with MSVC 7.1:

dynamic_cast:
$ time vc_project.exe

real 0m15.812s
user 0m0.015s
sys 0m0.015s

typeid + static_cast:
$ time vc_project.exe

real 0m20.656s
user 0m0.031s
sys 0m0.015s


Here are my results with g++ 3.4.2:

dynamic_cast:
$ time a

real 0m0.219s
user 0m0.015s
sys 0m0.031s

typeid + static_cast:
$ time a

real 0m4.047s
user 0m0.015s
sys 0m0.046s
I have tryed again, tryed it also on older g++ ( 2.95.3 ), with and without
-O2 flag, tryed on c++ compiler from M$ Visual Studio .NET 2003 and always
version with typeid and static_cast was faster than version with
dynamic_cast. You must have made some mistake.
 
A

Alf P. Steinbach

* Adam Zimny:
I have tryed again, tryed it also on older g++ ( 2.95.3 ), with and without
-O2 flag, tryed on c++ compiler from M$ Visual Studio .NET 2003 and always
version with typeid and static_cast was faster than version with
dynamic_cast.

I could have understood that with some unknown compiler on an unknown
system, that would just say that that compiler+system was low quality.

However, for MSVC that result is incorrect, and I suspect that's because
you haven't turned on RTTI, which is off by default for that compiler
(similarly, perhaps you didn't turn on optimization for g++, which for
that compiler is necessary to get warnings about uninitialized etc.).

Try the following variation on Bruce's code, where both quantities are
measured in _the same_ program, more directly:


#include <ctime>
#include <iostream>
#include <typeinfo>

enum testCaseEnum{ dynamicCastTest, staticCastTest };

testCaseEnum operator++( testCaseEnum& e )
{ e = testCaseEnum( e + 1 ); return e; }

char const* nameOf( testCaseEnum e )
{ return (e==dynamicCastTest? "dynamic cast" : "static cast "); }

class Shape { public: virtual ~Shape() {}; };
class Circle : public Shape {};
class Square : public Shape {};
class Other {};

int main()
{
for( testCaseEnum test = dynamicCastTest; test <= staticCastTest; ++test )
{
Circle c;
Shape* s = &c;
Circle* cp = 0;
Square* sp = 0;

int foo;
std::clock_t const startTime = std::clock();

for(int i=0; i<10000000; i++)
{
if( test == dynamicCastTest )
{
cp = dynamic_cast<Circle*>(s);
sp = dynamic_cast<Square*>(s);
}
if( test == staticCastTest )
{
if(typeid(*s) == typeid(Circle))
cp = static_cast<Circle*>(s);
if(typeid(*s) == typeid(Square))
sp = static_cast<Square*>(s);
}
if(cp != 0) foo=1;
if(sp != 0) foo=2;
}
std::clock_t const endTime = std::clock();
std::cout << nameOf(test) << ": " << endTime - startTime << std::endl;
}
}


C:\vc_project> g++ -pedantic -Wa -O2 vc_project.cpp

C:\vc_project> a
dynamic cast: 32
static cast : 407

C:\vc_project> cl /nologo /GX /GR vc_project.cpp
vc_project.cpp

C:\vc_project> vc_project
dynamic cast: 1609
static cast : 2125

C:\vc_project> _
 
A

Adam Zimny

Sorry for my lag.
I could have understood that with some unknown compiler on an unknown
system, that would just say that that compiler+system was low quality.

However, for MSVC that result is incorrect, and I suspect that's because
you haven't turned on RTTI, which is off by default for that compiler
(similarly, perhaps you didn't turn on optimization for g++, which for
that compiler is necessary to get warnings about uninitialized etc.).

RTTI was turned on ( without it test programs crashed). The compilation was
set to "release" with default settings (plus rtti) so optimization was also
turned on (without optimization tests were slower but again static_cast
with typeid was faster).
Try the following variation on Bruce's code, where both quantities are
measured in _the same_ program, more directly:


#include <ctime>
#include <iostream>
#include <typeinfo>

enum testCaseEnum{ dynamicCastTest, staticCastTest };

testCaseEnum operator++( testCaseEnum& e )
{ e = testCaseEnum( e + 1 ); return e; }

char const* nameOf( testCaseEnum e )
{ return (e==dynamicCastTest? "dynamic cast" : "static cast "); }

class Shape { public: virtual ~Shape() {}; };
class Circle : public Shape {};
class Square : public Shape {};
class Other {};

int main()
{
for( testCaseEnum test = dynamicCastTest; test <= staticCastTest;
++test )
{
Circle c;
Shape* s = &c;
Circle* cp = 0;
Square* sp = 0;

int foo;
std::clock_t const startTime = std::clock();

for(int i=0; i<10000000; i++)
{
if( test == dynamicCastTest )
{
cp = dynamic_cast<Circle*>(s);
sp = dynamic_cast<Square*>(s);
}
if( test == staticCastTest )
{
if(typeid(*s) == typeid(Circle))
cp = static_cast<Circle*>(s);
if(typeid(*s) == typeid(Square))
sp = static_cast<Square*>(s);
}
if(cp != 0) foo=1;
if(sp != 0) foo=2;
}
std::clock_t const endTime = std::clock();
std::cout << nameOf(test) << ": " << endTime - startTime <<
std::endl;
}
}


C:\vc_project> g++ -pedantic -Wa -O2 vc_project.cpp

C:\vc_project> a
dynamic cast: 32
static cast : 407

C:\vc_project> cl /nologo /GX /GR vc_project.cpp
vc_project.cpp

C:\vc_project> vc_project
dynamic cast: 1609
static cast : 2125

C:\vc_project> _

g++ 3.2.3 athlon XP 1700 results (my compiler didn't like -Wa flag, I assume
you ment -Wall):
dynamic cast: 1160000
static cast : 70000

g++ 3.2.2 old pentium 120:
dynamic cast: 16070000
static cast : 420000

ms vc, tested by a friend of mine:
dynamic cast: 2218
static cast : 1828
 
A

Alf P. Steinbach

* Adam Zimny:
g++ 3.2.3 athlon XP 1700 results (my compiler didn't like -Wa flag, I assume
you ment -Wall):

Yep, typo. However, the above is a direct screen dump, except for paths.
"Wa" passes options to the assembler, but curiously it didn't do anything.

dynamic cast: 1160000
static cast : 70000

g++ 3.2.2 old pentium 120:
dynamic cast: 16070000
static cast : 420000

Those numbers look _very_ bogus (compare 32, mine, and 1160000, yours): I'd
check this if I were you.

ms vc, tested by a friend of mine:
dynamic cast: 2218
static cast : 1828

Unbelievable! :) How on Earth did s/he manage that?
 
A

Adam Zimny

Alf said:
* Adam Zimny:
Those numbers look _very_ bogus (compare 32, mine, and 1160000, yours):
I'd check this if I were you.
fragment of man clock
RETURN VALUE
The value returned is the CPU time used so far as a clock_t; to get
the number of seconds used, divide by CLOCKS_PER_SEC.

Although further in manual is:
CONFORMING TO
ANSI C. POSIX requires that CLOCKS_PER_SEC equals 1000000
independent of the actual resolution.

So I don't get it. Can we measure time with clock or we can not :) ?
Does it mean that with lower resolutions clocks are not counted 1,2,3,4 but
for example 100,200,300,400 ?

maybe you have got diffrent CLOCKS_PER_SEC ?
on my machine under linux CLOCKS_PER_SEC=1000000.
You also had very diffrent results with g++ an vc ( 32 to 1609 ) so maybe
that's it ?
 
Joined
Aug 29, 2011
Messages
1
Reaction score
0
Alf P. Steinbach wrote:


> cut

>>
>> // Static Navigation of class hierarchies
>> // requires extra type information:
>> if(typeid(s) == typeid(cp)) // C++ RTTI
>> cp = static_cast<Circle*>(s);
>> if(typeid(s) == typeid(sp))
>> sp = static_cast<Square*>(s);
>
> Should be
>
> if( typeid( *s ) == typeid( Circle ) ) // C++ RTTI
> cp = static_cast<Circle*>( s );
> if( typeid( *s ) == typeid( Square ) )
> sp = static_cast<Square*>( s );
>
> However, the same can be done more efficiently with dynamic_cast.
>
>
>> if(cp != 0)
>> cout << "It's a circle!" << endl;
>> if(sp != 0)
>> cout << "It's a square!" << endl;
>

thanks,
and about more efficient dynamic_cast than above version - Bruce claims that
version with typeid is efficiency trick and is faster than dynamic_cast.
But maybe he is wrong again...

---------------------------------------------------------------------------------------------------
typeid( *s ) == typeid( Circle )
typeid( *s ) == typeid( Square )

But this statement is true both times and the console reads: It's a circle! It's a square!

So this example appears to be ****ed up a little bit too much for my taste and I can't find a way to fix it, but it would most likely be useless anyway.

I think that the static_cast can be used in place of an ordinary cast (which bypasses the type checking altogether) or dynamic_cast (which does typechecking but adds unnecessary overhead) when downcasting and that's what Bruce apparently meant when talking about the efficiency here.

PS: Anybody know, why this stupid, buggy example survived in this book for some ten years?
 

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

Forum statistics

Threads
473,754
Messages
2,569,521
Members
44,995
Latest member
PinupduzSap

Latest Threads

Top