Re: Whats the best language to learn...




"Juha Nieminen" <nospam@xxxxxxxxxxxxxx> wrote in message
news:AJnqk.257$F77.123@xxxxxxxxxxxxxxxx
cr88192 wrote:
Thus IMO it's so much that "people who don't think in terms of OOP
tend to use a language like C", but the other way around: "People who
are fluent in C, maybe because it was their first language, are not
inclined towards OOP". In other words, C teaches non-OOP thinking (which
IMO is often a bad thing).


variety is a bad thing?...

you mean, like, one has to force everything into an OOP mindset, even if
it
doesn't fit?...

No, what I mean is that, in my experience, many C programmers detest
the OOP mindset even when it would be the best solution to the problem.


well, sometimes OO makes sense, yes, but not necessarily as many people
insist on it.

for example, in my uses I reserve the right to lump a whole bunch of classes
in a single header file, continue using a lot of my custom "home-brew" stuff
(and refrain from STL and friends), ...


naturally C lacks some things: OO features; dynamic typing; garbage
collection; ...

but, these can be done manually to good effect as well.

The problem with C is that since it doesn't have good native support
for any of those, hacking some kind of support ends up being awkward and
very laborious to use.


not particularly, if they are done well...


For example the Gtk+ library is an example of a C library which tries
to use OOD, but due to the limitation of C everything is rather
laborious and awkward. Almost everything must be constantly
dynamically-cast using macro calls. It works, but the resulting code can
become rather cluttered.


GTK is not exactly something I would use as a good example of how to do
things well.
GTK is more of an example of how to make an ugly mess...

there are much better ways to do OO in C, IMO...


Moreover, *creating* such a library is an enormous job which would be
much simpler with a language with better native support for OOP (not to
talk about how much more efficient the library becomes).


again, not really.

actually, I will take the stance that it is my personal opinion that
libraries, even those themselves written in C++ and using OO internally,
should in general not use OO for the external API (and should infact
constrain themselves strictly to what is available in C, if not much more).

this is actually an approach I take (actually, I typically go further, in
often avoiding using structs or shared memory or data as well). however,
exactly what I do depends some on the task in question (good API design
often being an art in itself...).


C has half of native support for modularity...

Except that it's difficult (or inefficient) to instantiate objects
which have private data, inaccessible from the outside. Also the lack of
automatic construction and destruction of such objects can be rather
hindering.


not really.

here is a simple approach:
if you don't want data to be accessible, don't give a pointer to a struct
(or, possibly, access to the struct itself). instead, if the data is to be
passed at all, pass it as an opaque void pointer or integer handle.

furthermore, if the data is to be accessed, it can be accessed easily enough
through accessor functions.


construction is provided by another common simple approach:
people don't allocate the objects themselves, rather, the objects are only
gained from special creation functions.

destruction is similar:
people don't free the objects, rather they are to be destroyed again, by
calling special functions.


