Problems with program compilation and execution will often result in major (5-10 point) deductions during grading, though for less severe concerns (for example a compiler warning that should have been fixed but does not indicate a deeper problem with the program, minor (1-3 point) deductions are possible.
-Wall
command line switch) to see all possible compiler warnings about your code, and
then fix all those compiler warnings before submitting your code for grading.
If a warning cannot be fixed, and does not indicate a problem in your code, you
may use a compiler pragma to silence that specific warning, but
then please document that you have done so, and why it was necessary, both in
your readme file and in comments in your code.
main
function must return a value
of 0 (ideally by returning a descriptively named variable or label with that
value) on
successful termination, and a unique non-zero value for each different kind of
failure otherwise: please avoid using the same non-zero return value for
different kinds of errors. When different return values are possible,
it's also good practice to document what each return value means through
(1) giving each return value a descriptively named label (see guideline below about
not using hard coded numbers); (2) providing comments in the program header and
source code; and (3) describing the kind of error that occurred (and how to avoid
it) in the usage message(s) and error messages the program generates.
goto
statements, etc.) as a way
to control program execution under normal operating conditions.
One very helpful question to ask throughout the debugging process is "what was the last point where my program is known to be working correctly, and what was the first point where it is known to be working incorrectly?" A debugger is in invaluable tool in partially automating this process.
Problems with program design or coding can result in either major deductions (for example, for causing a memory leak by failing to delete memory that was allocated by new) or minor deductions (for example, checking for a pointer being 0 before deleting it without providing a comment for why that was appropriate) during grading. Please familiarize yourself with these guidelines, and the coding idioms they suggest, and apply them as you develop your solutions.
exit()
to end the program in the middle
of a function or class (or struct) method. Doing so interferes with the normal
graceful shutdown of the program, and the release of resources the program
acquired during its execution. It's better to use return statements with
appropriate values (or in some cases exceptions, though if so give comments why)
to cause the program to unwind its call stack and return an appropriate value
(indicating success or failure) from its main
function. It can be especially helpful to use smart pointers
in conjunction with this approach to ensure dynamically allocated
resources are cleaned up within appropriate scopes.
NULL
; with a C++11 adherent compiler please use
nullptr
(or with an older compiler use
0
, and then comment why you had to do that) instead.
C++ allows any pointer to be compared to or assigned
0
in older and more up-to-date compilers alike.
The definition of
NULL
is platform dependent in some older compilers,
so it may be difficult to use it both correctly and portably.
For example, use the
Resource Acquisition
Is Initialization (RAII) idiom with smart pointers (e.g., via
make_shared
with a shared_ptr
) whenever
possible, in preference to using new
and
delete
directly.
new
and
delete
instead of
malloc
and
free
to allocate and deallocate
memory, respectively.
new
to allocate
memory in a program, make sure to find and document where in the
program that memory will be freed with a corresponding
delete
. A more general way to state this point
is that you should always know (and comments in your code
should explain where appropriate) the lifetime of any dynamically
allocated memory.
free
to deallocate memory
that was allocated with new
.
Similarly, never use delete
to
deallocate memory that was allocated with
malloc
.
ssize_t n_bytes; if ((n_bytes = foo.send ((char *) &reply_port, sizeof reply_port)) == -1) // ...Write it like this instead:
ssize_t n_bytes = foo.send ((char *) &reply_port, sizeof reply_port); if (n_bytes == -1) // ...
if (test) { // true branch } else { // false branch }is preferred over:
if (! test) { // false test branch } else { // true test branch }
return condition;instead of
if (condition) { return true; } else { return false; }or
return condition ? true : false;
(int) foo
. Use standard C++ casts, e.g.,
static_cast<int> (foo)
, instead.
If you need to use a cast, you also need to provide a comment explaining why.
= delete
(or with an older compiler should be declared as private to the class or struct),
but then not defined.
make_shared
(or possibly
new
for an older compiler). If it's necessary to
be able to destroy class or struct instances from outside the class or struct
(or its derived classes or structs), then provide a member function that destroys
instances of the class or struct using delete
, but
be careful to make sure there are no remaining aliases to the instance at that
point, which if dereferenced could cause the program to crash (or even corrupt
the program's state).
BOOL
, or similar platform-specific or
compiler-specific types, even if a compiler supports them. For portability,
always use the standard C++ bool
type for Boolean
variables, instead.
size_t i = 0; for (size_t j = 0; file_name [j] != '\0'; ++i, ++j) { if (file_name [j] == '\\' && file_name [j + 1] == '\\') ++j; file_name [i] = file_name [j]; } // Terminate this C-style string. file_name [i] = '\0';
if (...) else
....
conditional logic statements instead of the
conditional ?:
operator. Your code will be a lot more readable, and this
also can help you avoid subtle bugs due to the precedence of
?:
compared with other operators
in an expression.
for
or do
or while
loop,
or an if
or else
statement branch, even if it's only a single
statement. For example:for (int j = 0; j < argc; ++j) { cout << argv [j] << endl; } int k = 0; do { cout << argv [k++] << endl; } while (k < argc); while (k > 0) { cout << argv [--k] << endl; } if (0 <= k && k < argc) { cout << argv [k] << endl; } else { cout << "argument position " << k << "is out of range." << endl; }
#pragma once
with a C++11 adherent compiler (or with older compilers
using #ifndef ... #define ... #endif
inclusion guards),
selectively replace inclusions of other header files with forward declarations
of the items that would have been included,
and remember that in many cases templates can be forward
declared as well.
new (nothrow)
).
new
), or use
Resource Acquisition
Is Initialization (RAII) and other programming idioms to make code
exception-safe.
.cpp
) files, placing
declarations in header (.h
) files, and using the
#include
directive to make declarations visible where
needed. Note that some compilers assume different source file
extensions (e.g., .cc
or
.cxx
) but for this course we will prefer the
.cpp
extension for source files.
.h
) files.
Also separate definitions
for class or struct templates and function templates from definitions for
non-template classes or structs and non-template functions by placing them
into different source (.cpp
) files. This will
give you better decomposition of your programs overall, and some
compilers may be confused when template and non-template code is
mixed in the same file. One nice naming convention to distinguish
files that contain template declarations or definitions from those that do
not, is to put _T
at the end of their names (just before the file
extension).
Note that some compilers, like g++, require the inclusion of template
definitions following their declarations. However, this does not mean that
you should put those definitions into the header files with the template
declarations. Instead, for portability of the code across platforms and
compilers, at the end of the header file put a conditional
inclusion guard and inside it include the appropriate template source file,
and then pass the guard symbol to the compiler via a command line switch
(e.g., -DTEMPLATE_HEADERS_INCLUDE_SOURCE
for g++).
For example, the end of Foo_T.h
would say:
#ifdef TEMPLATE_HEADERS_INCLUDE_SOURCE /* test whether guard symbol is defined */ #include "Foo_T.cpp" #endif
#pragma once
,
or for older compilers with an "inclusion guard" construct, as in:
#ifndef FOO_H #define FOO_H // Class or struct and function declarations in foo.h go here... #endif /* FOO_H */
Per the previous guidelines, files that contain type-parameterized classes or structs should follow this style:
#ifndef FOO_T_H #define FOO_T_H // Class or struct template and function template declarations for foo_t.h go here... #if defined (TEMPLATE_HEADERS_INCLUDE_SOURCE) #include "Foo_T.cpp" #endif /* TEMPLATE_HEADERS_INCLUDE_SOURCE */ #endif /* FOO_T_H */
.cpp
source code file always include the
corresponding header file as the first inclusion (after any
environment-specific mandatory inclusions, like
stdafx.h
on Windows with precompiled headers),
like this:
// This is Foo.cpp #include "stdafx.h" // Windows only, would not have this line on Linux #include "Foo.h" // Any other includes go here... // Definitions go here...
This ensures that the header file is self-contained and can be safely included from some place else (if it seems like other includes should go first, then they probably need to be included in the corresponding header file).
const int SUCCESS = 0; int main (int, char *[]) { // Rest of the definition of main ... return SUCCESS; }
Problems with program style and documentation usually result in only minor deductions during grading, but even minor deductions can add up quickly if there are a lot of them. In the longer term, practicing a consistent and high quality programming style will make your code more accessible to others (and to you after you've been away working on other code for a while), which is essential in modern team programming environments.
For example, for
,
if
,
switch
, and
while
statements should have a
space after the respective control keyword, as in:
for (unsigned int i = 0; i < count; ++i) { ++total; }
7
: instead
use an appropriately named constant variable or enum label (or if absolutely
necessary a precompiler constant) like one of the following:
const int expected_argument_count = 3; enum command_line_arguments {program_name, input_file, output_file}; #define SUCCESS 0