Journal Articles
Browse in : |
All
> Journals
> CVu
> 173
(15)
All > Journal Columns > Code Critique (70) Any of these categories - All of these categories |
Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.
Title: Student Code Critique Competition 34
Author: Administrator
Date: 05 June 2005 05:00:00 +01:00 or Sun, 05 June 2005 05:00:00 +01:00
Summary:
Body:
Prizes provided by Blackwells Bookshops & Addison-Wesley
Please note that participation in this competition is open to all members. The title reflects the fact that the code used is normally provided by a student as part of their course work.
This item is part of the Dialogue section of C Vu, which is intended to designate it as an item where reader interaction is particularly important. Readers' comments and criticisms of published entries are always welcome, as are possible samples.
Remember that you can get the current problem set in the ACCU website (http://www.accu.org/journals/). This is aimed to people living overseas who get the magazine much later than members in the UK and Europe.
Special thanks to Richard Corden for providing us with a snippet he came across.
I'm having a problem whose cause I'm not able to detect. I sometimes end up in the true block of the if statmement where iter->first is not '5'. Could you explain me what is going wrong?
#include <map> #include <algorithm> typedef std :: multimap <int, int> MyMapType; // // Filter on values between 5 and 10 struct InRange { bool operator ()(MyMapType::value_type const & value) const { return (value.second > 5) && ( value.second < 10); } }; // // Not really important how this happens. void initMap (MyMapType & map); int main () { MyMapType myMap; // initialise the map... initMap (myMap); MyMapType::iterator lower = myMap.lower_bound ( 5 ); MyMapType::iterator iter = std :: find_if ( lower, myMap.upper_bound ( 5 ), InRange () ); // // Did we find this special criterial? if (iter != myMap.end ()) { // // Yup...we have a value meeting our criteria } else { } }
From Seyed H. Haeri <shhaeri@math.sharif.edu>
First of all, I think I've got to 'fess up that this is the first time - out of three (!) - that I feel satisfied after solving the problem. In the previous times, the problem was more or less trivial, and all we'd got to do was to suggest the programmer how to improve his/her code. This time, we've got a real problem; something which is likely to happen in real programming, and, unfortunately, is not that much uncommon in the commercial world. OK, and now the code, line-by-line:
The first two lines, i.e.
#include <map> #include <algorithm>
Seem OK. Next we come across
typedef std::multimap<int, int> MyMapType;
(I'm wondering whether the lack of appropriate vertical spacing here is due to lack of enough space in the journal, or is that adjusted so by the programmer. If the latter is the case, add this to the list of drawback of the code.)
This line by itself is OK as well. The programmer has correctly observed the necessity of using typedef's, yet he/she has missed making the job complete. That is, he/she is better to add the following as well:
typedef MyMapType::const_iterator const_iterator;
The following two points about the above line worth mentioning:
First, I've chosen to use MyMapType::const_iterator over MyMapType::iterator as const-correctness implies that according to our usage. This will become clearer as we proceed. (Note that Item#26 of Effective STL does not apply here.)
Second, I've chosen typedefed MyMapType::const_iterator as const_iterator rather than iterator to clarify it for the (potential) code reader that I'm using constant iterators.
I'd like to add the following typedefs according to similar reasons:
typedef MyMapType::value_type value_type; typedef MyMapType::key_type key_type;
Next:
struct InRange { bool operator () (const MyMapType:: value_type& value) const { return (value.second > 5) && (value.second < 10); } };
Although there is no "problem" on retaining this predicate as a function object, it is better to be transformed to its pure function counterpart. (Consult Item#39 for more on the reason.)
Furthermore, those two magic numbers 5 and 10 are asking us to turn them into constants. (See Item#2 of C++ Gotchas for more on magic numbers.) As they apply only to the predicate itself, I'd prefer to make them template parameters. Therefore, here my refined version:
template <int lBound, int uBound> bool inRange(const value_type& value) { return (value > lBound) && (value < uBound); }
The comment of
void initMap(MyMapType& map);
Urges me leaving it off, and I'll obey that! Afterwards, there is no special point about the following lines.
int main() { MyMapType myMap; initMap(myMap);
until we reach
MyMapType::iterator lower = myMap.lower_bound(5);
Here is the first place where we should use const_iterator instead of MyMapType::iterator. I say that because we've nowhere tried to manipulate the result of dereferencing of lower.
Furthermore, supposing that this code is going to have enough functionality, 5 turns out to become another magic number. Thus, this is how I refine the above line:
const key_type rangeVal = 5; const_iterator lower = myMap.lower_bound(rangeVal);
The next line is where the programme goes logically wrong. Hereafter, the programmer has wrongly assumed that he/she has found "an exact" occurrence of 5 unless he's reached to the end of myMap. And that's wrong. That's exactly what has caused him to get astonished by the result of programme execution. I guess the following snippet from the Standard should make it easier to explain what's going on here (row 9 of Table 69):
"a.lower_bound(k) … returns an iterator pointing to the first element with key not less than k."
That is, it does not guarantee that it will return an iterator to an element with the exact key k. Consider the following multi-map for example
{<1, …>, <1, …>, <2, …>, <3, …>, <6, …>, …}
Where lower would return <6, …> - in which the key is not 5. Yet it is the first key not less than 5. Accordingly, I change the next line to the following:
if(lower->first == rangeVal)//if exact match found { const int lBound(5), uBound(10); const_iterator iter = std::find(lower, myMap.upper_bound(rangeVal), inRange<lBound, uBound>); if(iter != myMap.end()) { //… } else {} } }
A supplementary point about the above programme is that as Scott Meyers explains in Item#45 of Effective STL, if you need to know where the range (potentially) containing what you need is located, you should use std::equal_range(). Considering that, the programme will become:
std::pair<const_iterator, const_iterator> p = myMap.equal_range(rangeVal); //See Item#45 of Effective STL for why this works if(p.first != p.second)//if there is an exact match at all { const_iterator iter = std::find(p.first, p.second, inRange<lBound, uBound>); if (iter != p.second)//we've got a value meeting our criteria { //… } else {} //…
This programmer seems to know enough of the basics of STL programming. Yet, he/she lacks deep-enough knowledge of STL. I suggest reading the following books by order:
-
Generic Programming and the STL: Using and Extending the C++ Standard Library by Mathew H. Austern
-
Effective STL: 50 Specific Ways to Improve Your Use of Standard Template Library by Scott Meyers.
From Nick Buller <nickbuller@hotmailcom>
As has been mentioned many times the program entry point int main() is defined as returning an int value but no return statement is present in the main code block. The general program structure should be as follows:
int main() { // Program code omitted for brevity return 0; }
As mentioned in Francis' commentary for SCC28 "... the default form of the definition of main should encapsulate its code in a try block".
I also add the correct include and the using block as I prefer the code not to be littered with std::, so the main function becomes:
#include <iostream> using std::endl; using std::cerr; int main() { try { // Program code omitted for brevity } catch(...) { cerr << "An unexpected exception" " occurred." << endl; return 1; } return 0; }
As with reams of code that I have tried to decode the code contains magic numbers. I believe that the code I write will be read more often by other people then by myself (lets take it as a given they are looking for a bug). So we should try and make life simple rather than expect they automagically understand the magic number left provide them with a good starting point.
Rather than writing
return (value.second > 5) && (value.second < 10);
Write the following
static const int someMeaningFullName = 5; static const int someOtherMeaningFullName = 10; ... return (value.second > someMeaningFullName) && (value.second<someOtherMeaningFullName);
Now that you have code that can be understood a comment would not hurt.
Forgive me but MyMapType, or should it be OurMapType, Ummm. The name of the mapType should indicate its use. Unfortunately as no comments, well-defined constants or comments exist no recommendations can be made.
I dislike the following code for 3 reasons: -
MyMapType::iterator lower = myMap.lower_bound(5); MyMapType::iterator iter = std :: find_if (lower, myMap.upper_bound(5), InRange());
-
A local variable is used to store the lower bound iterator but the second is called directly and passed to the function.
-
myMap.upper_bound(5) returns an iterator by value so there is no reason not to store it in a local variable as is used for the lower_bound return or at least be consistent.
-
The call to std::find_if is split on 2 lines and the second line is not indented.
Code like this can be a nightmare to read.
MyMapType::iterator lower = myMap.lower_bound(5); MyMapType::iterator upper = myMap.upper_bound(5); MyMapType::iterator iter = std :: find_if( lower, upper, InRange());
A single line change to the code would provide a fix to the problem: -
if (iter != myMap.end())
would be replaced by
if (iter != upper) // or if (iter != myMap.upper_bound(5))
To understand what is happening we need to look at the definition of the multimap and its members:
template <class Key, class T, class Compare = less<Key>, class Allocator = allocator<T> >
The definition of myMap is only parameterised on <int, int> so the default Compare and Allocator will be used, in this case we are interest in less<Key>. If f is an object of class less<T> and x and y are objects of class T, then f(x,y) returns true if x < y and false otherwise.
const_iterator lower_bound(const key_type& x) const; const_iterator upper_bound(const key_type& x) const;
-
myMap.lower_bound(5) will find the first myMap element whose key is not less than 5, if no value is found then myMap.end() is returned.
-
myMap.upper_bound(5) will find the first myMap element whose key is greater than or equal to 5, if no value is found then myMap.end() is returned.
If the map contains the values (5, x), lower_bound will return an iterator to the one and only entry and upper bound.
I suspect that many readers were scared off by this SCC, which reflects people's lack of familiarity with std::multimap. If you are one such reader then I encourage you to explore the range of associative containers in the standard library - in my experience most people always use std::map even where another container might be better. The fundamental problem with the code shown is the test
if (iter != myMap.end ())
to detect failure to find an element in the map. What is wrong with this test? The value of iter on failure is the end of the range not the end of the container. The range ends with upper_bound(5), so this is the value against which iter must be tested to see if a match was found.
In this example the use of multimap was really a bit of a red herring and simply having a good understanding of the use of standard library iterators would have been enough to spot the bug.
A subsidiary problem is the lack of detail in report of the problem. When a failure occurs it would be more helpful to know what the value of the key is (and ideally the complete contents of the container). This could be a good place to start talking with the student about error handling, fault reporting, and designing for testability.
The first solution presented above does fix the bug and moreover covers some other problems with the code. There is even a quotation of the relevant section from the standard to explain what is going wrong. Unfortunately the standard is designed for precision and the technical vocabulary used is not always clear to casual readers!
I do however have a (minor) criticism with the above solution in that I think the first book listed may be too advanced for a student at this stage. I would suggest getting started with the collection classes via a good basic tutorial to using the STL, such as "The C++ Standard Library" by Nicolai Josuttis and progressing to Austern's book later on.
The second solution presented makes it easy for the student - a one line solution to the bug is given. The first remark - that main requires a "return 0" - is one of those things that is true in practice but not in theory… The standard states that main is a special function and that a return statement is not required. One popular compiler does not support that exclusion - so for maximum portability it is probably a good idea to add an explicit return statement.
The editor's choice is: Sayed H. Haeri
Please email <francis@robinton.demon.co.uk> to arrange for your prize.
(Submissions to scc@accu.org by July 10th)
Here is a problem with a 'C' program. Please try to both solve the student's current problem and also help them learn from this mistake.
I send an unsigned char called tipus which is meant to be some hex number from 0x00 to 0xff and which should decide the string to be returned...
If the unsigned int called valor I send is above 0xA000 I use the struct "decidetest" to send a string back, else, I send a string selected from the struct "decide".
If no substitution is made, then the string returned is always a space in html..." "
For some reason I always get the "static char escape" string returned... any idea? (the function compiles all right...)
char* Detect_type(unsigned char tipus, unsigned int valor) { int i; static char escape[16] = " "; static struct decide { unsigned num; char *string; } cadena [] = { {0x00, "Sobre V PK "}, {0x02, "Sobre V RMS "}, {0x0E, "--Power OFF--"}, {0x10, "--Power ON--"}, {0xff, " "}, }; static struct decidetest { unsigned numero; unsigned char num; char *string; } cadenatest [] = { {0x00, "Sobre V PK Test"}, {0x02, "Sobre V RMS Test"}, {0x0E, "--Power OFF--"}, {0x10, "--Power ON--"}, {0xff, " "}, }; if (valor >= 0xA000) { for(i = 0; i < sizeof(cadenatest) / sizeof(cadenatest[0]); i++) if(cadenatest[i].num == tipus) return cadenatest[i].string; } else { for(i = 0; i < sizeof(cadena) / sizeof(cadena[0]); i++) if(cadena[i].num == tipus) return cadena[i].string; } return (escape); }
Notes:
More fields may be available via dynamicdata ..