Chris said:
Steven T. Hatton wrote:
---8<---SNIP---8<--- [snip]
You say this->foo, they say m_foo, and I say _foo
Consistency seems to be the prevailing force here that separates the
good from the bad. C++ is a strongly typed language so I don't need a
prefix for types or privileges. The compiler will kindly inform me when
I need to _read_ the code better.
There are a few advantages I find in my use of this->. For one it uses the
actual semantics designed into the language to express the same basic idea
expressed by m_foo, or _foo. It also can be informative under certain
circumstances where I don't correctly understand the context, or make a
careless mistake. And one big advantage for me is that it tends to prime
the code completion of some IDEs, to include Qt Designer, and KDevelop.
[snip]
I agree with this philosophy. For example, if I have a name a string
will do. No invariants. If I have a name and an address, a struct will
do. Again, no invariants. If I have a name and an address that is
responsible for retrieving first names, last names, street, city, state,
world codes - again a struct. Use namespaces and operator overloading
etc for support. The more I add to that code the fatter and more
cluttered up I make the interface the less you _or_ I am likely to use
it in the future because it will become too specialized or complicated
to work with.
I think that really depends on the specifics. If I'm sharing the data
between classes in units which have an identifiable fixed set up data
fields, I believe a class is in oder. Sometimes breaking things down into
a lot of little pieces can lead to useless clutter, other times it can
provide us with a bunch of little 'orthogonal' building blocks with which
to implement many different designs. Sometimes what looks like it's going
to do the latter actually ends up doing the former, and vis versa.
If I have an employee record that will not warrant a full
blown class automatically. Now if this class, through design rules,
warrants opening it's own persistence, accessing of data & how, then it
now has all sorts of design invariants.
I believe you are using the term "invariant" differently than I do. But for
me, I'm a bit uncomfortable using the term in the context of computer
science. For me, an invariant is an attribute of a (mathematical) object
which is preserved under a set of 'natural' transformations. For example,
a (real) vector retains its magnitude under the group of orthogonal
rotations, and the group of translations.
Stroustrup provides the following which I can accept so long as it is
carefully adhered to when discussing computing:
http://www.giref.ulaval.ca/~ctibirna/work/readings/stroustrup-pwex.html
/************************************************
" Class Invariants
Consider a simple vector class:
class Vector {
// v points to an array of sz ints
int sz;
int* v;
public:
explicit Vector(int n); // create vector of n ints
Vector(const Vector&);
~Vector(); // destroy vector
Vector& operator=(const Vector&); // assignment
int size() const;
void resize(int n); // change the size to n
int& operator[](int); // subscripting
const int& operator[](int) const; // subscripting
};
A class invariant is a simple rule, devised by the designer of the class,
that must hold whenever a member function is called. This Vector class has
the simple invariant v points to an array of sz ints. All functions are
written with the assumption that this is true. That is, they can assume
that this invariant holds when they're called. In return, they must make
sure that the invariant holds when they return. For example:
int Vector::size() const { return sz; }
This implementation of size() looks clean enough, and it is. The invariant
guarantees that sz really does hold the number of elements, and since
size() doesn't change anything, the invariant is maintained."
************************************************/
I will add that his use of a C++ *vector* as an example of an invariant
seems potentially very misleading, though technically correct.
I'm not sure I agree with the his rule of thumb using invariance as the
determining qualification as to whether something should be a class, He
explains that here:
/************************************************
http://www.artima.com/intv/goldilocks3.html
Classes Should Enforce Invariants
Bjarne Stroustrup: My rule of thumb is that you should have a real class
with an interface and a hidden representation if and only if you can
consider an invariant for the class.
Bill Venners: What do you mean by invariant?
Bjarne Stroustrup: What is it that makes the object a valid object? An
invariant allows you to say when the object's representation is good and
when it isn't. Take a vector as a very simple example. A vector knows that
it has n elements. It has a pointer to n elements. The invariant is exactly
that: the pointer points to something, and that something can hold n
elements. If it holds n+1 or n-1 elements, that's a bug. If that pointer is
zero, it's a bug, because it doesn't point to anything. That means it's a
violation of an invariant. So you have to be able to state which objects
make sense. Which are good and which are bad. And you can write the
interfaces so that they maintain that invariant. That's one way of keeping
track that your member functions are reasonable. It's also a way of keeping
track of which operations need to be member functions. Operations that
don't need to mess with the representation are better done outside the
class. So that you get a clean, small interface that you can understand and
maintain.
************************************************/
I will typically make it a full
blown class at this point. Classes in my opinion enforce rule
invariants of the object represented and resource allocation to
construct such an object and nothing more.
I tend to think in terms of how helpful they are to the human reader in
communicating the ideas which are manifest in the program. I also often
think in terms of individual components communicating with eachother
through messages. That perspective seems to have become less emphasized in
the OO literature of the past decade than it originally was. Another
conceptual model i use is that of passing an object from one component to
another. There are thus client objects, server objects, and data objects.
It is common for an object to actually be both a client and serve (source
and sink, consumer and provider, etc.) It's less common that a server, or
client will also be the subject of a message, but it certainly happens, and
in some cases leads to very powerful solutions. But I digress...
Support functions, structs,
etc are for that. My limited experience over the last 20 years on this
shows that is what works best for _me_. KISS
I think in terms of vulnerability and dependence. That is, if I have a
reasonably restricted scope for a collection of data, and I can therefore
easily determine what operates on it, as well as what it operates on, there
is little reason to put a 'shell' around it by using private and protected
qualifers with the concomitant access methods. There are also issues of
event notification resulting from state changes. That is facilitated by
the use of access methods.
I have all kinds of production code in the wild at this point that I
would be absolutely be ashamed of taking ownership of in public.
I have to stress that I am not at all intending to shame the programmers who
wrote this code. As you can seem above, I will critically examine the
ideas of the most authoritative experts in a field. I emphasize: the code
accomplishes the intended purpose. Yes, the subsystem is currently
nonfunctional, but my car wouldn't run very well if someone were in the
process of replacing the head on the engine either.
I
Again I
bring up the argument that OOP isn't needed because you can accomplish
the same thing with modular programming and that can be done with the
tried and true methods of procedural programming.
I'm not overly comfortable with any particular definition of "modular
programming". The way I learned things, modular programming was basically
the step beyond sequential programming with non-returning branch (goto)
statements. This is represented by languages such as C and Pascal.
Different authors seem to differ on what they intend by 'modularization'.
To Roman Mæder, modularity is something akin to Ada's modules (Mathematica
Packages to be precise.) K&R are a bit nebulous regarding the meaning. The
word modularization is referenced 6 times in the index of TCPL, but I don't
see it actually present in the referenced text. They do seem to
distinguish between module and abstract data type.
BTW, this would probably be better suited to get more responses and more
active discussion from comp.programming. While technically using C++ as
an example the spirit of the discussion seems generically related to any
programming langauge.
I really hadn't intended for it to take this theoretical direction. I was
really interested in how C++ should be used in this situation, and whether
others believe it is being used optimally.
I cant fault someones style. They may not
even be mandated by the author of the code; be it good or bad.
There is a _big_ difference, in my mind, between criticizing a particular
instance of colaboratively implemented code, and finding fault in the
people involved. These programmers have chosen a very difficult task, and
are doing wonderful things with their project. Sure it could be better,
care to help?
Back to my original objectives. I find the below use of a for statement to
be inelegant, and after looking it up in the Standard, I believe it is
either a violation of the standard, or at least undefined by the standard:
fileIt = m_pCurrentAppInfo->fileList.begin();
for( ;fileIt != m_pCurrentAppInfo->fileList.end(); ++fileIt)
{
kdDebug( 9000 ) << "Process file " << (*fileIt).source << endl;
if( m_pCurrentAppInfo->subMap[(*fileIt).option] != "false" )
{
if( !copyFile( (*fileIt).source, (*fileIt).dest,
m_pCurrentAppInfo->subMap, (*fileIt).process ) )
{
KMessageBox::sorry(this, QString( i18n("The file %1 cannot be
created.")).arg( (*fileIt).dest) );
return;
}
}
}
This is what the Standard specifies a for statement to be:
/***************************************************
6.5.3 - The for statement [stmt.for]
-1- The for statement
for ( for-init-statement conditionopt ; expressionopt ) statement
is equivalent to
{
for-init-statement
while ( condition ) {
statement
expression ;
}
}
except that names declared in the for-init-statement are in the same
declarative-region as those declared in the condition, and except that a
continue in statement (not enclosed in another iteration statement) will
execute expression before re-evaluating condition. [Note: Thus the first
statement specifies initialization for the loop; the condition
(stmt.select) specifies a test, made before each iteration, such that the
loop is exited when the condition becomes false; the expression often
specifies incrementing that is done after each iteration. ]
-2- Either or both of the condition and the expression can be omitted. A
missing condition makes the implied while clause equivalent to while(true).
-3- If the for-init-statement is a declaration, the scope of the name(s)
declared extends to the end of the for-statement. [Example:
int i = 42;
int a[10];
for (int i = 0; i < 10; i++)
a
= i;
int j = i; // j = 42
--- end example]
****************************************************/
This example, though linguistically correct borders on the obsene if not the
perverse:
/* Why is the definition of it outside the scope of the for loop? Does the
programmer intend to use it after the loop? */
QStringList::Iterator it;
for (it = m_templateNames.begin(); it != m_templateNames.end(); ++it) {
kdDebug(9010) << (*it) << endl;
//...
}
/* Uh, it would appear so, but the only valid justification for having
subsequently used the variable would have been to use the result of it's
have been modified in the for loop. */
categories.sort();
for (it = categories.begin(); it != categories.end(); ++it)
insertCategoryIntoTreeView(*it); // <- And I never ever, ever do that!
This example from the same file as the above, is correct (IMO).
for ( QStringList::Iterator it =
m_pCurrentAppInfo->openFilesAfterGeneration.begin();
it != m_pCurrentAppInfo->openFilesAfterGeneration.end(); ++it ) {
(*it).replace("APPNAMEUC", getProjectName().upper());
(*it).replace("APPNAMELC", getProjectName().lower());
(*it).replace("APPNAME", getProjectName());
}