Journal Articles
Browse in : |
All
> Journals
> CVu
> 152
(9)
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
Author: Administrator
Date: 06 April 2003 13:15:57 +01:00 or Sun, 06 April 2003 13:15:57 +01:00
Summary:
Body:
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.
Before providing the entries for the current competition, here is one that arrived too late for the last one (note that competition material goes up on www.accu.org at the time an issue of C Vu is distributed so that those who have to wait for their C Vu because of slow post can still compete.)
Unfortunately the entry was not only late but the writer forgot to include their name in the file. Please help me give you credit by remembering to include your name in all files you send as attachments.
Since this is my first time to reply to a code critique, I hope I won't embarrass myself too badly with any blunders. I'd also just like to say how much I enjoy getting to learn about C++ by reading C Vu and lurking in the mailing lists. The comments below were heavily influenced by my reading of several of my reference books, including Accelerated C++ by Koenig and Moo, and Steve Myers' Effective C++ and More Effective C++.
First off, the design of the class has several flaws. In class Card, Display() is defined as a pure virtual function, thus making Card an abstract class that cannot be instantiated, a clear error. Also, why is the destructor defined as virtual? From these two functions, it seems that Card is meant to be an abstract base class with other classes derived from it. But why? It seems that a class representing a physical card should not need derived classes from it, but since I am still in the beginning stages of learning C++ and haven't learned much yet about class hierarchy design yet, I could be mistaken. If Card is not going to be derived from, we do not need any virtual functions. I am thinking that the Display() function would be used at a later date to draw the actual card, and the implementation of it isn't needed yet. Also, if we don't need a virtual destructor, we can leave that out of our class, since the compiler will auto-generate a non-virtual default destructor anyway.
Next are some style concerns in the code. First off, I noticed that RegularPack is a global variable, which in general is not recommended. In my example, I would move this variable into main(), making it a local variable. Second, the main code has two loops that are both meant to index across an array. But two different end conditions are used. One style should be used, and then adhered to make the code more consistent. The second loop doesn't even use RegularPack to identify the number of times the loop executes, just a so-called "magic number" which again is not recommended. The first loop also uses the variable "i", which is defined in main(). Common C++ practice it to define a loop index variable in the for header, so that it goes out of scope and is destroyed after the loop is finished. Third, the code uses both "\n" and std::endl to insert a new line in the output stream. A fourth inconsistency is that the naming convention used is not consistent in that at least one variable (pCard) uses Hungarian notation, while all other variables do not. For both of these, I think the best thing to do is use one form, and be consistent. The last, and biggest style issue in my opinion is that an array of pointers is being used to represent a deck of cards. It seems that using vector<Card> from the standard C++ library would be perfect to hold a number of cards. This allows us to use the generic sorting functions in the standard library in the future if needed, as well as not worrying about all the problems pointers can cause (especially for us beginners).
Now for the compilation errors. I tried compiling the code with Visual C++ .NET, as well as Comeau's online compiler. I added appropriate include and using statements, which were not in the original code posting. I am guessing that they were left out for brevity, but if not then certainly this would be an error as well. The first thing that I noticed was the typo in the first loop, where RegularDeck should be RegularPack. Also, I got two warnings, one that pCard was never used, and the second was that "label 'Card:' was declared but never referenced". The second warning is actually a syntax error in that the "Card:" shouldn't be there. The next error was the last line of the program, where no variable Card was defined. The last error was for the body of the second loop. To be honest, after the first loop, which just gives each Card in the array a value from zero to 53, I don't understand what the code is supposed to do. The second loop (I think) is meant to index through the array, and display each Card's value. But the second line in the loop body is all wrong, since the pointer shouldn't be deferenced, and even when you fix that, SetNumber returns void, which is an illegal type to pass to cin.
I've rewritten the class Card so that it no longer has a Display() function, which wasn't used anyway, as well as leaving out a destructor, and letting the compiler generate one when needed. I've added appropriate include and using statements. In main(), I've used a vector<Card> to represent the deck, and use the standard library functions to insert and view the values of the deck. The program gives each of the 54 cards a value, from zero to 53, then prints out each card and its value. I'm sure there is a better way to re-write the code, but as I am still learning, I'm just trying to keep it simple, and (hopefully) correct.
#include<iostream> #include<vector> using namespace std; class Card { public: Card():itsNumber(0) {} Card (int Number):itsNumber (Number) {} void SetNumber (int val) {itsNumber = val;} int GetNumber () const {return itsNumber;} private: int itsNumber; }; int main() { const int RegularPack = 54; vector<Card> Pack; for (int i=0; i < RegularPack; ++i) Pack.push_back(i); for (vector<Card>::size_type i = 0; i != Pack.size(); ++i) cout << "Card number " << i << " value is " << Pack[i].GetNumber() << endl; }
The problem this time was to help this student within the terms he specifies. However you should take the opportunity to correct errors and misconceptions.
I am trying to write a function that dynamically allocates an array of integers. The function should accept an integer argument indicating the number of elements to allocate. The function should perform necessary error-checking to determine if the memory was successfully allocated. if the memory was allocated the function should return a pointer to it. Otherwise it should return a null pointer.
This is "Homework" so I do not wish to have the program written for me. My problem at this point is that I am having trouble coming up with the concepts in my head. Any help would be appreciated. This is what I have so far.
#include <iostream> using namespace std; //function prototypes int *allocatemem(int); int *sortNums(int); void main(void) { int NoGrades; int *grades; int *test; cout << 'Number of grades to enter: '; cin >> NoGrades; grades = allocatemem(NoGrades); for(int i=0; i<NoGrades; i++); { cout<<'What is test score # ' << (i+1) <<' ?'; cin>> *(test +i); if(*(test +i) < 0) { cout << 'Must be positive \n'; cout<<'Please enter Test #' << (i+1) <<' correctly: \n'; i-; } } sortNums(NoGrades); } int allocatemem (int amount) { int *memory; memory = new int[amount]; if(memory != 0) { cout << 'We have memory \n'; cout << 'going back to main \n'; return memory; } cout << 'We do not have enough memory for' 'this task \n'; cout << 'going back to main \n'; return memory; } int sortNums(int scores) { for(int j=0; int temp=0; i<numberOfScores; j++) temp= *(test +1); if(*(test + 1) < * test +1 + 1) { *(test +1)= *(test +i +1) *(test + i + 1) = temp } for(int a = 0; a<numberOFScores; a++) cout<<*(test + 1)<<' '; }
From Catriona O'Connell <catriona38@hotmail.com>
While space is allocated for grades, the marks are actually read into test, which is an uninitialised pointer - leading to overwriting of storage. Pointers should be initialised before they are dereferenced.
The function allocatemem() is declared to return int*, but the definition returns int.
Nothing is done with the return code from allocatemem except to print a message. The code continues regardless of the success or failure of the allocation request. Actions need to be taken on return codes. As written, the code in allocatemem will not behave as the student expects if it is compiled with a conformant compiler. If new fails to allocate storage, it is required to throw a std::bad_alloc exception (3.7.3.1/3). To return a null pointer instead of an exception the user should call the nothrow variant of new. The <new> header must be included. At this point it is worth noting that the program does not free the allocated storage on exit. It is good practice to free-up dynamically acquired storage. Not all environments will clean-up after the program terminates. I'm thinking of some subpools in the MVS OS here.
In the function sortnums(), there is no good reason to declare temp in the for() and even if you did it should have been separated from int j=0 by a comma not a semicolon. The for() loop as written has four statements instead of three. The sortnums function doesn't sort the numbers either and there is a typo where i has been written as 1.
The argument is int scores which is never used and the loop goes over numberOfScores which is not defined anywhere - and even worse has two different spellings. The function needs an argument that defines the number of elements in the array. It is also defined to return an integer, but declared to return a pointer to an integer. The whole function is a mess and should be re-written.
The function main() should have been declared as int main() not void main(void). The C++ standard allows only int main() and int main(int argc, char *argv[]) as definitions (3.6.1/2).
There is no checking of user input. As a general rule input from a user should always be checked for validity. The coding of cin to retrieve values from the user is too trusting. If a user enters a non-integer value then the stream's fail bit will be set and the cin object becomes unusable. One solution is to call the clear member to reset the fail bit. This should be followed by the ignore member to discard any additional input from the stream.
Another minor flaw with the grade input code is that the student tampers with the loop control variable to handle incorrect input. While tampering with a loop control variable is not explicitly forbidden by the standard it is a sign of a poor program design. While not true in this case, there is a danger of such practices breaking the conditional test in the for() header.
The variable NoGrades is poorly named as it might be taken to mean that the student has no grades rather than holding the number of grades. Good naming standards are helpful to code maintainers.
The student has enclosed string literals in single quotes rather than double quotes in his/her dialogue with the user. Also as a matter of style, if cin and cout are being used it would be more consistent to use std::endl rather than '\n' as a newline[1].
It is also not beneficial to use the pointer notation for the test array, when the array[subscript] format would have been clearer. There is an equivalence between *(test +i) and test[i], but the latter is easier to visualise - especially if you are a Fortran programmer :-)
The pointer returned from allocatemem is used to determine if the code proceeds with data processing or drops through to the final return. This removes the need to have multiple return points.
The getInput() function addresses the problems raised by the student's use of cin to retrieve input from the user. The getInput function requires that a valid range of input values be passed as arguments so that the user is required to enter a valid data type within the specified range. The getInput() function is a specialisation of the template code presented in response to SCC12.
I have snaffled a bubblesort() routine from http://leepoint.net/notes/cpp/algorithms/arrayfuncs/bubblesort2.html[2]. The bubble sort routine is adequate for small amounts of input and is simple to understand and implement. The bubblesort routine sorts the array in place. The student did not specify what we were to do with the output, so I printed it just to demonstrate that the sort worked.
#include <iostream> #include <new> #include <climits> using namespace std; int *allocateMem(int); void bubblesort(int *, int); void getInput(const char *, int&, int, int); int main(){ int numGrades; int *grades; int i; getInput("Number of grades? ", numGrades, 1, 10); grades = allocateMem(numGrades); if(grades){ for(i=0; i<numGrades; ++i){ getInput("Enter Grade", grades[i], 0, 100); } bubblesort(grades, numGrades); for (i=0; i<numGrades; ++i){ cout << grades[i] << endl; } delete [] grades; } return 0; } int *allocateMem(int amount){ int *memory; memory = new(nothrow) int[amount]; if(!memory){ cout << "Error: Unable to allocate sufficient " << "memory." << endl; cout << "Program terminating." << endl; } return memory; } void bubblesort(int *x, int n){ // code suppressed use std::sort instead - Francis } void getInput(const char *prompt, int& r, int lower, int upper){ bool error; do{ cout << prompt << ": " << flush; error = !((cin >> r) && (r >= lower) && (r <= upper)); if(error){ cout << "Please try again." << endl; cout << "Enter an integer between " << lower << " and " << upper << endl; } cin.clear(); cin.ignore(INT_MAX,'\n'); } while(error); }
From Roger Orr <rogero@howzatt.demon.co.uk>
Dear student,
First the good news - most of your difficulty in solving the problem doesn't seem to be with the concepts. The bad news is you seem to have several sorts of problems, mostly with the syntax - that is the details of writing C++ code.
Let's deal with this systematically.
Firstly, the problem you were set was to write a function to allocate memory and return it, or null on failure. Your allocatemem function almost does this (apart from a small syntax error). However you have allocated the memory with new and in standard C++ this does not return at all if it fails but throws an exception. However you were asked to check for out of memory and return null. Perhaps the teacher was asking the wrong question? I'll leave it to you to discuss that with them!
So you need to use a different 'flavour' of new to get back a null pointer if there isn't enough memory. This is done by adding an extra argument to new and writing 'new(nothrow)' instead.
That solves the main conceptual error you seem to have with the problem set you. The second error is you forgot to check the return value when you call allocatemem in main. If it failed then you'd better stop there!
You have a few syntax problems - perhaps you were in a hurry? "More haste less speed" when it comes to syntax errors I'm afraid.
Firstly, C++ is fussy about the difference between a single and a double quote: ' and "; some other languages don't care. A single quote is for a single character, a double quote is for a string of characters. As it happens all your single quotes can be changed to double quotes.
Secondly, C++ is fussy about referring to variables in other functions. So sortNums tries to use the variable test from main but it can't see it - you need to pass this variable in as another parameter to the function.
Incidentally, sortNums is perhaps not the best name for the function since it does two jobs - it sorts the numbers but then prints them. I'd prefer writing two functions, sortNums and printNums, and so keeping code that changes the scores separate from code that prints them. You can then make the array constant in the printNums function that among other things prevents you accidentally writing code which changes it.
Thirdly, you aren't consistent and, alas, the compiler expects you to be completely consistent. It is also easier for you and everybody else if you use consistent names for things. For example, the number of grades in the program is called many names: NoGrades, grades, numberOfScores and numberOFScores in various parts of your code. You also have grades and test both referring to the same data.
Try to ensure that the names of variables are always the same - and this includes being in the same case. To make this consistency easier it is a good idea to adopt a simple policy on names - many people use a mixed case with first letter lower case like you have in numberOfScores. I've picked on one name and used it consistently in the program.
Another place where you must be consistent is that the types used for function returns and arguments must be the same in the function prototype and where the function is actually defined. The prototype for sortNums returns int* but the function itself claims to return int.
Lastly you have a small problem with the for-loop in sortNums - you can initialise multiple variables in the for-loop initialiser but if so you must separate them with commas. In your case I'd simple remove int temp=0; completely and put the int on the next line to declare and define temp in one go.
All being well you now have a body of code that compiles and does solve the problem you were set.
However we are not finished yet...compiling is not at all the same thing as working! It would be nice if your sample program worked and used standard C++.
Firstly, main is defined as void which many compiler still accept but it is an old usage. In standard C++ main always returns an int. You shouldn't need to put in a return-statement in main, the compiler ought to do this for you, but not all compilers do so put a return 0; just before the closing brace to be safe.
Secondly, sortNums is defined to return an int but doesn't return anything - and its return value is ignored where it is used! So I'd change sortNums to return void.
Thirdly, you have a very, very small loop. The for-loop in main has a spurious ';' which the compiler understands to mean 'do nothing' each time round the loop. Just remove it and then the loop will work as you expect.
And then lastly printNums doesn't print and sortNums doesn't sort! The for-loop in printNums uses 'test + 1' inside the loop where you wanted 'test + a'.
The sort is just broken but it is easily fixed. The solution here is to stop re-inventing the wheel. The problem didn't ask you to write a sort algorithm, so don't. Just use the standard library sort from the header <algorithm>.
The final code looks like this:-
#include <iostream> #include <algorithm> using namespace std; // function prototypes int *allocatemem(int); void sortNums(int* grades, int numberOfGrades); void printNums(const int* grades, int numberOfGrades); int main(void) { int numberOfGrades; int *grades; cout << "Number of grades to enter: "; cin >> NoGrades; grades = allocatemem(numberOfGrades); if(grades == 0)return 1; for(int i=0; i<numberOfGrades; i++) { cout << "What is test grade # " << (i+1) << " ?"; cin >> *(grades +i); if(*(grades +i) < 0 ) { cout << "Must be positive \n"; cout << "Please enter Test #" << (i+1) << " correctly: \n"; i-; } } sortNums(grades, numberOfGrades); printNums(grades, numberOfGrades); return 0; } int *allocatemem(int amount) { int *memory; memory = new(nothrow) int[amount]; if(memory != 0 ) { cout << "We have memory \n"; cout << "going back to main \n"; return memory; } cout << "We do not have enough memory for " "this task \n"; cout << "going back to main \n"; return memory; } void sortNums(int* grades, int numberOfGrades) { std::sort(grades, grades+numberOfGrades); } void printNums(const int* grades, int numberOfGrades) { for(int a = 0; a<numberOfGrades; a++) cout <<*(grades + a)<<' '; }
From William Fishburne <william.Fishburne@verizon.net>
The restrictions requested by the student strongly limit what can be said. I have constrained the analysis to the allocatemem function, as requested, but in a normal circumstance, it would be worthwhile to discuss the whole program!
A few overriding things need to be addressed, within the limitation of discussing this one function, these still apply:
-
using namespace std; is a bad practice[3]. It is better to specify std:: before the items you need from the standard namespace. You probably got this from your text, but the author also probably makes some statement about how you shouldn't really use it and that he is doing so in the interest of clarity. Some clarity, huh? A large reason for a namespace is to prevent a name clash. While this little program does not really incorporate enough to generate such a clash, why would you risk it? Wouldn't it be better to get in the habit of using namespaces properly so that you never have to try and debug something where somebody got clever and used cout in their own namespace?
-
main always returns an int. Setting its return value to void is simply an error.
-
It strikes me as very odd that a C++ class would require you to write a function that dynamically allocates arrays. It is certainly possible, but it is counter-intuitive to the flexibility that the standard library offers. At the risk of pointing you in the wrong direction, consider a multi-map from the standard library that has built-in the ability to associate a score with a test and to sort the result.
-
Globals are dangerous. It is much better to pass what is needed to each function as the values are needed. This is true because a global variable can be changed anywhere in the program. The bigger the program, the more likely that any change will break something else.
-
It is a big mistake to change the value of a control variable in a for-loop. Is there another way you can make sure that a valid test grade is entered that wouldn't require you to decrement the control variable? (Francis, since you have talked about C++ standards before, wouldn't it be nice if a for-loop variable in a for loop was a constant within the loop? This would sort of be a reverse sense of scope, it could only be changed outside the loop block... Similarly, I think it makes sense for-loop variables to never be instantiated outside the loop statement…
Actually I strongly disagree and changing the rule would break a good deal of existing, well-designed, code.
Now, because I respect the heck out of Francis (HEY! That is not butt-kissing, that is the honest truth…), I downloaded Quincy et al and typed this program into it. What do you know? The first error that came up was the problem with main.
Once I fixed my typos and a couple of obvious errors (I spare you as per your request), the next error that came up was the redefinition of allocatemem. As a prototype, you define it as int *, then when you define the function itself you use int. Certainly you must have meant the former as you plan to return a pointer to an integer, right? The compiler catches simple errors like this quite readily and it is worth working to make sure that your program compiles before seeking other help if you can. The listing below shows a version of your program that will compile (wait to look at it until you have managed to get it to compile yourself). If you attempt to run this program, you will find that it does not work (as you expected or you would not have submitted it). This is not unusual. Few programmers can write a program that both compiles and runs correctly on the first try (I've never met one).
OK, we have allocatemem returning a pointer to an array of integers. We accept in the number of elements in the array. You have correctly formulated the call to new and memory will be allocated to the pointer memory if it is available. What does new do, however, if the memory is not available? In the C++ standard, new is supposed to throw the error message std::bad_alloc. To catch this error message, you would need to encompass your call to new in a try/catch clause. In order to catch this error, you will have to include the <new> header at the top of your program.
There is a nothrow version of new which returns a NULL pointer. I think it is worth taking the time to get to know the try/catch clauses instead of using the nothrow version, but let us assume, for the moment, that you do decide to go ahead and use the nothrow version. Are NULL and 0 the same thing? If so, why have two different names? Consider, are NULL and 0.0 the same? I am not asking whether NULL and 0 can be the same, but whether they are required to be the same.
Finally, with regard to allocatemem, does your program pay attention to the return value from allocatemem? If not, why not? What will happen if memory isn't properly allocated?
Some questions to consider on the rest of the program:
-
What should sortNums return? Does it?
-
When printing an array, if you get the same value over and over, then you are either not incrementing across the array or you are not incrementing your array index.
-
What happens if you look one position past the end of an array? How can you make sure this doesn't happen?
-
How many times should a bubble sort iterate?
-
Why does C++ allow you to reference arrays as grades[6] instead of requiring you to write *(grades+6)? Is grades[1] always the same as *(grades+1)?
-
When is it appropriate to have two variables in a for-loop? Is your use appropriate?
-
What is the first value of test? Does test ever change? What are the implications for the first two values of temp?
-
How many integers do you pass to sortNums?
-
What happens when memory is allocated and never deleted? Where is the best place to delete the new memory you have created?
#include <iostream> using namespace std; // function prototypes int *allocatemem(int); int sortNums(int); // global variables int *grades; int *test; int main() { int NoGrades; cout << "Number of grades to enter: "; cin >> NoGrades; grades = allocatemem(NoGrades); for(int i=0; i < NoGrades; i++) { cout << "What is test score # " << (i+1) << " ?"; cin >> *(test+i); if(*(test+i) < 0) { cout << "Must be positive\n"; cout << "Please enter Test #" << (i+1) << " correctly: \n"; i-; } } sortNums(NoGrades); return(0); } int *allocatemem (int amount) { int *memory; memory = new int[amount]; if(memory != 0) { cout << "We have memory\n"; cout << "going back to main\n"; return memory; } cout << "We do not have enough memory for "; cout << "this task.\n Returning to main\n"; return memory; } int sortNums(int numberOfScores) { for(int j=0, temp=0; j<numberOfScores; j++) { temp=*(test+1); if(*(test+1) < *(test + 1 + 1)) { *(test + 1) = *(test +j1 + 1); *(test + j + 1) = temp; } } for(int a=0; a<numberOfScores; a++) cout << *(test + 1) << ' '; }
The editor's choice is: Roger Orr.
Please email <francis.glassborow@ntlworld.com> to arrange for your prize.
The original code for this program was in C++ but I have converted it to C because it was conceptually a C program using C++ for I/O. You will need to make an assumption about the input that is actually causing a problem - think of an input that has many digits. However there are several other immediate problems with the code as well as a general design error. Can you improve the design on the basis that the writer knows about for-loops but not about arrays?
I'm creating a program that inputs three integers, and returns the sum, product, average, largest and smallest. Very simple, but I'm getting a bad return on one of my variables. Except for my "largest" variable, the others are returning the correct numbers. I'm hoping that someone can tell me where I'm going wrong.
#include <stdio.h> int main(){ int num1, num2, num3, sum, average int product, smallest, largest; puts("Input three different integers: "); scanf("%d, %d, %d",&num1,&num2,&num3); // read three integers sum = num1 + num2 + num3; average = ((num1 + num2 + num3)/ 3 ); product = num1 * num2 * num3; if(num1 < num2 ) smallest = num1; if(num2 < num1 ) smallest = num2; if(num3 < smallest ) smallest = num3; if(num1 > num2 ) largest = num1; if(num2 > num1 ) largest = num2; if(num3 > largest ) largest = num3; printf("\nSum is %d",sum); printf("\nAverage is %d",average); printf("\nProduct is %d",product); printf("\nSmallest is %d",smallest); printf("\nLargest is %d\n",largest); return 0; }
[1] Actually many experts would not agree. std::endl should only be used where you require the output be flushed in addition to including a newline.
[2] And I suppressed it because this is not one of the rare places where a bubblesort offers any advantage, and in the context of this problem the student should simply use std::sort.
[3] Many would disagree with that assertion, particularly with regards to student code.
Notes:
More fields may be available via dynamicdata ..