what is the benefits and disadvantages of using generic methods( in compile time and run time, in performance and memory)?
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.
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).
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.
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.
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++.
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.
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.