Is this void* cast safe?

J

jason.cipriani

All right, I'm in this weird situation that's hard to explain but I've
put together a small example program that represents it. Please bear
with the fact that some of the stuff in the example seems useless,
it's from a much more complex situation.

The example program is confusing for me to think about and to look at,
and even though I'm -pretty- sure it's safe, I just want to make sure.
The thing in question is casting from a class pointer to a void * then
back to a class pointer again.

=== BEGIN PROGRAM ===

#include <cassert>
#include <cstdio>
#include <typeinfo>

// 'A' is just a run-of-the-mill template class.
template <class T> class A {
public:
void doit () {
std::printf("A<%s>::doit()\n", typeid(T).name());
}
};

// 'B' is not a template class...
class B {
public:
// ... but uses A<T> below based on enum passed to constructor:
enum Type { tInt, tFloat };
B (Type t);
~B ();
void call_doit ();
private:
// call_doit_helper is templated to access va_ as an A<T>.
template <class T> void call_doit_helper ();
Type t_;
void *va_;
};

// constructor constructs appropriate A<T> but stored in void*:
B::B (Type t) : t_(t) {

if (t == tInt)
va_ = new A<int>;
else if (t == tFloat)
va_ = new A<float>;
else
assert(0);

}

// destructor calls appropriate A<T> destructor as well
B::~B () {

if (t_ == tInt)
delete static_cast<A<int> *>(va_);
else if (t_ == tFloat)
delete static_cast<A<float> *>(va_);
else
assert(0);

}

// call_doit_helper is specialized for valid types in the actual code
this is
// all taken from; but in this example the default template works
fine:
template <class T> void B::call_doit_helper () {

A<T> *a = static_cast<A<T> *>(va_);
a->doit();

}

// in the actual code this is taken from this function does much more
but
// in this example it just calls call_doit_helper depending on the
type.
void B::call_doit () {

if (t_ == tInt)
call_doit_helper<int>();
else if (t_ == tFloat)
call_doit_helper<float>();
else
assert(0);

}

int main () {

B bi(B::tInt);
B bf(B::tFloat);
bi.call_doit();
bf.call_doit();

}

=== END PROGRAM ===


In that program there is a template class A and a non-template class
B. The B class has an A<T> member, but uses T based on some value
passed to the constructor at run time. It stores the A<T> it creates
in the member variable "void *va_" and then, later, casts back to an
A<T> in the call_doit_helper<T> function.

1. There's nothing weird going on here with that cast, right?
Everything should work out OK?

2. In B's destructor, will that destroy the A<T> appropriately?

3. Is static_cast<> what I want to be using here (as opposed to
reinterpret_cast, I guess)?

Again maybe this is a silly question but I've been confusing the heck
out of myself staring at this code all day.

Thanks,
Jason
 
F

Frank Birbacher

Hi!

// 'A' is just a run-of-the-mill template class.
template <class T> class A {
public:
void doit () {
std::printf("A<%s>::doit()\n", typeid(T).name());
}
};
and

void B::call_doit () {

if (t_ == tInt)
call_doit_helper<int>();
else if (t_ == tFloat)
call_doit_helper<float>();
else
assert(0);

}

This looks like a manual virtual function dispatch. I suggest you create
a base class for all A<T> which has a virtual destructor and an abstract
doit() function.

1. There's nothing weird going on here with that cast, right?
Everything should work out OK?

Yes, all is ok.
2. In B's destructor, will that destroy the A<T> appropriately?

Yes, correct dtor.
3. Is static_cast<> what I want to be using here (as opposed to
reinterpret_cast, I guess)?

Yes, use static_cast.
Again maybe this is a silly question but I've been confusing the heck
out of myself staring at this code all day.

How about:

struct BaseDoIt
{
virtual ~BaseDoIt() {}
virtual void doit() =0;
};

//copied from your code, but BaseDoIt added:
template <class T> class A : BaseDoIt {
public:
void doit () {
std::printf("A<%s>::doit()\n", typeid(T).name());
}
};

// non template class
struct B
{
// but template ctor:
template<typename T>
B();
void call_doit();
private:
const std::auto_ptr<BaseDoIt> helper;
};

template<typename T>
B::B()
: helper(new A<T>())
{
}

void B::call_doit()
{
helper->doit();
}

This is even safer. And cleaner! And faster!

Frank
 
J

joseph cook

// non template class
struct B
{
        // but template ctor:
        template<typename T>
        B();
        void call_doit();
private:
        const std::auto_ptr<BaseDoIt> helper;

};

template<typename T>
B::B()
        : helper(new A<T>())
{

}

Template C'tor in a non template class? How would one possibly create
an object of this type (type B)?
 
E

Eric Pruneau

All right, I'm in this weird situation that's hard to explain but I've
put together a small example program that represents it. Please bear
with the fact that some of the stuff in the example seems useless,
it's from a much more complex situation.

