I am grateful to Martin Fabian (he is not a member of ACCU) for taking time out to write and donate this review for publication.
- Francis Glassborow The title of this book is promising. So is the (description of) the author. From the back cover:
"Dr Ulrich Breymann is Professor of Technical Computer Science and Managing Director of the Institute for Computer Science and Automation at the Hochschule, Bremen. He has nearly 20 years of experience working in industrial systems analysis and software development, and is a member of the German DIN Working Group for the standardization of C++. He has written two best- selling German books on C++, edited another and is a regular contributor of C++-related articles to numerous journals and magazines."
Unfortunately, this book does not live up to the promise. Only 50 pages into the book I was thinking of demanding my money back. Continuing reading, and trying out the downloadable examples (http://www.informatik.hs-bremen.de/~brey/stlbe.html) did not alleviate my aversion. The numerous errors, bad practice, non-standard terminology, dubious comments, non-portable (even non-compilable!) and seriously exception-unsafe code, etc. spoiled my reading.
The first half of the book explains the stl and describes its contents. The main part of this is basically a repackaging of the publicly available documentation, perhaps slightly more accessible for the beginner. The second half of the book (
Part III, Beyond the STL: components and applications ) presents set operations on non-sorted containers (such as associative containers) in Chapter 6. In Chapter 7 a fast associative container is described; a hashed map implementation. For some reason overloaded operators are considered necessary (Section 7.4), '+' for union, '*' for intersection, '-' for difference and '^' for symmetric difference. (Why '<=' is not regarded as useful for expressing inclusion, I do not understand.) Chapter 8 shows a number of applications using the stl containers (why not the component developed in Chapter 7?), such as cross-referencing, indexing and a thesaurus implementation. Vectors and matrices are described in Chapter 9. For sparse matrices (Section 9.4) a comparison is made between usingstd::mapand the hash-map from Chapter 7. Chapter 10 describes external sorting, both with practically no usage of internal memory and with using the available memory (again, std::containers are used). Finally, Chapter 11 shows how std::containers can be used to implement graphs, and give some basic algorithms over such graphs (Dijkstra's shortest path, and topological sorting). There is an appendix with various code snippets that the author has found useful now and then.
I lack a clear explanation of what this book really is meant to be. "Desgning components" the title says, but I do not see that much of component design. I guess the hashed associative container, and matrices and graphs qualify as components, but there is not too much depth with respect to designing them. Their implementations are merely presented, and I hardly think this is enough for a book claiming to teach/explain how todesign components using the stl. What has Chapter 8 and the Appendix to do with designing components? Chapters 7, 9, 10 and 11 are the most "nutritious" chapters, however, they only encompass a small part of the entire book, definitely too little, and definitely too little of "designing components". Is this a book for the novice or the expert? I would have to answer "neither". It contains too many errors and bad practice to be useful for the novice. The example files are not straightforwardly compilable, for instance. As for the expert, well, the book still contains too many errors and bad practice to be enjoyably reading. In addition, the presented algorithms are well-known and straightforward.
I think that std::c++ programming is beginning to come to a mature state-of-the-art, as documented, investigated and explored by people like Stroustrup, Lakos, Meyers, Sutter and others. It seems that in the latter years our knowledge about how to use what std::c++ offers has grown and matured. We, as a community, and that includes authors and publishers of c++ books, have a responsibility to teach and present examples of good coding practices and standards. This book does not contain the type of mature high-quality std::c++ code that could be expected in the year 2000.
Nonstandard terminology:
Why is the default constructor referred to as the "standard constructor"? Also, the author goes a long way in trying to make a distinction between "abstract data types" and "implicit data types". I never did understand the distinction or why it has to be made (or what an "implicit data type" is.) It has to do with what are generally known as "adaptors", a term which is briefly mentioned in the introduction to Chapter 4.Nonportable code: :
In the second example of the book (Section 1.3.4) the author fearlessly assumes thatstd::vectoris equivalent to::iterator
int*. Admittedly, a remark that this is not guaranteed is made in Section 3.1, but that is much too late.
Bad practice: :
using namespace std;is routinely added to all header files. I understand that for the sake of brevity, it might make sense, at least in small examples in a book. Almost all books on std::c++ do this to save on space. However, most of those books also include some comment about this ("do as we say, not as we do"), as they should. Not so here. Furthermore, the downloadable files, which would represent ready-to-use "components" also include a
using namespace std;directive, and this is truly bad. Note that even files that do not include anything from std:: have a
using namespace std;directive at their top (for instance,
setalgo.h, see below). Obviously these are not compilable unless some assumption about the inclusion order is made.
Plain errors: :
In the description of the containers,const_iteratoris claimed to be returned by "container with constant elements". Of course, since constant elements are not assignable, such std::containers cannot exist, this is common knowledge. Furthermore, many of the presented functions take containers by
constref (as they should) but still try to get an
iterator(as opposed to a
const_iterator) from
begin(). Of course, this cannot and should not compile (though it seems that MSVC++6 compiles them! Other compilers issue errors).
Dubious comments:
It is claimed that container assignment can in some cases be optimised away by the compiler inserting a swap instead. The std::c++ community has recently been discussing this question on comp.lang.std.c++.moderated, and the consensus was that the claim is dubious at best, and probably incorrect.Inconsistent variable naming:
In some places, for no obvious reason, single letters are used for variables, in some cases single capitals. This makes for tough reading. Mostly, more reasonable naming schemes are adopted, though.Hard-to-grasp code:
Defining two elements in a single expression, in some cases leads to unnecessarily difficult-to-read code. Especially when one of the elements is (explicitly) initialised and the other is not (see below). This problem is emphasised by extra typename keywords (and a lack of syntax colouring, of course).Unncessary use of post-increment:
It is now well known that for iteration pre-increment usually provides better performance than post-increment. Still, the book uses post-increment throughout all the examples, thereby creating and destroying an unnecessary temporary. A good compiler may be able to optimise away that temporary, still it is not good nor common practice. In fact, in other places avoiding temporaries is explicitly a concern (Section 7.4.1, for example).Improper use of #include:
The example code makes no difference between#include'ing system-headers (
#include) and #include'ing user headers (
#include "header"). The examples only use the #include
Seriously exception-unsafe code:
For theHMap(a fast associative container developed)
operator=first removes everything from the assigned-to and then constructs a copy from the assigned-from. Obviously, if the construction of the copy throws, the resulting container is unnecessarily destroyed. This is not they way assignment is supposed to be done.
Strange advice:
Section 5.6 makes a strong argument against stl's set operations, such asset_union,
set_intersectionand the likes. The argument being that the output iterator passed to these algorithms "must refer to a container that already has enough space. When there is insufficient space, using an insert iterator does not always make sense." (Section 5.6.6). This argument rings hollow to me, though. If I pass an insert iterator for a non-empty container, then I am saying that I want the result of the operation appended to that container. If this is not what I want, I pass an insert iterator for an empty container. I see no problem with this.
Let us look at the initial part of setalgo.h, some of the problems noted above turn up there. The //** comments are mine.
#ifndef SETALGO_H #define SETALGO_H using namespace std; //** 1) Bad practice, don't do //** this 2) Does not make //** sense, nothing from std //** is included templateI also had problems with the files downloaded fromhttp://www.informatik.hs-bremen.de/~brey/stlbe.html. Opening the project workspace with MSVC++6sp3 opened an empty workspace. Trying to open the project k6.dsp, msvc++ brought up a dialog saying "This makefile was not generated by Developer Studio. Continuing will create a new Developer Studio project to wrap this makefile... Do you want to continue?" In the end, I had to create my own project files, and then files such asbool Includes(const set_type&s1, const set_type&s2) { // Is s2 contained in s1? if(&s1 ==&s2) // save time if the sets are // identical return true; typename set_type::iterator i2 = s2.begin(), i1; //** 3) How readable is this? //** 4) must be const_iterator while(i2 !=3D s2.end()) { i1 =3D s1.find(*i2++); //** 5) unnecessary use of //** postincrement if(i1 == s1.end()) // not found return false; } return true; }
setalgo.habove were not directly compilable when included into my own files due to the spurious using namespace std;. Well, at least they get the "
int main" right...