Journal Articles
Browse in : |
All
> Journals
> CVu
> 165
(13)
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 30
Author: Administrator
Date: 06 October 2004 13:16:08 +01:00 or Wed, 06 October 2004 13:16:08 +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.
It seems that praying for more participation among members is giving good results, but anyway, let's hope I don't have to repeat the plea issue after issue. Please note that you can participate not only by submitting critiques, but also by contributing code snippets you came across which attracted your attention. Remember that you can get the current problem set on the ACCU website (http://www.accu.org/journals/). This is aimed at people living overseas who get the magazine much later than members in the UK and Europe.
Looks like an ordinary snippet, doesn't it? Amazingly, it contains various mistakes for such a few lines. Please provide a correct version.
#include <iostream> using std::cout; using std::endl; #include <list> using std::list; int main() { list<double>::iterator it; list<double> lst; *it = 34; *++it = 45; *++it = 87; it = lst.begin(); for (;it < lst.end(); ++it){ cout << it << '\t' << *it << endl; } system("pause"); return 0; }
From Tony Houghton <h@realh.co.uk>
There are three errors preventing the program from compiling. The first is that < is not a valid operator for an iterator and needs to be replaced with !=. Then it can not be printed to cout; we could print the the address of the data it references, but that's a useless piece of information in this context, so I think it's better to introduce a second variable called n showing the numerical position we've reached in the list. This is only meaningful as a cue for the user.
I've chosen to initialise it outside the loop and increment it in the loop body rather than in the loop statement to emphasise that the loop is iterating through the list and n is supplemental. The final compile error is that we haven't included <cstdlib> so system is undefined. The pause command is not portable anyway, and C++ makes such a meal of waiting for a user to press Enter that I've just deleted that line and left it up to the user to run the program in a shell or IDE that will give them a chance to read the output.
Even though it will now compile the code is still badly bugged and likely to crash. We're derefencing it without initialising it. Not only that, it is not possible to add elements to a list by writing off the end of it. We need to explicitly create a new element by appending to the list with its push_back method.
Here is my version of the code:
#include <iostream> using std::cout; using std::endl; #include <list> using std::list; int main() { list<double> lst; lst.push_back(34); lst.push_back(45); lst.push_back(87); int n = 0; for(list<double>::iterator it = lst.begin(); it != lst.end(); ++it) cout << n++ << '\t' << *it << endl; return 0; }
From Roger Orr <rogero@howzatt.demon.co.uk>
The problem posed is to provide a correct version of the code.
The first question which needs answering is what is the purpose of this code? It looks very much like someone's first experiment with STL collections and iterators - but they don't really understand what they're trying to do.
#include "Josuttis/The C++ Standard Library/Chapter 5"
There's little point simply fixing the code since the basic misunderstanding seems so great; a good tutorial/reference is probably the best place to start. However, if you insist...
The code seems to be trying to fill a list using an iterator and then print out the list to verify that it filled properly.
11 *it = 34; 12 *++it = 45; 13 *++it = 87;
Unfortunately, although a list can be populated with an iterator, a standard list::iterator is not the right sort! There are classes of iterators which are designed to allow insertion, so we need one of those to insert either at the back or the front of the list. I'll pick a back iterator since that means the items will be printed in the order they're inserted, which seems more intuitive. So let's replace these lines with:
back_insert_iterator<list<double> > ins_it(lst); *ins_it = 34; *++ins_it = 45; *++ins_it = 87;
This code is perfectly OK, but there is a potential performance issue with using pre-increment. It's probably not worth worrying the writer of the code with this just yet (but you could refer them to one of the Effective C++ books...)
Now we'll need to include another header file, <iterator>, to be compliant with the standard and add a using std::back_insert_iterator.
On that note, programmers vary on their attitude to using. Again, I wouldn't worry this programmer too much about it at this point (unless company coding rules apply!) Now the code.
The code to output the list relies on operator< for the iterator - this pretty well implies that the iterator is a random access iterator, such as an iterator over a vector. The normal paradigm for iterators in STL is to use != for the loop condition.
The loop can be coalesced: currently we have 3 parts to the loop control structure:
-
Declaration
list<double>::iterator it;
-
Initialisation
it = lst.begin();
-
Condition and update expression
it != lst.end(); ++it
I'd recommend putting these all into the for statement for clarity and to reduce the scope of it:
for(list<double>::iterator it = lst.begin(); it != lst.end(); ++it)
or, since we're trying to output from the list only,
for(list<double>::const_iterator it = lst.begin(); it != lst.end(); ++it)
Again, more advanced techniques to avoid the for loop completely are possible but would be likely to simply confuse the programmer at this point. Now, what are we actually outputting inside the loop? The code as written tries to print the iterator itself and then its contents. By analogy with 'iterator is a generalised pointer' I guess the purpose is to display the address of each item and its value. However there's not a standard operator<< defined to do this - the easiest solution is to use the & operator:
cout << &*it << '\t' << *it << endl;
or possibly, for clarity, use a helper variable:
const double &value = *it; cout << &value << '\t' << value << endl;
Now we're almost done... on a Microsoft compiler on Windows anyway :-) The last statement, system("pause"), is target environment dependent. This might be fine and if so I've no problem with do the job like this. I might like to include <cstdlib> of course, since system currently works because, on my version of the standard library, one of the other header files is pulling in <cstdlib>.
If the code has to be portable then you'd need to replace it with something equivalent (or nearly so) from the C++ library. I'm assuming the code is fine on the target OS. And that's it - to get the code working anyway. Explaining the changes - and in particular the two types of iterators needed - might be a little more work!
From Roger Leigh <rleigh@whinlatter.ukfsn.org>
Overall, the intentions of the author are obvious, but it is clear that some misunderstandings over the use of containers and iterators resulted in non-functional code. The use of headers and using statements was fine, and the general structure of the code was also acceptable, bar two lines that required more indentation.
The first major error is with the use of iterators. When assigning values to the list, the iterator is not initialised and so is invalid (cannot be dereferenced). To compound the error, on the second and third assignments, the iterator is incremented in addition to dereferencing. All these mistakes will result in undefined behaviour.
The push_back() method is probably what the student wants. It looks like there is some confusion over how iterators work. The student needs to understand that iterators "point" to items within a container, and that they are not by themselves responsible for inserting values. Likewise, while an iterator can be incremented, this is only useful when there is a next element in the container, and error checking should be done to check that the new iterator is valid. Like pointers, they need to point to a valid location, and only then may they be dereferenced to access the contained value. For insertion, an iterator would typically only be used when inserting in a specific location in the list (used with the insert() methods), or when using an insert iterator, neither of which are applicable in this case.
The for loop iterator is initialised outside the for statement. While valid, it's not necessary and is bad style. Also, the for loop conditional uses it < lst.end() rather than it != lst.end(). Not all iterators implement operator<, but all implement operator== and operator!=. We only need to know if we are at the end of the list. When outputting the list contents, the iterator is output to an ostream, which is not possible (the operator is not implemented). Iterators do not have a (user-visible) index or a meaningful address, and so if the elements should be numbered, a suitable container should be used (e.g. a vector, which allows access by index), or the numbering should be done "by hand". std::endl is used after outputting each element. This adds a newline and flushes the ostream. The flushing is unnecessary, and would have a negative impact on performance when outputting the contents of a larger container. '\n' is adequate here. A more general issue is the use of list<double>. The numbers could more easily be stored in an int, or short int. I would also have used a vector<int> myself, given that the additional features a list provides are not used, and impose a greater overhead than a vector (e.g. memory usage).
Lastly, system("pause") is both system-dependent (non-portable) and mostly useless. I've only come across its use in Windows environments in order to stop the terminal window closing on program exit. This won't work on platforms without a pause command (e.g. UNIX), and is a terminal configuration issue, not something to "fix" in the program code itself. The solution is to configure the terminal window not to close on exit, or to run the program directly from the shell. A version of the code rewritten to take the above into consideration follows:
#include <iostream> using std::cout; using std::endl; #include <vector> using std::vector; int main() { vector<int> coll; vector<int>::iterator pos; coll.push_back(34); coll.push_back(45); coll.push_back(87); int n = 0; for(pos = coll.begin(); pos != coll.end(); ++n, ++pos) cout << n << '\t' << *pos << '\n'; return 0; }
From Nevin Liber <nevin@eviloverlord.com>
Where to begin, where to begin...
cout << it list<double>::iterator it; //... cout << it //...
it is an iterator, not a pointer, and there is no standard way to output it to a stream. Guessing here that the intent was to display the address of the element in question, the following will work:
cout << &*it //...
The dereference operator*() is called on the iterator, returning a reference to the element. Then the address-of operator&() is called upon that, yielding the address of the element. Note: if the container were of a user defined type instead of double, this idiom might not work if the user defined type overloaded operator&().
system("pause")
system() is not a built in function. One way to get its function prototype would be to #include a header which contained it, such as #include<cstdlib>. Typically, system("pause") would call another program called pause. While not a syntax error, my computer does not contain such a program, and rather than guess at its semantics (wait for a certain amount of time, wait for a key to be pressed, wait for a signal, etc.), I'm going to leave it out for the rest of this discussion.
it < lst.end()
Iterators are not pointers. list in particular has bidirectional iterators, and should only be compared for equality (operator==()) or inequality (operator!=()). Note: not all compilers will catch this at compile time, depending on how its particular implementation of list<T>::iterator was written. Whether or not it is a syntax error, it is definitely a semantic error, and the fix is
it != lst.end()
Now that we are done with syntactical errors, on to the purely semantic ones.
Looking at our declaration:
list<double>::iterator it; list<double> lst;
it does not refer to any particular list. I'll actually fix this later, since it won't be needed in the fix for the next three lines of code anyway...
*it = 34; *++it = 45; *++it = 87;
This is one of the most common errors I've seen when people start using the standard containers. I believe the intent here is to have a list of three items. However, *it is illegal, not only because it doesn't refer to lst, but even if it did, lst started out empty, and the dereference of any iterator into an empty collection is illegal.
Since there are only three elements, the simplest way to create this is:
lst.push_back(34); lst.push_back(45); lst.push_back(87);
Where push_back() places the element on the very end of the list. Note: there is an implicit conversion of each of these numbers from type int to double, which may or may not happen at compile time.
Since it isn't actually needed until the for loop, just declare it there.
Before doing so, I'm going to add the following typedef to the beginning of main() for a little bit of defensive programming:
typedef list<double> Collection;
The reason is that the iterator always has to match the type of the collection, so if you need to change this, it only has to be changed in one place. Now, one should pick a better name than Collection; however, better names will not include the word list or double, as that is what we are trying to abstract away. Names should reflect what something is for, not how it is implemented.
The typedef is placed inside main() itself because no one outside of main() cares about it. Always limit scope as much as possible.
Putting this all together:
#include <iostream> using std::cout; using std::endl; #include <list> using std::list; #include <cstdlib> // for system(char const*) int main() { typedef list<double> Collection; Collection lst; lst.push_back(34); lst.push_back(45); lst.push_back(87); for(Collection::iterator it = lst.begin(); it != lst.end(); ++it) cout << &*it << '\t' << *it << endl; system("pause"); return 0; }
At this point, we could be done. The above code works, is simple and straightforward, and does the job requested.
However, if this were the foundation of a bigger project, I might add a little more complexity for future flexibility. This is ultimately a judgement call; do too little, and you have under engineered the solution; do too much, and you have over engineered it. That is why I say plan for a reasonable future, and not all futures.
A future that I consider reasonable is one where the number of initial elements may be larger than 3. push_back() works fine for 3 elements. But what about 30? Or 300?
Making it data driven is more scaleable. The data driven way is to initialize the list from an array, as in:
static const Collection::value_type initialElements[] = {34, 45, 87,}; Collection lst(initialElements, initialElements + 3);
Note: I prefer using Collection::value_type over double for the type of the elements in the array to emphasize that the type here should correspond to the type of the elements in lst. By typedefing Collection, I have self-documented that there is a relation between initialElements, lst, and it. While iterators are not (necessarily) pointers, pointers can be used in place of iterators, and list has a templated constructor that can take any type of input iterator. There still is the matter of initialElements + 3. It is less error prone to have the computer count the number of elements than for me to count it. Using a C idiom:
Collection lst(initialElements, initialElements + sizeof initialElements / sizeof initialElements[0]);
While I can do this without any helper functions, it is still error prone. If initialElements was a pointer instead of an array (which could happen if this code had changed to pass in a pointer to an array of initial values), the calculation would be wrong, yet the code would still compile and run. To solve this, I have a set of templates that always gets this calculation right:
#include <cstddef> // for size_t template <typename T, size_t N> inline size_t size(T (&)[N]) { return N; } template <typename T, size_t N> inline T* begin(T (&a)[N]) { return a; } template <typename T, size_t N> inline T* end(T (&a)[N]) { return a + N; }
Basically, they make an array look like a collection, with begin(), end(), and size() functions (although they are free functions, not member functions). The array is passed in by reference to these functions, and uses template argument deduction to determine the number of elements in the array. Note: if we pass in a pointer instead of an array, it is a compile time error. In this case, they are used as follows:
Collection lst(begin(initialElements), end(initialElements));
Note: technically, instead of begin(initialElements) as the first iterator, I could have passed in initialElements directly, as the array will decay into a pointer when passed by value. I prefer the former, both as it is self-documenting, and I get an extra level of checking that initialElements is an array. Combining all of this, we get the following solution:
#include <iostream> using std::cout; using std::endl; #include <list> using std::list; #include <cstdlib> // for system(char const*) #include <cstddef> // for size_t template <typename T, size_t N> inline size_t size(T (&)[N]) { return N; } template <typename T, size_t N> inline T* begin(T (&a)[N]) { return a; } template <typename T, size_t N> inline T* end(T (&a)[N]) { return a + N; } int main() { typedef list<double> Collection; static const Collection::value_type initialElements[] = {34, 45, 87,}; Collection lst(begin(initialElements), end(initialElements)); for(Collection::iterator it = lst.begin(); it != lst.end(); ++it) cout << &*it << '\t' << *it << endl; system("pause"); return 0; }
From Mick Brooks <michael.brooks@physics.ox.ac.uk>
This is my first attempt at an SCC solution, so I may learn more than our student. Anyway, here goes:
Trying to compile your code, GCC flags the line with the for loop as an error. Trying to evaluate it < lst.end() is the problem, since the list::iterator is a bidirectional iterator, and so doesn't have the less-than operation defined. It would work if we used a vector container instead of the list, since vector::iterator is a random-access iterator which is more powerful, and has more operations defined for it, including one for less-than. In order to make this loop do what was intended, we can look to a C++ idiom for help: use != as the loop condition check. This operation is defined for all of the five iterator categories, and so will work for iterators over any of the standard container types. The loop still looks unusual, because the idiomatic style is to make use of the initialiser part of the for-loop syntax. The maintenance programmer (which could be you in about 6 months) will see that combination of (; and will needlessly have to wonder if that's a mistake. Make it explicit, and put in the initialization. This has the wonderful side-effect of limiting the scope of the it variable. While we are here, we can notice that you don't modify the value pointed to by the iterator it, and can make that explicit in the code by using a const_iterator instead. So now the loop looks like this:
for(list<double>::const_iterator it = lst.begin(); it != lst.end(); ++it) cout << it << '\t' << *it << endl;
which is made much clearer to a C++ programmer through its use of the standard idioms.
Unfortunately, the code still won't compile; this time because there is no output operator (<<) defined for the const_iterator type. I assume that the intention of cout << it was to print the address of the object pointed to by the iterator. This might just happen to work if list iterators were actually pointers, but they are more complicated than that. What I think you want here is &*it, which is the address-of operator applied to the result of dereferencing the iterator, and gives us the memory address of the double that is pointed to by the iterator.
Well, now the code will compile, but running it gives a segmentation fault on my Linux machine, which tells us that we aren't finished yet. This is due to using an iterator to access memory that we don't own, and means that there's some work to be done on understanding how to create our list. In the first line of main(), you define the iterator it but don't give it a value, which leaves it in an unknown state. Trying to dereference that iterator is a big mistake, which causes the segfault. The iterator would have to be made to point to a valid member of a list before we can use it, but there are no valid members of an empty list. We have to find another way of populating the list. My preferred solution would be to use push_back on the list and drop the iterator altogether, which leaves us with the following working code:
#include <iostream> using std::cout; using std::endl; #include <list> using std::list; int main() { list<double> lst; lst.push_back(34); lst.push_back(45); lst.push_back(87); for(list<double>::const_iterator it = lst.begin(); it != lst.end(); ++it) cout << &*it << '\t' << *it << endl; system("pause"); // if we must... return 0; }
As an aside, if you really want to use an iterator interface to do the job, you'll have to learn about Insert Iterators. A back_insert_iterator will call push_back for you, and you can then fill the list with the iterator interface, like this:
#include <iterator> using std::back_insert_iterator; // other includes, as before int main() { list<double> lst; back_insert_iterator<list<double> > it(lst); *it = 34; *it++ = 45; *it++ = 87; // ... as before
I'm not sure what this buys you though, except that you get to learn about the added confusion that it++ and ++it are no-ops, and so both the increments in that snippet could be dropped.
From Terje Slettebø <tslettebo@broadpark.no>
First, some comments about style, before we examine correctness. The program starts with:
#include <iostream> using std::cout; using std::endl; #include <list> using std::list;
Now, in this case, for a source file (not header file), it's generally safe to do:
using namespace std;
instead of the using-declarations. Any name clashes will be flagged by the compiler, and you may save yourself considerable amounts of typing this way. I know this is a hot topic, but anyway. Another alternative is explicit qualification of the names in the code: std::cout << it.
The code has a weird indentation, with some lines indented a couple of places for no apparent reason at all. This gives the code a messy/untidy look, and clarity is important; unclear code is a good breeding ground for bugs. Moving beyond pure layout, there are some other general comments that can be made:
-
It's a good idea to initialise the variables at the point of declaration, if possible. This avoids the chance of accidentally accessing an uninitialised variable. This is actually happening in the code (and that's just one of the bugs): it is declared but not initialised, and then subsequently used, leading to undefined behaviour.
-
Also, you should not "reuse" a variable for a different purpose, as the variable it is in the code (it's reused for the loop) (this is a bad case of reuse. ;) ).
-
Keep scopes as small as possible. If it is declared in the for-loop, it only exists in the loop, and you avoid accidentally using it after it should no longer be used.
-
Moreover, you may want to use const_iterator, rather than iterator, when the code doesn't alter the container (despite what Scott Meyers may say about preferring iterator).
-
The naming used in the code is not very good, to say the least. Names should generally be chosen based on the role of the variable, not its type (although some Hungarian Notation like ..._ptr might be acceptable, as it reminds us that it requires a different usage). In the code, the words lst and it are used. Avoid acronyms and abbreviations, unless the name might be rather long without it, or the acronym or abbreviation is well-known. However, this code doesn't just have bad style, it also have some real bugs (including the one mentioned in point 1, above):
-
When the iterator it is assigned to, even if it had been a valid iterator to the start of the container, the container is empty, so the iterator is the past-the-end iterator. Assigning to and incrementing it leads again to undefined behaviour.
The problematic assumption in the code seems to be that the programmer thinks assigning to the iterator, and incrementing the iterator, will insert the values into the container. It is not so. You have to explicitly insert the values using the container object, for example with push_back:
lst.push_back(34): lst.push_back(45); lst.push_back(87);
-
One small thing to note here is that there's an integer to floating point conversion when the values are inserted, but it gives the expected behaviour. To avoid it (and its possible overhead), you might use values of type double, instead: 34.0, etc.
-
The list iterator doesn't have the less-than operator defined, only equal and not equal, so the program won't compile as it is. It may be recommended to always use not equal, even for containers supporting less-than, for consistency, and stronger post-conditions to the loop (detecting bugs earlier).
-
The body of the for loop tries to print out the iterator, which fails, as there isn't a stream operator defined for it. The intention was possibly to write out the index of each element. This isn't available from the list, so you have to track it separately, if you need it.
-
One final point to note is that system() is not a standard C++ function (and there's no other header to include it in the program), so even if the right header was included, the program wouldn't be portable to systems lacking that function.
-
endl is a manipulator that, in addition to writing a newline to the stream, also flushes it, and you may want to avoid needless flushing, especially if it's done a lot. The output stream is flushed before any input, anyway. (Ok, so this was the final note.) Let's say the list is a list of percentages. Here's a possible corrected version of the program (the using part has several correct possibilities, as mentioned):
#include <iostream> #include <list> int main() { std::list<double> percent_list; percent_list.push_back(34.0); percent_list.push_back(45.0); percent_list.push_back(87.0); for(percent_list_type::const_iterator it = percent_list.begin(); it!=percent_list.end(); ++it) std::cout << *it << '\n'; }
This corrects the problems mentioned in points 1-11.
If you thought I would stop here, you don't know me well enough. ;) Let's step back, and try to see what is the intent of the code. The code should express the intent as clearly as possible. Well, does it? Let's find out. The code inserts few values into a list, and then prints them out. The code above says quite a bit more than this. One thing that is commonly mentioned is to use for_each, rather than for, in such situations. However, using only the standard library, you need to then create another class to do the printing. This isn't necessarily an improvement, as you can't define the class at the point of use. However, as Kevlin Henney shows in his "Omit Needless Code" article, there are several alternatives to printing out the values. One is to use stream iterators:
typedef std::ostream_iterator<double> out; std::copy(percent_list.begin(), percent_list.end(), out(std::cout,"\n"));
This makes it rather more succinct. Now, the code focuses on what to do - printing or copying the values to the output stream - rather than how to do it.
If you need more advanced formatting (such as enclosing each value in braces), this won't do, though. Fortunately, there are libraries that allow us to create function objects on the fly, usable with algorithms, such as Boost.Lambda [1]. With it, we may substitute the above with:
std::for_each(percent_list.begin(), percent_list.end(), std::cout << _1 << "\n");
That takes care of the printing. What about the insertion of values? That looks rather repetitive, doesn't it? Well, Boost can again help us, here, with the Assignment library [2]:
percent_list+=34.0,45.0,87.0;
Here's the last revised version of the code:
#include <iostream> #include <list> #include <algorithm> #include <boost/assign/std/list.hpp> #include <boost/lambda/lambda.hpp> using boost::assign::operator+=; using boost::lambda::_1; int main() { std::list<double> percent_list; percent_list+=34.0, 45.0, 87.0; std::for_each(percent_list.begin(), percent_list.end(), std::cout << _1 << "\n"); }
Now, there's no fluff; the code states what it does (at least when you learn the abstractions involved). A further improvement might be if there were overloaded versions of the standard algorithms taking containers, rather than iterators:
std::for_each(percent_list, std::cout << _1 << "\n"):
David wasn't kidding when he said the code contains "various mistakes for such a few lines"... This small snippet also turned out to be a good opportunity to demonstrate some software development fundamentals, as well as more advanced techniques.
[1] http://www.boost.org/libs/lambda/doc/index.html
[2] Available in the CVS, but not yet in the current release.
The editor's choice is: Mick Brooks
Please email <francis@robinton.demon.co.uk> to arrange for your prize.
#include <iostream> using std::cout using std::endl #include <list> using std::list;
Let me start with a small style issue. I do not like interspersing using declarations and headers. I like to see all the #includes up front. Preferably I like to see any user header files first (in alphabetical order) followed by the necessary standard headers (also in alphabetical order). Placing the user header files first avoids accidentally masking a dependency that should have been in visible in the user header. Placing the includes in alphabetical order just makes it easier to check whether one has or has not been included.
While I notice, std::endl is not required to be declared as a consequence of #include <iostream>, it usually is but only because iostream normally drags in ostream.
When it comes to using declarations and using directives I think we should tend to use fully elaborated names (i.e. do not use either using directives or using declarations) until the user knows enough to understand the implications of each. I know this is contrary to what I did in 'You Can Do It!' but the motive in that book was to get inexperienced programmers writing code so I was willing to make some sacrifices. However, even there I started with fully elaborated names and required that they be used in all reader written header files.
int main() { list<double>::iterator it; list<double> lst;
I cannot say that I am enamoured by the choice of it as a name for an iterator but I can live with it, but the choice of lst as a variable is beyond my tolerance levels (well it is today). And what is that extra indent for? Indents without purpose only serve to confuse.
Up until now, I have just being cantankerous. Now it is about to get serious:
*it = 34;
Do you know if list<T>::iterator has a default constructor? No, neither do I and I do not care to take time to look it up. Whether it does or not, *it is surely introducing undefined behaviour because it has never been initialised to point to any storage.
And now it gets worse:
*++it = 45; *++it =87;
More purposeless indentation coupled with incrementing what is, at best, an iterator that points nowhere. And only now does the student make any attempt to relate it to lst. Had the program no had multiple instances of undefined behaviour the next line would have been perfectly OK, just not exactly the idiomatic way to do it.
it = list.begin();
Time to wind back to the beginning and write the code properly by avoiding early or unnecessary declarations.
#include <iostream> #include <list> typedef std::list<double> list_of_double; int main() { list_of_double data;
Note that there are no using declarations, but I have used a typedef. That is often a much more useful tool, and one that provides a modicum of documentation. In fact it is the exact reverse of using declarations because it adds information (not much in this case, but the problem is pretty abstract) rather than removing it (the library that a name belongs to).
Next the list starts empty so there is nowhere to store values. My preferred choice is to use push_back(), however we could have created the object data is bound to with three default initialised nodes by changing the definition to list_of_double data(3);. Sticking with my preferred option we get:
data.push_back(34); data.push_back(45); data.push_back(87);
The reason that I prefer this option is because it makes it very clear to even the rawest novice that data has nothing in it until we start pushing things in. If teaching, I would break off here and have a brief discussion as to what push_back() does.
Having created a list of values, I am ready to write them out:
for(list_of_double::iterator iter = data.begin() iter != data.end(); ++iter) {
You did notice that the student had used the wrong comparison? std::list::iterator is not a random access iterator and so values of it are not ordered. We simply cannot use a less than comparison. The only thing that will work is to keep going until equality with the end marker is achieved. Less than will work for most of the sequence containers but not for this one. Comparison for inequality is idiomatic for C++, those that want to do something else should understand why sticking with idioms is helpful.
std::cout << &*iter << '\t' << *iter << '\n';
I am guessing that the student wants to see the address used to store the value. If he didn't then he is completely out of luck because there is no requirement that a value of a std::list::iterator object be acceptable to an ostream object. The standard technique for getting the address of an object in a container is to apply the address-of operator to the dereferenced value of the iterator for the object; another idiom of modern C++.
Another feature is that I do not use std::endl unless I actually want to force both an end-of-line and a flush to output. I only need an end-of-line so I use the correct character for that; '\n'.
Now to finish:
} std::cout.flush(); std::cin.get(); return 0; }
Now I force the flush by calling the correct function. I don't want to hunt around to see which header declares system() and I certainly do not want to introduce that kind of system dependence into my code unless I really have to.
There are several other things that I might do to polish this program a bit, but I think the above will do. Now I wonder how the rest of you got on, and how many things I missed. The sad thing about much of the code we see here is that it shows just how badly instructors are explaining what is happening. Most of the code we publish comes from students who really want to get it right rather than ones who went to sleep during the lectures. The kind of errors they make expose fundamental misunderstanding of C, C++ etc.
(Submissions to <scc@accu.org> by November 10th)
Here is a program Francis collected which is riddled with poor design, naming, etc. as well as the actual problem:
I'm getting a "parse error before else" at the line indicated by the arrow
void IS_IT_A_DDR(string& mtgrec, string& temprec,int& ddrrc) { string Day2="SunMonTueWedThuFriSat"; string Daytoken="0123456"; int badday=0; if (mtgrec.size() < 8) { ddrrc=0; return; } for (int i=0; i <= 6; i++) { if (mtgrec.substr(0,3) == Day2.substr((i+1)*3-3,3)) { if ((mtgrec.substr(3,1) == "0") || (mtgrec.substr(3,1) == "1")) { if ((mtgrec.substr(7,1)). find_first_of("BCLMOPSTW*") != -1) { temprec=Daytoken.substr(i,1) + mtgrec.substr(1); ddrrc=1; return; } else { ddrrc=2; return; } else { <<< compiler diagnostic ddrrc=3; return; } } } else badday++; } if (badday == 7) { ddrrc=4; return; } else ddrrc=5; return; }
Notes:
More fields may be available via dynamicdata ..