Is this void* cast safe?

Discussion in 'C++' started by jason.cipriani@gmail.com, Jun 8, 2008.

  1. Guest

    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
     
    , Jun 8, 2008
    #1
    1. Advertising

  2. Hi!

    schrieb:
    > // '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
     
    Frank Birbacher, Jun 8, 2008
    #2
    1. Advertising

  3. joseph cook Guest

    > // 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)?
     
    joseph cook, Jun 9, 2008
    #3
  4. Eric Pruneau Guest

    <> a écrit dans le message de news:
    ...
    > 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>;
    else if(t == tFloat) a_float = 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);


    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.

    --------------

    Eric Pruneau
     
    Eric Pruneau, Jun 9, 2008
    #4
  5. Eric Pruneau Guest

    "Frank Birbacher" <> a écrit dans le message de news:
    ...
    > Hi!
    >
    > schrieb:
    >> // '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


    helper is a member function of B???

    Can't call a member function in the constructor initializer list...
     
    Eric Pruneau, Jun 9, 2008
    #5
  6. Gianni Mariani, Jun 9, 2008
    #6
  7. James Kanze Guest

    On Jun 9, 4:00 am, "Eric Pruneau" <> wrote:
    > <> a écrit dans le message de news:
    > ...


    > > 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.


    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.

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
     
    James Kanze, Jun 9, 2008
    #7
  8. Hi!

    Eric Pruneau schrieb:
    > 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
     
    Frank Birbacher, Jun 9, 2008
    #8
  9. joseph cook schrieb:
    >> // 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)?


    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
     
    Frank Birbacher, Jun 9, 2008
    #9
  10. Gianni Mariani schrieb:
    > wrote:
    > ...
    >>
    >> Again maybe this is a silly question but I've been confusing the heck
    >> out of myself staring at this code all day.

    >
    > 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.

    --
    Thomas
     
    Thomas J. Gritzan, Jun 9, 2008
    #10
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Ollej Reemt
    Replies:
    7
    Views:
    569
    Jack Klein
    Apr 22, 2005
  2. Stig Brautaset

    `void **' revisited: void *pop(void **root)

    Stig Brautaset, Oct 25, 2003, in forum: C Programming
    Replies:
    15
    Views:
    814
    The Real OS/2 Guy
    Oct 28, 2003
  3. Replies:
    5
    Views:
    851
    S.Tobias
    Jul 22, 2005
  4. Abhishek
    Replies:
    12
    Views:
    817
    Eric Sosman
    Jan 30, 2006
  5. Replies:
    1
    Views:
    420
    Victor Bazarov
    May 23, 2007
Loading...

Share This Page