Re: Maintance of c++ code



In article <dttvmf$j0$1@xxxxxxxxxxxxxxxxxxxxxxxxxx>,
piotr5@xxxxxxxxxxxxxxxxx (Piotr Sawuk) wrote:

In article <postmaster-84682A.14091723022006@xxxxxxxxxxxxxxxxxxxxxxx>,
"Daniel T." <postmaster@xxxxxxxxxxxxx> writes:
In article <dt91dn$me4$1@xxxxxxxxxxxxxxxxxxxxxxxxxx>,
piotr5@xxxxxxxxxxxxxxxxx (Piotr Sawuk) wrote:

In article <postmaster-C30328.17344217022006@xxxxxxxxxxxxxxxxxxxxxxx>,
"Daniel T." <postmaster@xxxxxxxxxxxxx> writes:
In article <dt3m1t$11i$1@xxxxxxxxxxxxxxxxxxxxxxxxxx>,
piotr5@xxxxxxxxxxxxxxxxx (Piotr Sawuk) wrote:

In article <postmaster-CB1F1F.22283909022006@xxxxxxxxxxxxxxxxxxxxxxx>,
"Daniel T." <postmaster@xxxxxxxxxxxxx> writes:

'find_sawuk_number' and 'initialized',) finally notice the comments.

I too use comments to document the reasons why I made one choice
over the other, but your methodology to comment every single function
is new for me. Another thing to try out...

The comment is supposed to answer the question: What does the function
do? (The code tells us how it does it, but that isn't always the most
clear picture.)

I see, so for example:

template<class V>
class rIt : public std::list<V>::reverse_iterator
{
...
public:
//down-casting
rIt(Self_ s) : Self_(s) {}
//no comment needed.
rIt() : Self_() {}
//any type which can be statically cast to the base-class
template<class Itera>
rIt(const Itera& i) : Self_(static_cast<Self_>(i)) {}

//applies non-symetric operator& in reverse
static int op(const V& p1,const V& p2) {return p2 & p1;}
};

But this leaves the question where to put the comments for the
template-interface? For example above op() is contained in both,
rIt and It, so that using one of them as a template-parameter
would yield a special behaviour which isn't obvious from the code.

In this case the comment would be: "op(p1,p2) executes operator&
and fulfills the requirement that if p1 and p2 where returned by
an iterator-class defined in this object, and recursively incrementing
the iterator which pointed to p1 would eventually yield p2, then
the same is also true for cl::iterator and the operator&(p1,p2)
which gets executed by this function." Now, my question is: Is
this comment explaining "what", or "how"? What about the comment above?

The point is to provide a brief, high level description of the function.
For example:

// find a path from start to end using breadth-first search
void find_path( Tree tree, Node start, Node end );

If the comment is longer than the code it explains, it probably isn't
useful (unless it is explaining something that the code doesn't provide,
like preconditions that the code can't detect.)

You somehow keep forgetting that not OOP with virtual functions,
but OOP with templates is a whole new world for me...

That's a problem, because templates are not a method of implementing
OOP. They are not a means of doing different things with the same
(abstract) type, they are a means of doing the same thing with different
(concrete) types. Templates implement generic programming not
object-oriented programming.


You can't test every possible combination, except with trivial code. The
point of the tests in this case is to demonstrate proper usage of the
class and cover some boundary conditions so users will know what happens
then.

Of course, usually there are thousands of possibilities, but one
could categorize them into some, as you called it, boundary conditions.
However, then writing tests is as tiresome as writing the actual
code, where those boundary-conditions also need to get identified.
In my experience it's just more difficult to find an example which
would trigger such a boundary-condition, than to write code for
handling those highly unlikely cases. But the idea to merely display
proper usage isn't new for me, it's what I did in the examples posted
here -- just no asserts since my code is supposed to be used in
combination with other code which could get thoroughly tested.

Unfortunately you didn't do that, at least not in what you posted, and I
expect you didn't do it at all because the code didn't do what you said
it should do.


So, where does one unit end? Is it just the class which needs to
get tested, is it a bundle of related classes, or does it merely
encompass all classes which are related by the power of templates?
What is a unit-test anyway, and how does it apply to the kind of
OOP I demonstrated with my use of templates?

One unit == one block, one function, one class, one package, one
program... At each level there are tests that must be performed to
ensure that the particular unit in question does what it's supposed to
do.

a rarity in stl -- it's a template-library afterall. However, I
thought it was common practice to derive from a class on which
one wishes to operate without even defining a single member-variable.

No, it isn't. Better would be to set up a template parameter so that
your code will work for *any* container and not just std::list.

Then those people in the Java-course where telling bull*** when they
told me about "no differences between c++ and java"... :-)

Sorry to burst your bubble... :-)

Anyway, each container-type has a different storage-policy, and
there are many more possibilities than those few defined in stl.
In my opinion it is impossible to create code which is useful
for all cases. That's just a warning. Now I'll ty to forget this
prejudice and consider your proposal.

Your quite right. Some algorithms in the standard library work with any
container, some only work with particular containers. What's your point?

In my code I defined an iterator which does contain its very own
begin() and end() as static functions, and which is capable of
inverting itself into an iterator of the opposite direction. For
generic code, compatible with any container, I would need, as
template-parameters, the container's type, the value-type stored
in the container (remember that for example a map-container does
store a pair of objects, of which only one is of interest for me),
a function for retrieving the object of interest from that value-type,
and of course the object-type itself. That's 4 parameters of which
2 could have a default-value. (Of course I'm assuming here that
iterator and reverse_iterator are types defined in each container.)

