I've heard a lot of people saying that C++ templat开发者_如何学Pythones are very powerful. I still don't seem to understand the advantages of using them instead of using inheritance.
And as I am mainly a Java developer, thought that generics and templates were one and the same thing, but accordingly to Wikipedia:
Although C++ templates, Java generics, and .NET generics are often considered similar, generics only mimic the basic behavior of C++ templates.
I'm also wondering whether using templates where one could just have used classes isn't obfuscating the code?
Templates and inheritance fulfill different roles, and it's fairly rare
where you can choose between them. One very simple description should
be that templates provide a common implementation for different
interfaces, where as inheritance provides a common interface for
different implementations. In their usual role, templates enforce
invariants over the type system at compile time; consider some of the
pre-template libraries, where a Vector
could only contain Object*
,
and everything had to derive from Object
(and you had to box things
like int
). Inserting an int
into the vector, and trying to read a
double
, was a runtime error (or simply undefined behavior), rather
than a compile time error.
I'd disagree with the quote from Wikipedia: technically, C++ templates and Java templates are almost unrelated. The goal of Java templates is to provide compile-time enforcement of invariants over the type system, which is also one of the important uses of C++ templates, but the mechanism used is completely unrelated, and C++ templates can be used for other purposes as well.
Finally, if you're using a template where just a simple class would do the job, you're abusing templates. Just because C++ has templates doesn't mean that you should make every class and function a template.
Templates occur at compile-time. Inheritance occurs at run-time. You can catch errors with templates at compile-time that you would have to unit-test inheritance for (and then hope you don't miss them). In addition, templates are cleaner and smoother than inheritance.
Consider the simple case of, in Java, List. When you have a List which is only supposed to contain, I don't know, Customers, but if in reality it holds a bunch of Object references, you can't guarantee that it doesn't contain a bunch of Animals or DatabaseConnections, and when you get it back, you have to cast and it can throw. Generics guarantee the correct result, no casts necessary. If you write a bug trying to insert something here that doesn't belong, your compiler will throw a fit. This level of security is far above what polymorphism can offer.
In addition, templates (in C++) can accept arguments other than types, like integral types, can perform compile-time type introspection, and that sort of thing. What's possible with templates is massively greater than what's possible with inheritance, and it's much safer and faster, too.
Finally, with templates, you don't have to explicitly inherit. Do I have to inherit from Addable if I want to offer operator+
? No, the template will pick it up automatically. This is more maintainable than having to explicitly specify every functionality I can inherit, as when a new library comes along, I will not have to change my code to inherit from their Addable interface. In C++, I don't have to inherit from a Callable interface to allow the use of boost::function
, even if it was developed after my function object was written. Java could never do that with inheritance. How could you even develop a class that can deal with variable numbers of arguments without generics? Write a Callable1, Callable2, Callable3 interface?
Consider another simple example. Let's say I want to add two objects together, and then subtract the result with a final object. If you have an IAddable interface, how could you possibly specify that the result of the addition operation is subtractable? And subtractable with what- I sure hope that's not another paramter? You'd basically have to write a new interface for every complex use case. Templates, on the other hand, maintain their type information all the way through, so if I'm performing more than a couple of operations, the results don't lose their information.
These are basically the same arguments as static and dynamic typing, effectively, where templates are static and inheritance is dynamic. Static types are significantly faster and less error-prone than dynamic types.
In the old days, you would have used:
List myList = new ArrayList();
myList.add(new String("Hello"));
....
String myString = (String) myList.get(0);
If somewhere you where inserting in the list a value of a different type, you would only find when retrieving the object, without information about where the real failure (inserting a wrong type of object was).
Now, you do:
List<String> myList = new ArrayList<String>();
and the error is thrown where the mistake is.
This already was controlled "in the old days", but it involved extending the ArrayList or creating a wrapper so it only accepted the valid classes; generics simplify it a lot.
I ... thought that generics and templates were one and the same thing
Actually they are almost polar opposites. C++ templates generate new types. Java Generics restrict existing types. Chalk and cheese really. It's a bit unfortunate that they have similar syntaxes.
Templates also facilitates meta-programming - a technique that can be used to have compile-time code generation (loops, multiple functions, recursion, compile-time calculations etc), static-assert implementation, restricting a class adapting only to particular type (say an int
, or having some operator/method defined).
While, for the beginners meta-programming is hard to understand and adapt, but has been widely used, specially in Boost.
Inheritance is not enough. Consider std::vector
(or perhaps more relevantly, std::vector<T>
), as an example. It gives you a container class that's type-safe (for any T
) at compile-time. That is not possible with inheritance.
If you have a type agnostic method like (p-code)
void add (object lhs, object rhs)
how would you implement it properly? lhs
and rhs
could be everything, it is not even guaranteed that both are of the same type, so what if you call it like
add ("foo@example.com", 3)
add (std::vector<int>, std::pair<Foo, float>() )
add (add, add)
?
With templates/generics, you can be explicit (p-code)
template <PARAM_TYPE>
void add (PARAM_TYPE lhs, PARAM_TYPE rhs)
And this also means you preserve type safety.
This is just one of many examples where polymorphism does not cut. Also, generics are really not the same as templates, so it is actually very hard to give a well tailored answer.
Runtime dispatch (virtual functions et al) also always comes at a potential cost. Specialized code via C++ templates is instantiated just for what you call it with, i.e. comes at no additional performance cost. Code bloat is negligible, as C++ templates are instantiated lazily (only what you really call emits to actual code) and is not a byte more than with manually specialized ("overloaded") code.
The C++ world simply consists not only of OOP [-dynamic polymorphism].
精彩评论