Would a static_cast be better style here?

J

Jerry Coffin

[ ... ]
For downcasts static_cast is better to be used. dynami_cast is for upcasting
and crosscasting.

It sounds like you've already figured out that this statement was
backwards. It may have already been mentioned, but when casting up a
hierarchy, you don't normally need to use an explicit cast at all.
Don't tease our curiosity. Tell it. :)

One is converting a pointer to a derived object to a pointer to an
inaccessible base. E.g.:

class base1 {};

class base2 {};

class derived : base1, base2 {};

int main() {

derived *d = new derived;

base1 *b1 = (base1 *)d;
base2 *b2 = (base2 *)d;
return 0;
}

There is no combination of static_cast, const_cast or dynamic_cast
that can give the results of C-style casts in this situation.

Since this uses private inheritance, neither an implicit conversion
nor a static_cast is allowed. A const_cast can only add or remove
cv-qualifiers, so it's a non-starter.

You could substitute reinterpret_cast for the C-style casts above, and
the compiler won't complain. The result will be incorrect though. If
you had only a single base class, there's a pretty fair chance that
things would work, even though IIRC you'd officially have undefined
behavior. When you have two (or more) base classes, however, a
reinterpret_cast _can't_ produce correct results. If you used
reinterpret_cast, the conversions to base1* and base2* would
necessarily produce pointers of those types, but with THE SAME
ADDRESS.

Since the two base classes canNOT have the same address, one of them
must be wrong. The C-style cast will actually produce two different
results (i.e. two different addresses), each containing the address of
the correct base subobject.

There are a number of variations on this theme, such as using
references instead of pointers to the objects, or using a
pointer/reference to a member instead of to the whole object.

Now (hopefully) you can see why I didn't really want to get into this:
it's a lot of explanation to cover something that virtually never
matters anyway.
Later,
Jerry.
 
I

Ioannis Vranos

Jerry Coffin said:
One is converting a pointer to a derived object to a pointer to an
inaccessible base. E.g.:

class base1 {};

class base2 {};

class derived : base1, base2 {};

int main() {

derived *d = new derived;

base1 *b1 = (base1 *)d;
base2 *b2 = (base2 *)d;
return 0;
}

There is no combination of static_cast, const_cast or dynamic_cast
that can give the results of C-style casts in this situation.


Actually there is


base1 *b1 = static_cast<base1 *>(static_cast<void *>(d));

base2 *b2 = static_cast<base2 *>(static_cast<void *>(d));


The above makes the proper pointer conversion but the above use is an
attempt to defeat the purpose of static_cast (type-relationship checking to
provide some protection).

But in reality we do not perform a casting here but a hack. The inheritance
should not be private in the first place.


So, instead of using C-style casting to do all conversions, C++ provides the
new casting types so as to provide the maximum level of protection and make
it easier to spot the dangerous conversions. For example if your program
crashes somewhere, the first thing you will do is check the
reinterpret_casts since they are the most dangerous ones.


And as i said elsewhere in summary:


static_cast converts between related types, such as one pointer to another
in the same class hierarchy. The conversion is compile-time checked.

reinterpret_cast handles conversions between unrelated types.

dynamic_cast is a form of run-time checked conversion, and the type of the
object must be *polymorphic*, that is to have virtual functions.


So if in an hierarchy and you want to perform (compile-time checked) related
type conversion use static_cast.

If you want to check at runtime if the types are related use dynamic_cast
provided that the object is polymorphic.

If you want to convert between oranges and potatoes use reinterpret_cast.






Ioannis Vranos
 
S

Steven T. Hatton

Jerry said:
One is converting a pointer to a derived object to a pointer to an
inaccessible base. E.g.:
There is no combination of static_cast, const_cast or dynamic_cast
that can give the results of C-style casts in this situation. [snip]
The C-style cast will actually produce two different
results (i.e. two different addresses), each containing the address of
the correct base subobject.

Is this per the Standard, or just how your/most/some implementations work?
There are a number of variations on this theme, such as using
references instead of pointers to the objects, or using a
pointer/reference to a member instead of to the whole object.

Now (hopefully) you can see why I didn't really want to get into this:
it's a lot of explanation to cover something that virtually never
matters anyway.
Later,
Jerry.

I've actually learned a lot by asking this question. It clarified something
for me in Java as well as a few things in C++. Until now, I had been under
the false impression that casting a Java class to a superclass type would
result in the invocation of a superclass method. I don't know what gave me
that idea. Testing it proved me wrong.

