Not having scope tied destructors hurts. There are a whole lot of
methods for simplifying code through tying to scope that can't be done
without them. The gcc compiler has an extension though that allows
this in C. Of course it is quite obviously possible to go without
these things in C, especially since you're probably not using
exceptions (setjmp/longjmp is available but pretty rarely used), it
just makes things easier and more straight forward in some people's
opinions.
Some people like being able to create a variable that is guaranteed to
have a function called on it so you can fill that function with
important things like closing file handles, releasing resources,
etc... That way you don't have to remember to do so for every error
condition and in the right order, and only those parts you've
initiated, etc... Other people find the hiding of these details in an
interface like that makes code harder to understand and prefer the
verbosity of having to clean up properly for different conditions
within the block that deals with those conditions. Neither
perspective is wrong.
Oh, please. At least to of the above tend to make code unreadable. I
mean, if you see the following line, for instance:
x = y++;
and the compiler doesn't warn on this line on highest level, then in C
we have a pretty limited set of possibilities, namely:
- x and y can both be arithmetic types
- x and y can both be pointers to the same type
- x can be a void* and y can be a typed pointer
- x or y can be preprocessor macros, though in this case that would
really be bad... oh wait, there's at least one instance of it in my
system library:
#define errno (*__errno_location())
C++ adds:
- x or y can be class instances, in which case:
- there might be a typeof y:
perator++() which is then called
- there might be an operator++(typeof y) defined somewhere else
- there might be typeof x:
perator=(typeof y&)
- there might be operator=(typeof x&, typeof y&)
- typeof x might have an implicit constructor taking a typeof y
- typeof x might have an implicit constructor taking whatever
typeof y:
perator++() or operator++(typeof y) return
- I don't know exactly about this one, but operator++(typeof y) might
even return a class instance that has an operator(typeof x)(), IIRC
All of these features are important and have their uses. They can all
be used badly. Poor design is poor design in C, C++, or any other
language. Consider this function:
void function_that_outputs_blob(blob* b);
If that function does something other than outputting a blob, whatever
that means, then people reading code that use it are not going to know
what that code does. This is exactly the same for using standard
function names like operator++ to mean something other than the
standard use or having conversion operators for types to which
conversion makes no sense.
- even if they aren't class instances, we have:
- x might be a reference to typeof y
- y might be a reference to something
- x might even reference y, which means that this line invokes
undefined behaviour
- or y might reference x, which yields the same
These later cases can all occur in C if the line is changed to:
*x += (*y)++;
As you can see, C++ blows this line all the way from "something will be
incremented and something else will store the old value" to "no friggin
idea what this line does! Might be anything. And I wouldn't even know
where to start looking"
Only if you're an idiot. I'm sorry, but it's simply true. If you
can't look at declarations of your x and y variables to see what type
they are then you are quite fucked in whatever language you choose to
be programming in.
You might of course be tempted to say something like, "But I shouldn't
have to go looking at the type to know what that line is doing." This
is of course a good argument but it is a good argument for variable
names that are more informative than 'x' and 'y'. Variable names need
to have some semantic meaning that explains what the variable is used
for. When that is done well you generally know what can be done with
the variable, which is more important than what type it is (which is
something I can let the compiler worry about).
As of yet I only saw one useful application of operator overloading and
references, and that was a typesafe printf() implementation (which
basically has the compiler choose the correct functions according to the
argument type and those functions then check whether the conversion
specification on the format string was correct).
Which is a pretty excellent example of great use of templates.
If that's the only example that you've seen though then you're
obviously not looking at a lot of C++.
Templates are yet another pothole to the learning programmer: Every
instanciation creates an entirely new and unrelated set of routines and
class variables.
As well it should. A list of widgets is not the same as a list of
blobbets. If they were then you'd instantiate the same template to
hold them both and your argument is moot.
C programmers tend to bring up this bloat "issue" a lot. It's not an
issue. In C++ you write a template to create new types. Templates
are like the preprocessor on steroids and fulfill many, but not all,
of the tasks originally reserved for macros. While I can write a
template for a container that works with any type in C++, in C I'm
stuck having to use void* or macros. The former might be a good
solution that doesn't introduce bloat, but it does require a lot of
care and may also introduce an unnecessary level of obscurity; it's
also no different from instantiating std::list<void*>. The other
option, macros, can get you a lot of the same thing, but they're MUCH
harder to use and are inherently unsafe.
If you want to write generic code that operates at the static type
level then you really need templates. Macros can almost get you there
but at a very high cost.
The one thing that can be said against templates is that they can be
hard to learn. The syntax isn't exactly optimal and the requirements
of when, where, and how to use the typename and template keywords can
be confusing. For example:
template < typename T >
struct example
{
typedef typename T::some_typedef local_typedef;
template < typename U > static void fun();
};
example<int>::template fun<double>();
It's kind of confusing and ugly. Bloated? Depends on how much
depends on the templates' parameters. If none of it does, then yeah
it's going to be unnecessarily bloated. If most of it does then no,
it's going to be about as "bloated" as it has to be.
Of course, the generic programming paradigm, which is what templates
are pretty much about, is often foreign to someone stuck in the C
world and unwilling to expand their knowledge. This is no different
from any other tool though. If you don't want to learn these things,
that's fine, but your willing ignorance isn't a good argument.
I mean, if I have:
class A { static int count; };
then there's only one A::count in the whole program. If I have
template <class T> class A {static int count; };
suddenly there's no bound to the number of A::counts in the program,
because there is _no_ A::count, but instead A<int>::count,
A<long>::count, etc.
As well it should. If you mean to count the amount of times a
particular class is instantiated then you SHOULD have a different
count for each class. Templates are not classes. They are
*templates* for creating classes...and other things. If you want to
count the amount of times any class built with that template are
instantiated then you need to account for that with a different
structure. Perhaps something like so:
struct count_these { static int count; };
template < typename T > struct A { static count_these our_count; };
This also more accurately reflects the fact that you are counting many
different things in the same bucket.