The example program is confusing for me to think about and to look at,
and even though I'm -pretty- sure it's safe, I just want to make sure.
The thing in question is casting from a class pointer to a void * then
back to a class pointer again.


I you dont like the void*, you can always use a union.

=== BEGIN PROGRAM ===

#include <cassert>
#include <cstdio>
#include <typeinfo>

// 'A' is just a run-of-the-mill template class.
template <class T> class A {
public:
void doit () {
std::printf("A<%s>::doit()\n", typeid(T).name());
}
};

// 'B' is not a template class...
class B {
public:
// ... but uses A<T> below based on enum passed to constructor:
enum Type { tInt, tFloat };
B (Type t);
~B ();
void call_doit ();
private:
// call_doit_helper is templated to access va_ as an A<T>.
template <class T> void call_doit_helper ();
Type t_;
void *va_;
};

union {
A<int>* a_int;
A<float>* a_float;
}

// constructor constructs appropriate A<T> but stored in void*:
B::B (Type t) : t_(t) {

if (t == tInt)
va_ = new A<int>;
else if (t == tFloat)
va_ = new A<float>;
else
assert(0);

if (t == tInt) a_int = new A<int>;
}

// destructor calls appropriate A<T> destructor as well
B::~B () {

if (t_ == tInt)
delete static_cast<A<int> *>(va_);
else if (t_ == tFloat)
delete static_cast<A<float> *>(va_);
else
assert(0);

here you can get rid of the static cast.:

if (t_ == tInt)
delete a_int;
else if (t_ == tFloat)
delete a_float;
else
assert(0);

using a union is probably a little clearer, you get rid of the cast and the
void* without having to make big changes to your code wich is a good thing
if your code is complex and if it is already working.
 
E

Eric Pruneau

Frank Birbacher said:
Hi!



This looks like a manual virtual function dispatch. I suggest you create a
base class for all A<T> which has a virtual destructor and an abstract
doit() function.



Yes, all is ok.


Yes, correct dtor.


Yes, use static_cast.


How about:

struct BaseDoIt
{
virtual ~BaseDoIt() {}
virtual void doit() =0;
};

//copied from your code, but BaseDoIt added:
template <class T> class A : BaseDoIt {
public:
void doit () {
std::printf("A<%s>::doit()\n", typeid(T).name());
}
};

// non template class
struct B
{
// but template ctor:
template<typename T>
B();
void call_doit();
private:
const std::auto_ptr<BaseDoIt> helper;
};

template<typename T>
B::B()
: helper(new A<T>())
{
}

void B::call_doit()
{
helper->doit();
}

This is even safer. And cleaner! And faster!

Frank

helper is a member function of B???

Can't call a member function in the constructor initializer list...
 
J

James Kanze

<[email protected]> a écrit dans le message de (e-mail address removed)...
I you dont like the void*, you can always use a union.

Which is a lot cleaner when the number of types is limited.
Also, when an enum is involved, typically, a switch is
preferable to a string of if/else if's.

But of course Frank's solution is even cleaner, since it avoids
the type switching entirely.
 
F

Frank Birbacher

Hi!

Eric said:
helper is a member function of B???

Can't call a member function in the constructor initializer list...

"helper" is a member variable of type "const std::auto_ptr<BaseDoIt>".
It is constructed in the initializer list. Const values must be
constructed there.

Frank
 
F

Frank Birbacher

joseph said:
Template C'tor in a non template class? How would one possibly create
an object of this type (type B)?

Hmm, you are right. There is not way to explicitly specify the
instantiation to be used and there is no type deduction.

Ok, two "workarounds":

template<typename T>
B::B(T) //use parameter to deduce the type
: helper(new A<T>())
{}

Here you would supply an object of the type you need:
B b1(8); //using int
B b2(8.f); //using float

**OR:**

B::B(std::auto_ptr<BaseDoIt> inst)
: helper(inst)
{}

Here you would need to supply an instance of BaseDoIt your self:
B b1(new A<int>);
B b2(new A<float>);

Iff B was copyable you could do:

template<typename T>
B B::create()
{
return B(new A<T>);
}

Frank
 
T

Thomas J. Gritzan

Gianni said:
Apart from Frank's suggestion, you could use an Any object.

This is the austria "Any" class.
http://austria.svn.sourceforge.net/viewvc/austria/src/austria/code/at_any.h?view=markup

...or you could use a variant class (typesafe union), like
boost::variant, which might be a good idea if you have a limited set of
types.

In case of boost::variant, you then could use the visitor pattern to
operate on the object, instead of a chain of if statements.

I think that a 'typesafe union' is more useful than a 'typesafe void*',
because it is easier to work with a fixed set of types. Your code can
only have a limited number of if/else if statements anyway.
 

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,997
Messages
2,570,239
Members
46,827
Latest member
DMUK_Beginner

Latest Threads

Top