Thread: Template usage

  1. #1
    Carnivore ('-'v) Hunter2's Avatar
    Join Date
    May 2002
    Posts
    2,879

    Template usage

    Quick question:
    Code:
    class A
    {
    public:
       //Some other stuff
    
       template <typename T>
       void setData(T* data) {userData = (void*)data;}
    
       template <typename T>
       T* getData() {return (T*)userData;}
    
    protected:
       void* userData;
    };
    Would this be an 'acceptable' template usage? I'm thinking it would be convenient in that it eliminates the need for typecasting everything to void*, but it might also gets rid of typesafety in some way (though I'm not sure exactly what). Also, I'm concerned that it may generate additional code, bloating the executable slightly (relative to a manual typecast by the user), with no speed benefit at all.

    Comments anyone?

    [unrelated edit]Also, if you don't write any constructors for a derived class, will the corresponding constructors of the base class be called by default? [/unrelated edit]

    [Edit 2]And, I'm having problems finding anything useful on Google - but is it safe to delete a pointer to void (delete, not delete[])? [/Edit2]
    Last edited by Hunter2; 06-14-2005 at 08:53 PM.
    Just Google It. √

    (\ /)
    ( . .)
    c(")(") This is bunny. Copy and paste bunny into your signature to help him gain world domination.

  2. #2
    Work in Progress..... Jaken Veina's Avatar
    Join Date
    Mar 2005
    Location
    Missouri. Go Imos Pizza!
    Posts
    256
    Yep. That's a perfect use of templating. If you want to see other examples of template implementation, as well as most any aspect of classes, check out your C++ Header files (no .* extension). Those are mostly only used for declaring classes, and all the code is there. It's a great place to learn from.

  3. #3
    Carnivore ('-'v) Hunter2's Avatar
    Join Date
    May 2002
    Posts
    2,879
    Good idea Thanks.
    Just Google It. √

    (\ /)
    ( . .)
    c(")(") This is bunny. Copy and paste bunny into your signature to help him gain world domination.

  4. #4
    Registered User
    Join Date
    Jan 2005
    Posts
    7,366
    The constructor that is added if you don't write your own will call the default constructor of the base class or classes automatically. The only problem would be if your base class does not have a default constructor that can be called without arguments.

    That is a somewhat strange use of templates in that you require the user of your class to remember which type they indicated when they called setData and use that same type when they called getData. There is no reason they couldn't do:
    Code:
    A test;
    test.setData(new int(5));
    char* res = test.getData<char>();
    Last edited by Daved; 06-15-2005 at 04:50 PM.

  5. #5
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    The standard allows you to cast any pointer type to a void* and back again to the original pointer type. As Daved pointed out, the downside is that there's potential for misuse.

    If "class A" can't be a templated type, then you could use something like boost::any instead.

    gg

  6. #6
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Actually, this is not a good idea the way you're doing it. It adds the illusion of type safety without actually adding any type safety. Since you templated on the methods, it's perfectly valid to say:
    Code:
    int i = 3;
    A a;
    a.setData(&i);
    std::string *ps = a.getData<std::string>();
    float *pf = a.getData<float>();
    You're eliminating type information without any way of retrieving it.

    If you're looking for something that can store various different data types, look at Boost.Any or Boost.Variant.

    Edit: And I just realized I essentially copied the last two posts.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  7. #7
    Carnivore ('-'v) Hunter2's Avatar
    Join Date
    May 2002
    Posts
    2,879
    Thanks, Boost.Any seems to be what I'm looking for. I don't have Boost installed though, and I'd really like to figure out how to implement this on my own. It looks to me like it might be built around void* anyway, except that it preserves the typeid of the object it's holding, and throws an error if you try retrieving a value of any type other than that stored. Can anyone confirm this?
    Just Google It. √

    (\ /)
    ( . .)
    c(")(") This is bunny. Copy and paste bunny into your signature to help him gain world domination.

  8. #8
    Registered User
    Join Date
    Jan 2005
    Posts
    7,366
    I just looked at the any.hpp header and it does not use void*. It creates a wrapper on the heap for the object. You use an any_cast to get out the data in the original data type, and the any_cast fails if you attempt to get back the wrong type. The wrapper is a templated class derived from a non-templated class, which I assume is so that the any class can use a pointer to the non-templated base class and avoid having to be templated itself.

  9. #9
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Yep, Daved, that's pretty much how it works.

    Code:
    class holder_base
    {
    public:
      virtual const type_info &get_type() const = 0;
    };
    
    template <typename T>
    class holder : public holder_base
    {
      T obj;
    public:
      holder(const T &t) : obj(t) {}
    
      virtual const type_info & get_type() const
      {
        return typeid(T);
      }
    
      T & get()
      {
        return obj;
      }
    
      const T & get() const
      {
        return obj;
      }
    };
    
    class any
    {
      holder_base *ptr;
    public:
      template <typename T>
      any(const T &t)
        : ptr(new holder<T>(t))
      {}
    
      template <typename T>
      T & any_cast_impl()
      {
        if(typeid(T) == ptr->get_type()) {
          return static_cast< holder<T> *>(ptr)->get();
        } else {
          throw bad_any_cast();
        }
      }
    
      template <typename T>
      const T & any_cast_impl() const
      {
        if(typeid(T) == ptr->get_type()) {
          return static_cast< const holder<T> *>(ptr)->get();
        } else {
          throw bad_any_cast();
        }
      }
    };
    
    template <typename T>
    T & any_cast(any &a)
    {
      return a.any_cast_impl<T>();
    }
    
    template <typename T>
    const T & any_cast(const any &a)
    {
      return a.any_cast_impl<T>();
    }
    That's the gist of it.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  10. #10
    Carnivore ('-'v) Hunter2's Avatar
    Join Date
    May 2002
    Posts
    2,879
    By gum, that's a lot of work! I was expecting (modifying the structure a bit):
    Code:
    class any
    {
       void* obj;
       std::type_info type;
    public:
       template <typename T>
       any(const T& t)
       {
          obj = (void*)(new T(t));
          type = typeid(T);
       }
    
       template <typename T>
       T& whatever_cast()
       {
          if(typeid(T) != type)
             throw bad_whatever_cast;
          return *(T*)obj;
       }
    };
    Essentially, then, the difference between what I expected and Boost.Any's true implementation is:
    -Eliminate the need for a type_info member variable
    -Introduce virtual function overhead

    But with my idea, would there be any mechanism for safely deleting the object (is it safe to delete void*, or would I still have to find some way to cast it back to its original type before deleting)?
    Just Google It. √

    (\ /)
    ( . .)
    c(")(") This is bunny. Copy and paste bunny into your signature to help him gain world domination.

  11. #11
    Registered User Micko's Avatar
    Join Date
    Nov 2003
    Posts
    715
    And here's what Mr. Eckel says about delete void*

    delete void* is probably a bug

    It’s worth making a point that if you call delete for a void*, it’s almost certainly going to be a bug in your program unless the destination of that pointer is very simple; in particular, it should not have a destructor. Here’s an example to show you what happens:


    Code:
    // Deleting void pointers can cause memory leaks
    #include <iostream>
    using namespace std;
    
    class Object {
      void* data; // Some storage
      const int size;
      const char id;
    public:
      Object(int sz, char c) : size(sz), id(c) {
        data = new char[size];
        cout << "Constructing object " << id 
             << ", size = " << size << endl;
      }
      ~Object() { 
        cout << "Destructing object " << id << endl;
        delete []data; // OK, just releases storage,
        // no destructor calls are necessary
      }
    };
    
    int main() {
      Object* a = new Object(40, 'a');
      delete a;
      void* b = new Object(40, 'b');
      delete b;
    }
    The class Object contains a void* that is initialized to “raw” data (it doesn’t point to objects that have destructors). In the Object destructor, delete is called for this void* with no ill effects, since the only thing we need to happen is for the storage to be released.


    However, in main( ) you can see that it’s very necessary that delete know what type of object it’s working with. Here’s the output:


    Constructing object a, size = 40
    Destructing object a
    Constructing object b, size = 40

    Because delete a knows that a points to an Object, the destructor is called and thus the storage allocated for data is released. However, if you manipulate an object through a void* as in the case of delete b, the only thing that happens is that the storage for the Object is released – but the destructor is not called so there is no release of the memory that data points to. When this program compiles, you probably won’t see any warning messages; the compiler assumes you know what you’re doing. So you get a very quiet memory leak.


    If you have a memory leak in your program, search through all the delete statements and check the type of pointer being deleted. If it’s a void* then you’ve probably found one source of your memory leak (C++ provides ample other opportunities for memory leaks, however).


    I hope this helps!

    - Micko
    Gotta love the "please fix this for me, but I'm not going to tell you which functions we're allowed to use" posts.
    It's like teaching people to walk by first breaking their legs - muppet teachers! - Salem

  12. #12
    Magically delicious LuckY's Avatar
    Join Date
    Oct 2001
    Posts
    856
    I was attempting to do exactly what you're talking about (see this thread). Like yours, my first inclination was to use a void* but it is all but impossible to do for various reasons. I ended up writing a class that works just lke boost.any, relying solely on templates and a lot of specialization (for singleton variables, [static] arrays, and pointers). I'll post the code here with sample usage for you to review or work from, but keep in mind it must still be considered beta code (I stopped working on it about 4 months ago).
    Code:
    //
    // 06/15/05
    // [email protected]
    // http://www.geocities.com/lucky760
    //
    
    #include <sstream>
    #include <algorithm>
    #include <iostream>
    using namespace std;
    
    class Putty {
      class Generic {
      public:
        virtual const std::type_info& type_info() const = 0;
        virtual Generic* copy() const = 0;
        virtual std::ostream& output(std::ostream &out) const = 0;
      };
      
      //////////////////////////////////////////////////////////////////////////////
      
      template <typename T>
      class GenericTemplate : public Generic {
      public:
        virtual const std::type_info& type_info() const {
          return typeid(T);
        }
        
        virtual std::ostream& output(std::ostream &out) const = 0;
        
        virtual T* get_ptr() = 0;// should the pointer be const?
      };
      
      //////////////////////////////////////////////////////////////////////////////
    
      template <typename value_type>  
      class GenericSingleton : public GenericTemplate<value_type> {
        value_type _value;
      public:
        GenericSingleton(const value_type &rhs)
        : _value(rhs)
        {
        }
        
        virtual Generic* copy() const {
          return new GenericSingleton(_value);
        }
        
        virtual std::ostream& output(std::ostream &out) const {
          return out << _value;
        }
        
        virtual value_type* get_ptr() {
          return &_value;
        }
      };
      
      //////////////////////////////////////////////////////////////////////////////
      
      template <typename ptr_type>  
      class GenericPointer : public GenericTemplate<ptr_type> {
        const ptr_type *_ptr;
        
      public:
        GenericPointer(const ptr_type *&rhs)
        : _ptr(rhs)
        {
        }
        
        virtual Generic* copy() const {
          return new GenericPointer(_ptr);
        }
        
        virtual std::ostream& output(std::ostream &out) const {
          if (_ptr == 0)
            return out;
    
          return out << _ptr;
        }
        
        virtual ptr_type* get_ptr() {
          return _ptr;
        }
      };
      
      //////////////////////////////////////////////////////////////////////////////
      
      template <typename data_type>  
      class GenericArray : public GenericTemplate<data_type> {
        data_type *_data;
        size_t _el;
        
      public:
        GenericArray(const data_type *rhs, unsigned int elements)
        : _el(elements), _data(0)
        {
          if (_el == 0)
            _data = const_cast<data_type*>(rhs);
          else {
            _data = new data_type[_el];
            memcpy(_data, rhs, _el * sizeof(data_type));
          }
        }
        
        ~GenericArray() { if (_data && _el > 0) delete [] _data; }
        
        virtual Generic* copy() const {
          return new GenericArray(_data, _el);
        }
        
        virtual std::ostream& output(std::ostream &out) const {
          if (_data == 0)
            return out;
    
          std::string type = type_info().name();  
          if (type.substr(0, 4) == "char")
            return out << _data;
            
          size_t last = _el - 1;
    
          out << "{ ";
          for (size_t i = 0; i < _el; ++i) {
            out << _data[i];
            out << (i < last ? ", " : " ");
          }
          return out << "}";                
        }
        
        virtual data_type* get_ptr() {
          return _data;
        }
      };
    
      template <typename T>
      static Generic* create(const T &rhs) {
        return new GenericSingleton<T>(rhs);  
      }
      
      template <typename T>
      static Generic* create(const T *rhs) {
        return new GenericSingleton<const T*>(rhs);
      }
    
      template <typename T>
      static Generic* create(const T &rhs, unsigned int elements) {
        // this only happens for pointers
        return 0;//new GenericPointer<T*>(rhs, elements);
      }
    
      template <typename T>
      static Generic* create(const T *rhs, unsigned int elements) {
        // this only happens for arrays
        return new GenericArray<T>(rhs, elements);
      }
    
      Generic *_content;
      
    public:
      Putty() : _content(0) { }
    
      Putty(const Putty &rhs)
      : _content(rhs._content ? rhs._content->copy() : 0)
      {
      }
      
      template <typename T>
      Putty(const T &value) {
        std::string name = typeid(T).name();
        bool ptr = name.find("*") != std::string::npos;
        std::string::size_type brack = name.find("[");
        bool arr = brack != std::string::npos;
    
        if (ptr && !arr) {
          _content = create(value);//, 0);
        }
        // when T is in the form of "int"      
        else if (!arr) {
          _content = create(value);
        }    
        // when T is in the form of "int* or char[14]"
        else {
          unsigned int el;
          std::istringstream istr(name.substr(brack + 1));
          istr >> el;
          _content = create(value, el);
        }
      }
    
      Putty& swap(Putty &rhs) {
        std::swap(_content, rhs._content);
        return *this;
      }
      
      Putty& operator=(const Putty &rhs) {
        return swap(Putty(rhs));
      }
      
      template <typename T>
      Putty& operator=(const T &rhs) {
        return swap(Putty(rhs));
      }
      
      ~Putty() { if (_content) delete _content; }
    
      const std::type_info& type_info() const {
        return _content ? _content->type_info() : typeid(void);
      }
      
      friend std::ostream& operator<<(std::ostream &out, const Putty &rhs) {
        return rhs._content ? rhs._content->output(out) : out;
      }
    };
    
    
    
    
    int main() {
      int a = 110;
      int b[] = { 111, 222, 333 };
      int *c = b;
      
      char d = 'X';
      char e[] = "This is a character array!";
      char *f = e;
    
      {  
        Putty singleton(a);
        cout << singleton << endl;
        cout << "------------------------" << endl;  
        Putty array(b);
        cout << array << endl;
        cout << "------------------------" << endl;  
        Putty pointer(c);
        cout << pointer << endl;
      }
    
      cout << "=============================" << endl;
      
      {  
        Putty singleton(d);
        cout << singleton << endl;
        cout << "------------------------" << endl;  
        Putty array(e);
        cout << array << endl;
        cout << "------------------------" << endl;  
        Putty pointer(f);
        cout << pointer << endl;
      }
      
      return 0;
    }
    Quote Originally Posted by the output of that program
    110
    ------------------------
    { 111, 222, 333 }
    ------------------------
    0066F828
    =============================
    X
    ------------------------
    This is a character array!
    ------------------------
    This is a character array!

  13. #13
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    A good read on the subject: http://www.gotw.ca/gotw/085.htm

    gg

  14. #14
    Carnivore ('-'v) Hunter2's Avatar
    Join Date
    May 2002
    Posts
    2,879
    Wow, some pretty heavy reading here ^_^ Well, it looks like I'll be forced to go the boost::any route, or something similar, since void* won't do the trick. Thanks for all the info everyone!

    P.S.
    @Lucky, and anyone else interested: That code is a whole lot more involved than what I'm looking for I just wanted a mechanism by which to associate a Client object with a Socket object, inside a callback function that triggers whenever a network event occurs.
    The Socket object has only 1 member variable, which is a smart-pointer (reference counted) to a SockInfo structure containing all of the class' internals, for simple copying. As such, I could simply store the Client* (or whatever the user wants associated with the socket) as a void* in the SockInfo struct; however, in a derived class (TCPSocket), I need an additional std::queue<SendPacket> stored. For the sake of a HUGE simplicity gain, I wish for this to be stored in the same SockInfo struct. To accomplish this, I define a struct (TCPInfo) that contains the required queue as well as another void* (or whatever I replace void* with), and overload the accessor function to access (SockInfo).userData->userData instead of directly accessing (SockInfo).userData.

    As you can see, all I need is something that will allow me to store single objects of any kind, and automatically clean up whatever is stored once the holder is destroyed. boost::any seems to fit this description perfectly, since I don't need to store arrays or whatnot (or if it should become a necessity, the user ought to be smart enough to just create a struct encapsulating the array).
    Just Google It. √

    (\ /)
    ( . .)
    c(")(") This is bunny. Copy and paste bunny into your signature to help him gain world domination.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Template usage
    By Todd88 in forum C++ Programming
    Replies: 10
    Last Post: 09-12-2008, 01:25 AM
  2. Specialising a member function with a template template parameter
    By the4thamigo_uk in forum C++ Programming
    Replies: 10
    Last Post: 10-12-2007, 04:37 AM
  3. Screwy Linker Error - VC2005
    By Tonto in forum C++ Programming
    Replies: 5
    Last Post: 06-19-2007, 02:39 PM
  4. error: template with C linkage
    By michaels-r in forum C++ Programming
    Replies: 3
    Last Post: 05-17-2006, 08:11 AM
  5. oh me oh my hash maps up the wazoo
    By DarkDays in forum C++ Programming
    Replies: 5
    Last Post: 11-30-2001, 12:54 PM