An iterator is *any* class that can be used in an iterator context, that
is:
// needed for output iterators
*p =
p++ and ++p

// for input iterators
= *p
p->
++p and p++
p == q
p != q

// for forward iterators, all of the above

// for bidirectional iterators, all of the above and
--p and p--

// for random access iterators, all of the above and
p[]
p += q
p -= q
p < q
p > q
p <= q
p >= q


For what you are doing, the implementation of a bidirectional
reverse_iterator would be useful to look at. All it needs to be
parameterized on is the type of bidirectional iterator it's reversing.

Your iterator should work much like the reverse_iterator except you have
a method to change it's direction. The reverse_iterator template class
doesn't need to know anything about the container it is iterating over.

Of course if you want your iterator to *also* have the characteristics
of a container (ie have a begin() and end() member-function) then I'd
say your class is not separating its concerns very well.


Further you told me to evade inheriting the original iterators, and
that means that I would need to implement all the functions which are
usually part of an iterator: ++, --, *, ->, ==,... Of course those
would merely consist of calling the apropriate iterator's functions
and returning a new object initialized with the result. Also the
iterator's typedefs need to be re-implemented if I wish to use any
of stl's generic functions. Could you explain to me what gain I
would receive from this additional work?

You need to implement almost all those things anyway because your
iterators behavior is different than a list iterator's behavior.

There is a class that is defined to be inherited from and that's
std::iterator. Here is an example, my "range" iterator.

class range: public std::iterator< std::forward_iterator_tag, int >
{
public:
// sentinel iterator
range();

// creates a 'range' object that dereferences to 0. '++' increments
// the value. It will equal the sentinel when "end" is reached.
explicit range( int end );

// as above except it starts on 'start' rather than 0, and ++ will
// add "step" to the value.
range( int start, int end, int step = 1 );

const int& operator*() const { return value; }

range& operator++();
range operator++( int );

// an iterator past its end will always equal a sentinel. Otherwise, two
// 'range' objects are equal if dereferencing them will produce the same
// value, and incrementing them will produce the same value.
friend bool operator==( const range& lhs, const range& rhs );
};

bool operator!=( const range& lhs, const range& rhs ) { return !( lhs ==
rhs ); }

Yours would be more like:

template < typename BiIt >
class reversable_iterator :
public std::iterator< std::bidirectional_iterator_tag,
typename BiIt::value_type >
{
};

Notice, it's not called 'It' or 'RIt'...

Suppose my code to be useful for any container, do you think it would
still be readable with all those template-parameters?

No offense, but I don't think you could possibly loose much in the
readability department.

I can understand
that before deciding to use one particular container-type such generic
coding-style might be useful, but a quick decision in these affairs
could speed up the code and code-creation. Is the additional administrative
work really worth the hassle?

Your iterator, as described so far could work with *any* class that
conforms to the container category. Why limit it to only one specific
container?

So far I haven't met any templated code
which could be used without completely re-writing it (except for stl
and similar of course), so you obviously do have more experience in
this area. Could you please tell me from your experience how useful
such templated functions really are, and what to watch out for when
creating template-parameters and the associated classes?

Once you have a function or class working with a particular concrete
type, ask yourself, would it work with other concrete types? If so, and
those types aren't related by inheritance, then the function or class
would work well as a template...

I'm currently working on a group of template classes that implement the
active object pattern and will work with many different and otherwise
completely unrelated classes. The idea is that by wrapping an object of
some class with my "ActiveObject" all member-functions will return
immediately rather than block until complete.

I first got it working on a simple "Object" class who's methods simply
slept for a few seconds and then did some simple state change.


The main reason why I use inheritance is for code-reuse, especially
since less code inside a class-definition does also mean more
readability of the relevant changes I made to that class. Am I wrong?

As you have learned, "less code" doesn't work. :-)

No, I didn't learn such a thing. But my question was more in the
direction of maintance, and you are right that stub-functions are
more easy to maintain than static_casts.

However, what I also learned is that documenting templates with
simple comments is quite difficult. I learned a new method for
programming: first write the function the way you imagine it
would be self-explanatory, and then create the classes to support
that underlying structure your function does expect. This way I
do get small classes which contain all the differences between
possible implementations, full of static functions and members,
which are difficult to explain. Now I would like to learn how
to make such classes more readable and maintain-able.

I leave you with some good advice (IMHO) from Allen Holub:

If you can't say it in English, you can't say it in C/C++

The act of writing out an English description of what a program
does, and what each function within the program does, is really a
critical step in the thinking process. A well-constructed,
grammatically correct sentence is a mark of clear thinking. If you
can't write it down, odds are that you haven't fully thought out
the problem or the solution. Bad grammar and sentence construction
is also an indication of sloppy thinking. The first step of
writing any program, then, is to write out what the program does
and how it does it.

Do the comments first.

Just take the implementation description in the document you just
wrote and add blocks of code after each paragraph implementing the
functionality described in the paragraph. The excuse "I didn't
have the time to add in the comments" is really saying "I didn't
design the code before I wrote it and don't have time to reverse
engineer it."
.