in practice, this usually works pretty well (in fact, this can be called OO,
albeit all this is more of the CLOS style than the C++/Java/C# styles).


actually, for some of my APIs, all people are given, is an opaque pointer
and some acessors, and for some, they are not even given this (partly though
the API design trick of stuffing a context in a thread-local variable and
sorting things out behind the scenes).

I actually have a message passing interface that works this way (the client
has some operations for working with the message queue and getting/setting
properties, but nothing else...).


are you next going to object to, and have me then explain, how you can have
things like virtual methods and derivable object types?...

it is not, that, bad...


(for example, try to imagine an operation like surface subdivision or
simplification, where each vertex and polygon is expected to be a proper
object...).

I really can't see the problem.


the problem is that the mesh itself is almost completely destroyed and
replaced in the process, and if each vertex and face is represented as an
object, has to be destroyed and recreated.

additionally issues is that it is necessary to (and very useful to) iterate
over the arrays of vertices (apart from the faces), or to consider faces
apart from vertices, which is awkward if one assumes that they are
'physically' connected, rather than associated via indices, or keys.


when I was younger, I had much more often treated each face as its own piece
of memory with its own vertices, but over time I had abandoned this
approach, finding it far more workable to represent geometry as associated
collections of arrays, for example:
xyz vertices;
st vertices;
face-points, tell which xyz and st vecs to use;
faces, tells which points/edges to use, and possibly which texture/shader to
use;
edges (often separate from face-points, used to connect vertices and faces).

in some cases, joint face-points/edges may make sense, but I keep them
separate.


or, maybe also (for animations):
bones (info describing the basic skeleton for the model);
local bone transforms (several sets of arrays);
anim sequences (collections of bone transformations);
weight-vertices and weights (indicate how to convert the bone transforms
into usable vertices);
current xyz vertices (an array containing the calculated xyz positions);
current face and vertex normals;
....


some arrays may also contain flags (these arrays being considered part of
the arrays they reference), for example, telling which faces are currently
selected or were deleted, ...

now, often manipulations are implemented as special loops working on 1 or
more of these arrays, and usually ignoring the others.


however, having to take into account the whole structure (for example, each
face is a separate object with its own vertices), can become rather awkward
(requiring large nested loops or multiple functions), to perform presumably
simple tasks like, say, translating all of the vertices.

for example, performing a mesh-rotation in an object-based scene:
loop over the selected models;
loop over the selected meshes;
loop over each each face;
loop over each vertex;
accumulate vertex and other info.
calculate centroid and other info;
loop over the selected models;
loop over the selected meshes;
loop over each each face;
loop over each vertex;
rotate point around centroid by selected amount.

it is even worse if each model has its own local translations/rotations, ...

now, consider that many such operations need to be performed, many of which
are similar to, but only mildly different from the others (leading to bulky
and hairy code, possibly passing around magic numbers or contexts indicating
which exact operation is being performed).

now, one could theorize that these loops could be reused, but often they
can't. for example, finding all of the faces that use a certain vertex, ...
require very different loops.


now, consider that a BSP-tree is thrown into the mix. with the above
approach, nearly the whole thing would presumably have to be largely
rewritten to accomodate the addition of a BSP tree (in my case, many
operations also make use of dynamically built and re-structured BSP trees).


the solution then, is big massive arrays (in my case, usually per-model or
per mesh), which greatly simplify all this:
loop over models selected models;
loop over selected vertices;
accumulate vertex and other info.
calculate centroid and other info;
loop over models selected models;
loop over selected vertices;
rotate points.


it may seem counter-intuitive (like, that maintaining all of these arrays
would be painful), but it actually tends to be much simpler than the
alternative (considering the number and variety of operations that are to be
performed).

most operations may only apply to a small subset of the data at a time, and
in many cases, can be moved from one type of geometry to another without
much alteration.

for example:
rotating a part of the mesh need only concern itself with the currently
selected vertices;
animation operations may work purely in terms of the skeleton and ignore the
geometry;
rendering may ignore both the skeleton and parts of the model not relevant
to what it is currently drawing (it may care about xyz/st vertices, points,
faces, and textures/shaders, but safely ignore everything else);
....


for example, recently my edge construction and shadow-volume rendering code
was moved from convex polyhedra (aka: brushes), with minimal alteration, to
also work with vertex-mesh models, and then skeletal models.

mostly this was because:
edge construction cares about faces and face-points, but not much else;
the shadow-rendering code cares primarily about edges and face normals (at
each site, the code for rendering the end-caps was adapted from the code for
doing the normal drawing).

note that, for example, rendering may involve numerous steps with different
approaches (rendering dark, rendering for a given light source, rendering
the texture or a shadered-overlay, ...). these steps each using different
parts of the model and ignoring others.


of course, to really see this in action, one has to write their own 3D
modeling and animation software (plain 3D rendering makes geometry seem
simple, and one can get by using OO just fine in a renderer, but working on
meshes shows a little more complexity, and one can quickly run into walls if
things are not designed well).


IMO, this is an example of a case where the relational model generally works
best.

now, of course, this is not so much a language issue (both C++ and Java
allow fairly good array processing), none the less, it shows an area where
OOD is not well suited.


or, as a cliche: sometimes we need a screwdriver, and not to just hit
everything with a hammer.
or: when we have a hammer, not everything is a nail...


in this case, the basic unit is larger, such as, for example, a mesh (the
whole mesh is then a single big 'object').

There's nothing stopping the mesh object from containing smaller
objects inside it. That's the whole idea of data containers.


yes, but the issue is not the structuring, but in performing the operations
(this being a very different mindset, for a very different kind of task).

a lot of this is about complexity and scalability as well.


not in terms of performance, but they are heavy-weight in how they effect
the code itself...

code which overloads too many operators in too many bizarre ways is just
horrible, as is code where the writers thought it a good idea that nearly
every declaration also make use of templates...

Operator overloading can be misused to the point where it becomes
counterproductive, and the code can become a mess. However, that same
thing is true for almost anything else as well. Why single out operator
overloading precisely?


because these are very common in much C++ code I have seen.
many coders are better at using overloading badly than at using it well,
which is why there is the common notion that it is best to avoid it when not
actually needed.


actually, personally I don't much use enums either.

IMO enums are really useful, as they are a very handy shortcut which
help abstracting the code. (Not that the same couldn't be achieved with
integral constants, but enums just make it easier and quicker to make
changes.)

For example, I might typically have something like this somewhere:

enum ButtonIndex
{ OK_BUTTON, CANCEL_BUTTON, HELP_BUTTON, BUTTONS_AMOUNT };

The actual button objects are then stored in an array (sized with
BUTTONS_AMOUNT), at their respective locations, using those enumerated
values as their indices.


but, this approach is also commonly performed with '#define's, which in many
cases may offer advantages over enums.


Now if I later need to add a new button, I just add a name for it
there, and everything gets set up almost automatically. Most
importantly, if I later *remove* some button from the middle, the array
gets reduced, and the rest of the buttons get renumbered automatically
without me having to do anything about it.


and, though sometimes good, this tendency to renumber things can also be one
of the biggest detractors of enums...

very often, if we have constants, we want constants...

consider a great big mass of GL-like property names...
it would be a very bad thing if these were to change between implementations
or versions.

defines can be much better, for example, at open-ended sets, or when the
sets may partly overlap or are prone to depend on context.


defines also seem to be much faster in very large numbers (less likely to
bog down the compiler, ...).

it can be noted the almost universal absence of enums in system headers (for
example, my dynamic compiler has currently broken enum support, and yet can
still can use the various system headers, the enum issue not proving to be
much of a problem in practice).


as a potential detractor though, '#define's are inherently global wrt their
namespace (of course, many of use make use of naming conventions, rather
than scoping, to deal with possible clashes).


in this case, we see huge masses of files with maybe only 25-100 lines
each,
and it gets painful endlessly opening and closing text editor windows

I assume some Java guru would answer that you just need the proper
editor.



I use notepad.

notepad is a good enough editor, and is currently what can be presumed to be
most common (if it works in notepad, presumably nearly anyone can use it).

I also tend to stick to a rigid 80 column formatting rule as well.


all this is something that rather annoyed me about Scheme (back when I used
it a lot):
it was difficult to work with in notepad, and using emacs was annoying...

the expectation that people use IDEs for coding is much the same IMO as the
expectation that people use emacs whenever coding in LISP or Scheme...



.


Quantcast