I also tested the following code and found it produces different results in
C++:
b.A::setName("Recast A");
((A)b).setName("C-cast A");//calling A::setName() on a temporary object
 
K

Kevin Goodsell

Ioannis said:
Actually there is


base1 *b1 = static_cast<base1 *>(static_cast<void *>(d));

base2 *b2 = static_cast<base2 *>(static_cast<void *>(d));


The above makes the proper pointer conversion but the above use is an
attempt to defeat the purpose of static_cast (type-relationship checking to
provide some protection).

I don't know for sure, but I really think the above is equivalent to the
reinterpret_cast version, and not correct for the same reasons.

-Kevin
 
I

Ioannis Vranos

ADDRESS.
Since the two base classes canNOT have the same address, one of them
must be wrong. The C-style cast will actually produce two different
results (i.e. two different addresses), each containing the address of
the correct base subobject.



Also the code:

#include <iostream>

class base1 {};

class base2 {};

class derived : base1, base2 {};


int main() {

using namespace std;

derived *d = new derived;


//base1 *b1 = static_cast<base1 *>(static_cast<void *>(d));
// base2 *b2 = static_cast<base2 *>(static_cast<void *>(d));

base1 *b1 = (base1 *)d;

base2 *b2 = (base2 *)d;


cout<<"\t"<<b1<<"\t"<<b2<<endl;


return 0;
}



prints the same address. I got a head ache, i 'll check more on this later.






Ioannis Vranos
 
I

Ioannis Vranos

Ioannis Vranos said:
prints the same address. I got a head ache, i 'll check more on this
later.


This was due to the empty classes/compiler optimization. So i added an int
member to each. Check this:



#include <iostream>

class base1
{
int x;
};

class base2
{
int x;
};

class derived : base1, base2 {};


int main() {

using namespace std;

derived *d = new derived;


base1 *b1=reinterpret_cast<base1 *>(d);

base2 *b2=reinterpret_cast<base2 *>(reinterpret_cast<unsigned char
*>(d)+sizeof(base1));

/* In simpler steps
unsigned char *p=reinterpret_cast<unsigned char *>(d);

base2 *b2=reinterpret_cast<base2 *>(p+sizeof(base1));
*/


base1 *b3 = (base1 *)d;

base2 *b4 = (base2 *)d;


cout<<"\t"<<b1<<"\t"<<b2<<endl;

cout<<"\t"<<b3<<"\t"<<b4<<endl;



delete d;


return 0;
}




It prints:

C:\c>temp
0x3d2448 0x3d244c
0x3d2448 0x3d244c

C:\c>



So there is a way to do it using the new C++ casts and the behaviour *is*
well defined. True it is an ugly line but this is how a line for doing hack
should look like anyway. And it is a hack not usual operation.






Ioannis Vranos
 
J

Jerry Coffin

[ ... ]
Is this per the Standard, or just how your/most/some implementations work?

The part about the addresses being unique takes (IIRC) a concatenation
of about 3 or 4 different sections, but (hopefully) isn't particularly
difficult to believe.

The part about this sort of conversion working with (and only with) a
C-style cast is in 5.4/7.

[ ... ]
I also tested the following code and found it produces different results in
C++:
b.A::setName("Recast A");
((A)b).setName("C-cast A");//calling A::setName() on a temporary object

Yes -- this is the basic difference between a virtual and a
non-virtual function. E.g.:

#include <iostream>

struct base {
void non_virtual_f() { std::cout << "base::non_virtual\n"; }
virtual void virtual_f() { std::cout << "base::virtual\n"; }
};

struct derived : base {
void non_virtual_f() { std::cout << "derived::non_virtual\n"; }
virtual void virtual_f() { std::cout << "derived::virtual\n"; }
};

int main() {
base *b = new derived;

b->non_virtual_f();
b->virtual_f();
return 0;
}

Since we're casting to a visible base class, the conversion from
derived to base can be done implicitly, but the basic effect would be
the same if we cast the address as a pointer to base explicitly. In
any case, when the function isn't virtual, the function that gets
called is determined by the type to which the pointer refers. OTOH,
if the function is virtual, then the function that gets called is
determined by the type of the object being referred TO.

BTW, depending on your compiler, there's a pretty fair chance that
you'll get a warning message if you try to compile this code -- as-is,
base::non_virtual_f is HIDDEN by derived::non_virtual_f, and writing
one function that hides another is typically a mistake (because it
leads to exactly what this code demonstrates -- the function that's
invoked depends on how you refer to the object).
Later,
Jerry.
 

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
474,164
Messages
2,570,898
Members
47,440
Latest member
YoungBorel

Latest Threads

Top