views:

204

answers:

7

what is the benefits and disadvantages of using generic methods( in compile time and run time, in performance and memory)?

+2  A: 

In Java (not sure about C++) generics are a compile-time feature. They avoid the use of potentially unsafe casts. The types a collection holds, for example, are explicitly made available to the compiler so that it knows what kinds of objects / primitives can be placed in it. This removes unsafe assumptions made by developers about what might be in a collection at some time. It also serves to improve code readability. In Java, I dont believe there are performance or memory gains to be had.

darren
Same in C++. Generics are compile-time.
Daniel Daranas
Thanks. I thought I had read in the Java tutorials that C++ worked a bit differently. They mentioned something about C++ creating a new class for each template made, whereas Java does not do this. Vague I know, but C++ isn't fresh with me anymore.
darren
@darren: C++ (may) generates new code for each type used with a template. In C++, templates are often not used with any virtual methods (think (the library formerly known as) STL). Therefore, you don't get to use RTTI on templates, which is much the same as Java in this particular aspect.
Tom Hawtin - tackline
+1  A: 

During coding time, the benefit is that you don't have to cast your objects into the specific type, thus there is some compile time safety. At runtime there is no difference (in Java).

bertolami
+2  A: 

Let's just forget the benefit in runtime, as such will be a premature optimization.

In compile time though, generic method can significantly improve readability and as a bonus, you will find a lot of errors much earlier (in compile time instead in runtime). Of course, the prerequisite of all that is that you have to define the generic as correctly as possible, not too loose and not too tight.

nanda
A: 

The main benefit in both languages is type safety: for example the compiler will guarantee for you that your List<Foo> list will contain only Foo objects. Without generics (e.g. in earlier versions of Java) your list would accept any class that inherit Object.

Performance wise Java differs from C++. In C++ a generic class is specialized during the compilation process, so there is no overhead at all.

In Java on the other hand, generics were implemented on top of an existing JVM specification so the compiler produces Java byte code that actually uses type casts, which are not free. On the other hand the alternative to generics is treating everything as an Object which also requires type casts, so there isn't really a performance difference between these two alternatives in Java.

Finally, sort of related, when generics were introduced in Java, they also added auto boxing which allows you to use primitive types with generics. Autoboxing means that a primitive type is automatically boxed in its class equivalent, i.e. an int is boxed in an Integer object when it is used in a generic context. This again adds runtime overhead since a new instance of Integer must be first created and later garbage collected.

liwp
"In C++ [...] there is no overhead at all." You are generating new code for each template instantiation. That is a shed load of overhead.
Tom Hawtin - tackline
+8  A: 

Okay, Java generics and C++ templates are so different that I'm not sure it's possible to answer them in a single question.

Java Generics

These are there pretty much for syntactic sugar. They are implemented through a controversial decision called type erasure. All they really do is prevent you from having to cast a whole lot, which makes them safer to use. Performance is identical to making specialized classes, except in cases where you are using what would have been a raw data type (int, float, double, char, bool, short). In these cases, the value types must be boxed to their corresponding reference types (Integer, Float, Double, Char, Bool, Short), which has some overhead. Memory usage is identical, since the JRE is just performing the casting in the background (which is essentially free).

Java also has some nice type covariance and contravariance, which makes things look much cleaner than not using them.

C++ Templates

These actually generate different classes based on the input type. An std::vector<int> is a completely different class than an std::vector<float>. There is no support for covariance or contravariance, but there is support for passing non-types to templates, partial template specialization. They basically allow you to do whatever you want.

However, since C++ templates create different classes for every variation of their template parameters, the size of the compiled executable is larger. Beyond that, compilation time increases greatly, since all template code must be included with each compilation unit and much more code must be generated. However, actual runtime memory footprint is typically smaller than the alternative (frees an extra void*) and performance is better, since the compiler can perform more aggressive optimizations with the known type.

EDIT (thanks David Rodríguez): While a generic Java class compiles it's entire self, when using a C++ template, you only compile what you use. So, if you create an std::vector<int> and only use push_back and size, only those functions will be compiled into the object file. This eases the size of executable problem.


If you're curious about the differences between them, check out this comparison of generics in C#, Java and C++.

Travis Gockel
There is no obligation to include all template code in each compilation unit, although it's a bit complicated to master the alternative.
Benoît
@Benoît -- I'm extremely curious: How?
Travis Gockel
At any rate, only instantiated templates are actually compiled. If you instantiate a `vector<int>` and only use `push_back`, `begin` and `end` methods, none of the other methods will get compiled.
David Rodríguez - dribeas
One other big difference is that C++ allow for a lot more than Java generics. You can specialize templates for given types (that includes partial specialization for class templates), there's SFINAE...
David Rodríguez - dribeas
@Travis G -- an export keyword is designed to eliminate the need to include a template definition in every unit using template. But only Comeau supports it.http://www.parashift.com/c++-faq-lite/templates.html#faq-35.14
Tadeusz Kopec
@Travis G: the only problem of compiling templates is their instantiation. If you define somewhere what template instantiations you need, you may use explicit instantiation "template std::MyClass<int>;" which instantiates all non-template methods. More can be done, but it's tough to explain in a comment !
Benoît
A: 

You asked for drawbacks as well, here's one.

Generic programming in C++ can yield some pretty "space-age" code which can be very verbose and difficult to read & comprehend by humans. That is, humans other than the one who designed it. As such, it can be challenging to maintain and to use. Code that is difficult to maintain or use has a big strike against it. One place where I have found this to be particularly true is in the use of policy classes.

Here's an example. Some time ago, I wrote a policy-based resource manager. Kind of like a smart pointer, but generic enough to be used for any kind of resource, not just memory. Things like mutexes, GDI (Windows) resources, etc. The motivation behind writing this was two fold. One, I just wanted to write it :) but two, I wanted to create a repository of code that could be generally useful to manage resources of all kinds. In order for it to be generally useful, people would have to want to use it.

So let me ask you, would you want to use this?

    /*** COPY POLICIES ***/
    class SimpleCopyPolicy
    {
    public:
        template<class Resource> Resource copy(const Resource& rhs) const { Resource ret = rhs; return ret; }
    protected:
        ~SimpleCopyPolicy(){};
    };

    class DuplicateHandleCopyPolicy
    {
        public:
            HANDLE sourceProcess, targetProcess;
            DWORD access, options;
            BOOL inherit;

            DuplicateHandleCopyPolicy(HANDLE sourceProcess_=GetCurrentProcess(), HANDLE targetProcess_=GetCurrentProcess(), DWORD access_=0, BOOL inherit_=FALSE,DWORD options_=DUPLICATE_SAME_ACCESS)
            : sourceProcess(sourceProcess_), targetProcess(targetProcess_), access(access_), inherit(inherit_), options(options_) {}

            template<class Resource> Resource copy(const Resource & rhs) const
            {
                Resource ret;
#               if defined(VERBOSE_STLEXT_DEBUG) & defined(MHDAPI)
                if( !verify( DuplicateHandle(sourceProcess, rhs, targetProcess, &ret, access, inherit, options) ))
                {
                    DWORD err = GetLastError();
                    mhd::WarningMessage("DuplicateHandleCopyPolicy::copy()", "Error %d Copying Handle %X : '%s'",
                    err, rhs, stdextras::strprintwinerr(err).c_str() );
                }
                else
                    mhd::OutputMessage("Duplicated %X to %X", rhs, ret);
#               else
                DuplicateHandle(sourceProcess, rhs, targetProcess, &ret, access, inherit, options);
#               endif
                return ret;
            }

        protected:
            ~DuplicateHandleCopyPolicy(){};
    };

    /*** RELEASE POLICIES ***/
    class KernelReleasePolicy
    {
    public:
        template<class Handle> bool release(Handle& h)
        {
#           if defined(VERBOSE_STLEXT_DEBUG) & defined(MHDAPI)
            OutputMessage("Closing %X", h);
#           endif
            return 0 != CloseHandle(h);
        }
    };
    class CritsecReleasePolicy
    {
    public:
        template<class Handle> bool release(Handle& h)
        {
            DeleteCriticalSection(&h);
            return true;
        }
    protected:
        ~CritsecReleasePolicy() {};
    };
    class GDIReleasePolicy
    {
    public:
        template<class Handle> bool release(Handle h) { return 0 != DeleteObject(h); }
    protected:
        ~GDIReleasePolicy(){};
    };
    class LibraryReleasePolicy
    {
    public:
        template<class Handle> bool release(Handle h) { return 0 != FreeLibrary(h); }
    protected:
        ~LibraryReleasePolicy(){};
    };
#   ifdef WINSOCK_VERSION
    class SocketReleasePolicy
    {
    public:
        template<class Handle> bool release(Handle h) { return 0 != closesocket(h); }
    protected:
        ~SocketReleasePolicy(){};
    };
#   endif

    class DestroyWindowPolicy
    {
    public:
        template<class Handle> bool release(Handle h) { return 0 != DestroyWindow(h); }
    protected:
        ~DestroyWindowPolicy() {};
    };

    /*** LOCKING POLICIES ***/
    class WFSOPolicy    // Wait For Single Object
    {
    public:
        WFSOPolicy(DWORD timeout_=INFINITE) : timeout(timeout_) {};

        template<class Handle> bool wait(Handle& h) const
        {
#           if defined(VERBOSE_STLEXT_DEBUG) & defined(MHDAPI)
            DWORD ret = ::WaitForSingleObject(h,timeout);
            if( !verify( WAIT_OBJECT_0 == ret ))
            {
                DWORD err = GetLastError();
#               ifdef UNICODE
                mhd::WarningMessage("WFSOPolicy", "Error %d Waiting for object %X [Timeout %s] : '%S'",
                    err, h, INFINITE==timeout?"INFINITE":std::formatstr("%d ms", timeout).c_str(),
                    stdextras::strprintwinerr(err).c_str() );
#               else
                mhd::WarningMessage("WFSOPolicy", "Error %d Waiting for object %X [Timeout %s] : '%s'",
                    err, h, INFINITE==timeout?"INFINITE":std::formatstr("%d ms", timeout).c_str(),
                    stdextras::strprintwinerr(err).c_str() );
#               endif
                return false;
            }
            return true;
#           else
            return WAIT_OBJECT_0 == ::WaitForSingleObject(h,timeout);
#           endif
        }

        DWORD timeout;
    };


    /*** LOCK/UNLOCK POLICIES ***/
    class CritsecLockPolicy  // CRITICAL_SECTION lock/unlock policies
    {
    public:
        template<class Handle> bool lock(Handle& h)
        {
            EnterCriticalSection(const_cast<CRITICAL_SECTION*>(&h));
            return true;
        }
        template<class Handle> bool unlock(Handle& h) 
        {
            LeaveCriticalSection(&h);
            return true;
        }
    };

    template<DWORD waitTimeout = INFINITE>
    class MutexLockPolicy : public WFSOPolicy
    {
    public:
        MutexLockPolicy() : WFSOPolicy(waitTimeout) {};
        template<class Handle> bool lock(Handle& h) const
        {
            return wait(h);
        }
        template<class Handle> bool unlock(Handle& h) const 
        {
            return 0 != ReleaseMutex(h);
        }
    };

    class PlaceboLockPolicy // this lock policy doesnt actually do anything!  useful for debugging & experimentation
    {
    public:
        PlaceboLockPolicy() {};
        template<class Handle> bool lock(Handle&) const
        {
            return true;
        }
        template<class Handle> bool unlock(Handle&) const
        {
            return true;
        }
    };


    template<class Resource, typename ReleasePolicy, typename CopyPolicy = SimpleCopyPolicy>
    class simple_auto_resource : public ReleasePolicy, public CopyPolicy
    {
    public:
        typedef simple_auto_resource<Resource,ReleasePolicy,CopyPolicy> base_type;

        simple_auto_resource() : res(0) {}
        simple_auto_resource(const Resource & r) : res(copy(r)) {}
        ~simple_auto_resource() { if(res) release(res); }

        void clear() { if(res) release(res); res = 0; }

        Resource& get() { return res; }
        const Resource& get() const { return res; }

        Resource detach() { Resource ret = res; res = 0; return ret; }

        operator const Resource&() const { return get(); }
        operator Resource&() { return get(); }

        base_type& operator=(const Resource& rhs) { clear(); res = copy(rhs); return * this; }

        template<class Comp> bool operator==(const Comp& rhs) const { return res == (Resource)rhs; }
        template<class Comp> bool operator!=(const Comp& rhs) const { return res != (Resource)rhs; }
        template<class Comp> bool operator<(const Comp& rhs) const { return res < (Resource)rhs; }
    private:
        Resource res;
    };

    typedef simple_auto_resource<HBRUSH,GDIReleasePolicy> auto_brush;
    typedef simple_auto_resource<HINSTANCE, LibraryReleasePolicy> auto_lib;
    typedef simple_auto_resource<CRITICAL_SECTION, CritsecReleasePolicy> auto_critsec;
    typedef simple_auto_resource<HWND,DestroyWindowPolicy> auto_destroy_hwnd;
    typedef simple_auto_resource<HANDLE,KernelReleasePolicy,DuplicateHandleCopyPolicy> auto_kernelobj;  
#   ifdef WINSOCK_VERSION
    typedef simple_auto_resource<SOCKET,SocketReleasePolicy> auto_socket;
#   endif

    typedef auto_kernelobj auto_mutex;
    typedef auto_kernelobj auto_event;
    typedef auto_kernelobj auto_filehandle;
    typedef simple_auto_resource<HANDLE,KernelReleasePolicy> auto_localkernelobj;   
    typedef simple_auto_resource<HANDLE,KernelReleasePolicy> auto_localmutex;   
    typedef simple_auto_resource<HANDLE,KernelReleasePolicy> auto_localevent;   
    typedef simple_auto_resource<HANDLE,KernelReleasePolicy> auto_thread;   
    typedef simple_auto_resource<HMODULE,KernelReleasePolicy> auto_hmodule;

If your answer is 'no, way too complicated,' that's precisely my point.

John Dibling
Let's see the equivalent, non-template code for comparison.
Travis Gockel
A: 

I have coded up some generic field and record classes. They do not use templates. A cool attribute is that they have a read and a write method that uses the Visitor design pattern.

One nice attribute is that you can process records without knowing the details. (BWT, a record consists of one or more fields). So a record would be read by passing a Reader (a Visitor class for reading fields) to each field and having the field use the given reader to fill its member(s). Similarly with writing.

If I need to read from an XML file or a database, I just create a Reader that specializes in reading from XML or a database. This requires no changes to the Record or Field classes. Nice, quick and easy.

One drawback is that I can't easily see the record in the debugger. I have to write code to print the record or use an if statement to trap when a specific record is encountered.

I'm amazed at how much work can be performed without knowing the details of the objects and sticking to the interfaces.

Thomas Matthews