This page provides hints, tips and tricks which may prove useful when solving the exercises of the C/C++ course. Use them to your advantage. The bracketed names given with the hints refer to the set of exercises from where the hint can/should be applied.
New hints may be added to the various sections while the course is
running. In those cases: newly added hints are flagged by a
-image
Hints WRT Part I Hints WRT Part II Hints WRT Part III
-Wall for all compilations. Make sure your code compiles free
of warnings.
---std=c++26 standard.
cout for
insertions into the standard output stream, use cin for extractions from
the standard input stream. Do not use the C print and scan family
of functions.
>>) by default skips
leading whitespace. If a string is extracted, all initial whitespace
is skipped as well.
enums or enum class-types and values, rather
than int constants in cases where your values aren't used as numeric
values but to indicate categories.
errorCount rather than error_count.
char const name[] array (where `name' is
an appropriately chosen name, describing the nature of the raw string literal),
and use that name in, e.g., cout statements inside function
bodies. Preferably define the variable inside an anonymous name
space. Example:
#include <iostream>
namespace { // anonymous name space
char const hello[] = // define the Raw String Literal
R"(
***********
hello world
***********
)";
} // namespace ends
int main()
{
std::cout << hello; // display the Raw String Literal
}
string name, lastname, address;
Instead define each variable on a line of its own:
string name;
string lastname;
string address;
The reason for this is that it makes it easy to relocate definitions (you
should always localize variables definitions as much as possible), and you'll
immediately see what their initial values are.
About using comment:
offset = tmp.tellp(); // offset of the 1st entity
avoid this type of comment:
offset = tmp.tellp(); // offset hold tmp's current offset
The latter type of comment should be avoided because it basically repeats
the statement itself. Instead, the former type of comment tells the reader
something about that particular offset location. That's why it's called a
semantic comment.
// a generic object copy, owned by uid, and
if // initially copied by uid
(
obj.genID() != maxUint16 and obj.ofUser(uid) and
obj.orgID() == uid
)
...
int Parser::local(string &ident, int value)
{
d_symtab.local(ident);
return value;
}
// The name of the variable is stored in the symbol table in a vector of
// names. The initial value is stored in an Int32Vector, using the
// variable's value at the corresponding index position.
// When the variable is subsequently used, its index is obtained, allowing
// for instructions like 'push var 3'. Since the variable array of that
// state also has at least that many elements the value can be retrieved
// and saved.
int main()
{}
-------------
int main()
{
}
void execute(Uint8Vector const &code, bool showObjects)
{
if (code.size() == 0) // leave if there's no code to exec.
return;
d_ip = &code.front(); // begin at the 1st opcode
while (s_opcode[*d_ip++]()) // execute the instructions in 'code'
;
}
char-type has signed values. This may result in
unexpected program behavior if a char variable is used as an
array-index. In order to prevent this from happening, avoid char variables
except for true character input/output. When a character must be read with the
intention to use its value as an array-index, take one of the following
approaches:
int istream::get() (e.g., cin.get()), returns an int,
which is positive except at end-of-file, when the predefined constant EOF
is returned. This return value can be assigned to an int variable,
which can thereupon be used in, e.g., index expressions.
When assigning the return value to a char realize that the char
may be negative, and should thus not be used in index expressions.
char may directly be assigned to an unsigned
char or to an uint8_t, defined in cstddef. This effectively maps the
range -128 .. -1 to 128 .. 255. E.g., static_cast<unsigned
char>(char-expression). The header cstddef is often already included by
other standard headers (like iostream) in which case you do not have to
include it explicitly.
char type, using a compiler flag. If the flag
-funsigned-char is provided the char type will be interpreted as an
unsigned type.
std::string objects
where possible' deprecate certain functions (in particular str...()
functions) see below.
c_str member.
strlen) are better provided by C++-string objects. Don't use the
C str... legacy functions when the C++ strings can be used as
well. E.g.:
// assume c_string is a NTBS, cpp_string is a C++ string
strlen(c_string); // don't use this
cpp_string.length(); // use this instead
at member function. If you do so
because Java offers the at member function then learn about the index
operator ([]). Whenever you're convinced that the index does not or cannot
exceed the string's index-bounds, use the index-operator []: it's more
efficient. When a pointer to a string (introduced later during part I) is
available, first dereference the pointer, then use [] (e.g., prefer
(*str)[idx] over str->at(idx), let alone str->operator[](index)).
#define compiler directives,
except for defining include guards, which are covered in the
lecture about functions (in part III we encounter another (local) use of the
#define directive).
cout statement might be
printed like:
cout << "Let's assume that this is printed by your printer, but the line
is too long, and so the printer `wraps around'\n";
This is interpreted by us as two lines of source code, which
cout << "Let's assume that this is printed by your printer, "
"but the line is too long, and so the printer `wraps around'\n";
The above example illustrates string concatenation, which is supported
by the language and avoids an additional cout statement or insertion
operator. In general,
cout
statements.
cout with an insertion operator or a semicolon. Use string
concatenation if a series of C-strings are inserted (as in the
above example).
cout statement illustrating the preferred
layout:
cout << "Let's assume that this is printing " << aValue << "\n"
"using string concatenation\n"
"Start a new line following \\n, insert " << multiple <<
variables << likeThis << '\n';
static_cast<size_t>(x) // OK
static_cast<size_t> (x) // don't
static_cast <size_t>(x) // don't
static_cast <size_t> (x) // don't
std::endl into streams unless you're really
intending to flush the stream. Streams are automatically flushed when they
go out of scope (and with std::cout: when the program ends and also when
the next statement involves std::cin).
cout << "\n";
use:
cout << '\n';
An NTBS (double quotes) tells the stream object to insert all
characters until a final 0-byte is encountered; a character constant tells the
stream object that exactly one character should be inserted and thus is more
efficient.
If a single character is immediately followed by the insertion of additional textual characters then use string concatenation: in that case use double quotes for the single character but omit the next insertion operator. E.g.:
cout << whatever << "\n"
"string concatenation. The previous \\n is merged with this text\n";
static_cast operation is not required when assigning values of
comparable data types but different sizes. Assigning an int to a char
and v.v. can be performed without a static_cast. A static_cast is
appropriate to change the `signed' quality of a type or to cast a value to a
`wider' type in a non-default way. A good example of the latter use is casting
an int variable in an int expression to a double if the resulting
exxpression should be of double type. Casting the full expression is
pointless, as the compiler will first evaluate the expression (as an
int) and then convert the result to double, which basically
doesn't do anything at all.
floor to remove the fraction of a double before
assigning its value to an int is pointless: the default assignment process
already ignores the fraction.
char or short unless the
context requires their use. With char that's always a situation where
characters are to be displayed, short is only required in very specific
cases (and even then, (u)int16_t is probably preferable). Therefore, use
short only when you're required (as in: forced) to use it.
if (x == y) // not: if( x == y )
// not: if(x == y)
// not: if ( x == y )
Blanks in parenthesized expressions are OK if the expression is complex
and itself contains sub-expressions that are parenthesized.
Comment within your code (within functions) should use end-of-line (eoln) comment. But again: don't put the comment at the same level of the code, because that makes it hard to distinguish at first glance code and comment.
Eoln comment should be concise and must provide additional information about the statement beyond what the statement already offers. E.g., this is CC:
string str; // define str
Cluttering comment should be omitted. There really is no need to provide
comment to self-explanatory lines in your sources.
This example's comment, however, isn't CC:
// process all command-line arguments
for (size_t idx = 1; idx != arc ++idx)
Wrt comment layout, to distinguish comment from code use the following rules:
nCombinations = 1 << argc; // compute 2**argc: # combinations
// process all command-line arguments
for (size_t idx = 1; idx != argc; ++idx)
// idx iterates from 1 to argc
for (size_t idx = 1; idx != argc; ++idx)
provides no additional information, and we call that cluttering
comment, lowering your exercises' ratings
int main(int argc, char *argv[])
{
..... // inspecting the arguments
.....
..... // processing arguments
.....
.....
..... // performing the program's core business
.....
}
if or if-else construction, do not use curlies around
single nested statements, but only if the nested statements themselves contain
multiple statements. Avoid constructions like:
if (argc == 1)
{
usage(argv[0]);
}
else
{
process(argc, argv);
}
Within the world of C++ this is a dialect that belongs to another
language (e.g., Perl).
if (double value = assign(); -1e-8 <= value and value <= 1e-8)
...
or:
if
(
double value = assign();
-1e-8 <= value and value <= 1e-8
)
...
if-else construction, try to write the shorter nested
statements first, and put the longer statement in the else
substatement, especially if the if-true statement is a compound
statement. This holds especially true for constructions like:
...
else
break;
By the time you reach the else, you've probably forgotten what the
condition was all about. Furthermore, changing substatements may actually make
the if-else superfluous as execution terminates at the break. So,
rather than
if (condition)
{
// relatively much code
}
else
break;
use:
if (not condition)
break;
//relatively much code
if-else statements (an if-else ladder), write the if
following an else immediately after the else, and do not
additionally indent:
if (cond-1)
statement-1;
else if (cond-2)
statement-2;
else if (cond-3)
statement-3
else
statement-4
if-else construction where the same variable receives a value
in both conditions, should be rewritten using the ternary operator (and by
implication: this holds true for comparable other situations as well, e.g.,
return statements). Example:
// don't:
if (condition)
x = value_1;
else
x = value_2;
// do:
x = condition ? value_1 : value_2;
It's less work, describes concisely what you intend, it's applicable
within expressions, and possibly allows the compiler to apply more direct
optimizations and in an if-else statement, which, after all, contains
three `statement' parts.
if-else construction, avoid else following a
sub-statement terminating in break, return, exit or throw (covered
later):
if (condition)
{
...
return;
}
else // the else and subsequent compound stmnt are not required
{
stmnt 1
stmnt 2
...
}
Instead, use:
if (condition)
{
...
return;
}
stmnt 1
stmnt 2
switch over an if-else ladder: it's faster and
easier to understand and maintain.
for statement
(from 0 to n) is:
for (size_t idx = 0; idx != end; ++idx)
The basic form for defining a decremental for statement (from end to 0)
is:
for (size_t idx = end; idx--; )
Reserve the for statement for situations where you know in advance
how many iterations to perform; use the while statements for an
undetermined number of iterations.
while (true)
{
optionally perform tasks before reading information
read information
if the reading fails (e.g., end of file was reached)
break;
process the obtained information.
}
Contractions are often possible, in particular when no repeated
pre-processing is required (i.e., there is no task to perform before
information is read):
while (getline(cin, string))
process the read information.
or:
while (cin >> variable)
process the read information.
for
statement. Whenever the number of iterations is unknown, use a while
statement. This is the distinguishing characteristic of these two statements,
and respecting this distinction helps you or others to understand your code.
for statements use the canonical forms. Using the canonical
forms avoids the notorious `off-by-one' error and automatically takes you into
the `world of offsets' which is almost always the world in which for
statements are used:
// IdxType: the index type that is used
for (IdxType idx = 0; idx != end; ++idx) // proceeding upwards
for (IdxType idx = end; idx-- != begin; ) // proceeding downwards
for (size_t idx = end; idx--; ) // (same, numeric IdxType)
...
string const text = "constant text";
for (size_t idx = 0; idx != 5; ++idx)
cout << text << '\n';
...
But also note that the range-based for-loop has an optional initial
variable initialization section (see the slides)
while statement is an
if-else statement, consider leaving out the else, ending the first
substatement in continue: it allows you to omit the indentation of the
else substatement. Since the else substatement usually is the longer
substatement it tends to produce code that is easier to read.
while statements prevent the famous Pascal statement
flaw, which is another example of a dialect. Usually the perpetual loop is the
construction to apply:
// Avoid the Pascal-like statement repetition:
prompting();
while (obtainData())
{
processData();
prompting(); // statement repetition
}
// Instead use a continuous loop:
while (true)
{
prompting();
if (not obtainData())
break;
processData();
}
)
do-while statements. They are seldom
required. If you think you need them you're probably wrong. In this course its
not prohibited to use do-while statements, but your solution is
downgraded if you use them without real necessity.
cin), do not use the Perl-idiom of reading the file's full content in a
string variable, followed by processing the string. This may work for
relatively short files, but for longer files it's a definite no-no, as you'll
easily exceed the string variable's storage capacity.
cin
(usually a full line using getline(cin, line) or extracting the next entry
using cin >> destination), cin itself can be used to check whether the
extraction succeeded (as in if (getline(cin, line)) or if (cin >>
destination) or even if (cin)). Before using the 3rd form you have to
make sure that you first have tried to read something from cin.
void functionName(int param1, int param2)
(some keywords, like if, switch, for and while, are followed by
open parentheses. A blank is inserted between those keywords
and the open parentheses following them).
void usage(string const
&programName) (or char const *programName) displaying usage
information and terminating the program. This function is called when
no (or maybe: too few) arguments are passed to the program.
Notes: Your usage information should make sense, and should provide a to-the-point summary of how to use your program (and maybe a single line stating its purpose). Don't be frivolous here. Here is an example of how what usage info may look like:
cidr by Frank B. Brokken (f.b.brokken@rug.nl)
cidr V1.20.01 2006-2013
Usage: cidr [options] cidr-file
Usage: cidr [options] cidrpattern line
Where:
[options] - optional arguments (short options between parentheses):
--help (-h) - provide this help
--quiet (-q) - don't output the matching cidr if found
--verbose (-V) - give a verbose report of what's going on
--version (-v) - show version information and terminate
cidr-file - file containing cidr-patterns, one per line.
cidr returns 0 if a line on stdin contains
an IP4 address matching a cidr-pattern, and 1 otherwise.
cidrpattern line: inspect line for an ip-address matching cidrpattern
returns 0 if so, 1 if not
main;
.ih rather than .h.
#include <iostream>
#include <string>
// other header files may be required as well. Include them here.
void usage(char const *programName); // all declarations of functions we
... // developed ourselves for this
... // program, preferably alphabetically
... // ordered by function name
// optionally, as we're the only ones using this header file:
using namespace std; // and/or any other namespace you use.
When we're constructing a program demo all sources of the demo
program may now start with:
#include "demo.ih"
// followed by a function definition ...
Although main can define an
int argc parameter, that's a legacy. We know that argc is
positive and at least 1. Often argc needs to be passed to
functions processing command line arguments, and often a for
statement is used for that. In those cases simply define a size_t
argc parameter for such functions, and pass it main's argc. You
can safely do that because
argc > 0;
int to size_t are automatically
performed: using a cast is not necessary
So, if your function is showArgs(size_t argc, char *argv[]) you
can simply do:
int main(int argc, char *argv[])
{
... // whatever
showArgs(argc, argv);
... // whatever
}
and define:
void showArgs(size_t argc, char *argv[])
{
for (size_t idx = 1; idx != argc; ++idx) // canonical for loop
cout << "argument " << idx << " is: " << argv[idx] << '\n';
}
------------------------------------------------------------------------
value:
primitive types: e.g., (int counter)
Don't use const, since the parameter's scope is
restricted to the function anyway.
class types: Generally: AVOID
Avoid class type value parameters:
they require a full object copy which sometimes is
downright impossible.
USE a value class type parameter when it is used
as a working object which would otherwise have
been defined as a local object initialized by the
parameter.
const reference:
primitive types: AVOID
Avoid this. Use value parameters instead:
parameter passing is easier and value
parameters are handled more efficiently.
class types: e.g., (std::string const &message)
Use this when you only access the information of
the parameter. In this case never use a value
parameter instead.
non-const reference:
primitive types: e.g., (int &nSuccesses)
AVOID this.
It is acceptable to use this form to implement
`return by argument' until we have covered
pointers. By convention return by argument
parameters are defined before other parameters.
class types: e.g., (std::istream &in)
Used to communicate the existence of objects living
outside of the function to the function. Such
objects are used, and may be modified. `Return by
argument' constructions are primarily used to
assign new values to the objects/variables
referred to by the `return by argument'
parameters. It is acceptable to use non-const
references to implement `return by argument' until
we have covered pointers.
r-value references: e.g., (std::string &&tmp)
Used when the current function may swallow (some
say: steal) tmp's data. Following the call of this
function tmp should cease to exist. Rvalue
references are primarily used to implement
so-called `move constructors' which are introduced
at the earliest by the end of part I.
pointers to const entities:
primitive types: e.g., (char const *message) Used when the `natural
type' is a pointer, like an NTBS. Here the
parameter is an input parameter.
class types: e.g., (std::string const *msgPtr)
Generally not used. Instead const references are
preferred.
pointers to non-const entities:
primitive types: e.g., (int *successPtr)
Used for `return by argument' constructions. To be
defined before any other kind of parameters
class types: e.g., (std::string *textPtr)
Same.
--------------------------------------------------------------------------
void process(std::string const &arg); // process all lines 1.cc
// process one line 2.cc
void process(std::string const &line, std::string const &arg);
The following describes how to create and use (static) libraries. Dynamic libraries are, construction-wise, a different topic, and may or may not be covered at some point during the course. For the time being, all that needs to be done can be done using (your own) static libraries.
g++ -c *.cc - create object modules of all sources in the
current directory.
ar rsv libmylib.a *.o - add all .o files in the current dir. to
some library. `libmylib.a' may also be a
path-name
ranlib libmylib.a - randomize the index
This process may be repeated. Existing libraries may be
extended by adding completely new objects to it. Just make sure the
object files are uniquely named.
rm *.o
The library is now constructed. Hereafter it can be used:
/where/it/is/stored/libmylib.a':
g++ -o program *.o */*.o -L/where/it/is/stored -lmylib -s
Notes:
g++ -o program *.o */*.o -L. -lmylib -s
Relative paths may also be specified using -L. Multiple -L
and/or -l combinations may be specified if multiple libraries are
to be used.
-s flags strips symbolic information off the final
executable. We won't use this symbolic info, and it usually
greatly reduces the size of the final executable.
icmake or ccbuild. Whatever you prefer, but make sure
you do use a program maintenance tool.
d_.
Class names, like all your self-defined types, start with a Capital letter.
const member
functions, must be const member functions.
int const values
from members or if you think you should define, e.g., bool const &
parameters. Defining bool const parameters is OK if you want to stress
that you're not going to change the parameter's value in your function.
<iostream> you may also implicitly have included
<string>. Never rely on this relationship between headers: it's an
`undocumented feature' at best, which may not be supported anymore by newer
versions of your compiler. Instead,
X, make sure
the appropriate X header file is included before your class header or
definition starts.
C structs, (like tm), consult the man-page to find
the appropriate header file (in this case time.h or ctime).
std stream type or the type
std::string include iosfwd.
MyClass defines an enum
Type and declares a member Type type() const, then type's
implementation looks like this:
MyClass::Type MyClass::type() const
{
....
}
Since the enum is now firmly attached to the class name, this usually
also obviates the use of a strongly typed enum.
struct, and immediately below the
class's opening brace define all publicly visible types and enums, followed by
a private section for the data.
private section.
driver program (e.g., in a
subdirectory of the class directory itself) showing the proper functioning of
the (public) members of the class. Such a driver can be developed in parallel
with the development of the members of a class: construct a member, add its
use to the driver, show to yourself that the driver works properly. Its good
practice to show/test a member's behavior for both correct and incorrect
input.
get.... Eg, use
size()rather than:
getSize().
set members can be defined likewise: overloaded functions
usually do the trick. Instead of
setInt(int x);
setDouble(double x);
define:
set(int x);
set(double x);
Member functions can be defined in-line if their code is smaller than their call (as a rule of thumb: this may hold true for functions consisting of at most one normal-sized statement) and if it can be assumed beyond reasonable doubt that their code remains unaltered.
When defining inline members, always make sure the class interface itself remains implementation-free. The class interface should contain only member function declarations.
.f for `function', which are included
below the interface, as shown in the next example:
class MyClass
{
size_t d_size;
public:
size_t size() const;
};
#include "size.f"
While size.f merely contains:
inline size_t MyClass::size()
{
return d_size;
}
This approach has multiple advantages:
inline by a line like #include "myclass.ih", and
to rename the .f file to a .cc file.
.f convention we
retain the advantages of implementation-free headers we also enjoy with
standard, statically compiled functions (i.e., functions implemented in
.cc files)
Note that private inline members (or their .f files) should
not be included in the class's header file as they are never used outside
of the class. Instead, define (or include the .f files of) private inline
members in the internal header file.
An exception to this rule is occasionally encountered with inline public members calling inline private members. In this situation the inline private member must also be defined in the class's header file.
A side note: when creating a library the class's header files are made
available for inclusion by other programs. In those cases create those header
files from the project's header file + .f files by including the .f
files into the `exported' header file. Using the .f files convention is
primarily for the benefit of the developer. Users of a class shouldn't
have to inspect the header files anyway; they should be given a decent
man-page.
When using :, starting member initializer specification(s),
use the preferred layout of opening curly ({) characters:
: position;
: character.
Example:
Demo::Demo(int value, string text)
:
d_value(value),
d_text(text)
{
... body statements
}
icmake and follow the design
principles we advocate. Using precompiled headers in that case is as simple as
changing the line
//#define SPCH ""
to
#define SPCH ""
in the file icmconf.
data.cc in which you define and initialize
the static data. That way the header remains as it should: it declares instead
of defines the class's elements.
+-------+
| ---|---> ?
+-------+
Likewise, let a 0-pointer point at 0:
+-------+
| ---|---> 0
+-------+
NULL is old-school C programming. Use 0 (a plain zero)
or nullptr (nullptr is only necessary to distinguish between
overloaded functions when one function uses, e.g., an int parameter and
another uses, e.g., a char * parameter).
new with delete, match new[] with
delete[] and operator new with operator delete
int array[3][5]
int (*ptr)[5] = array // note the parentheses!
Now, *ptr points to array's 1st row, and thus has type
int *.
new, new[],
operator new, delete, delete[] and operator delete. They're type-safe
and prevent your program from continuing when the allocation fails.
size_t loop control variables and index expressions. This
avoids the repeated evaluations of index expressions. E.g.,
// rather than, assuming 'Type array[size]'
for (int idx = 0; idx != size; ++idx)
array[idx] = whatever();
// use:
for (Type *begin = array, *end = array + size; begin != end; ++begin)
*begin = whatever();
README file for further instructions.
string *sp = new
string[0]). They are pointless and useless. To initialize a pointer to an
array for which no elements are as yet available simply initialize it to 0
(string *sp = 0), allowing you to check for this value to infer that it
doesn't point to elements yet.
Type array[] = { ,-separated list of Type values };
size_t const sizeOfArray = sizeof(array) / sizeof(Type);
// or, sometimes shorter:
size_t const sizeOfArray = sizeof(array) / sizeof(array[0]);
This is always correct. You don't have to count, and later on
array can be modified resulting in a new, again correct, value of
sizeOfArray.
pointers2:
[] when defining array parameters of functions. Since
the parameter is always a pointer define it as a pointer. Note that
the pointer must be protected by parentheses when defining a parameter
pointing to a multi-dimensional array.
operator new allocating a block of raw memory returns
a void *. However, if the type of information stored in the raw memory is
Type then the bytes are to represent Type objects, of which there may
be d_size out of a potential d_capacity number of objects. Thus, when
the raw memory becomes available indicate that the raw bytes are to represent
Type objects, by encapsulating the raw memory allocation in the following
function:
Type *newRaw(size_t requestedNoOfObjects)
{
return static_cast<Type *>(
operator new(requestedNoOfObjects * sizeof(Type))
);
}
and let the remaining software simply refer to Type elements accessed
using a Type * pointer.
d_size objects are stored at the location returned by
newRaw, with Type *d_ptr having received newRaw's return value,
then another support function comes in handy to properly delete the objects
and the allocated raw memory. Here is a function deleteRaw complementary
to newRaw, performing the object destruction and the deletion of the raw
memory:
void YourClassName::deleteRaw()
{
for (Type *ptr = d_ptr + d_size; ptr-- != d_ptr; )
ptr->~Type(); // directly call the
// object's destructor
operator delete(d_ptr); // release the raw memory
}
newRaw is defined as a free function: it has no
class name scope. It doesn't need one, since no data members are used. You
could consider storing newraw.o in a library (e.g., libtools.a) which
can be used by any of your programs. In that case also define, e.g., a
tools.h header file declaring newRaw. To use newRaw in your code
your programs now merely heave to include tools.h, and you should link
those programs (also) against libtools.a.
Alternatively, newRaw could be defined as a class member. If so,
either define it as a const member function, or define it as a
static member.
inline, but always define it
in a source file of its own, even if it looks like this:
Class::~Class()
{}
At some point the compiler must define a table with pointers to member
functions. If it finds the class's destructor's source file then it stores
that table in the desstructor's object file. If there isn't a destructor, it's
undefined where the compiler will store that table, highly complicating
program maintenance.
istream, ostream), and not Xfstream or
Xstringstream. When using istream and ostream your
function will be more generally usable.
#include<iosfwd> if a class interface or function declaration
only uses references or pointers to streams or strings. In those
cases the streams can merely be declared, speeding up the
compilation.
std::string's conversion functions to convert values of basic
types to text. For more complex conversions use Xstringstream
objects.
stringstream to extract from strings,
ostringstream to insert into strings. Do not just use
stringstream objects: in practice you never have to read and
write from stringstreams.
-c option):
makeclass Demo
constructing class Demo in directory demo
make Demo() [y/n]? y
Allocation support [y/n]? y
Move support [y/n]? y
use Bobcat's Swap [y/n]? n
(The last question may be answered by y if you know what you're
doing, but for the time being we advise you to answer n.)
This results in the class demo containing:
demo.h demo2.cc frame swap.cc
demo.ih demo3.cc operatorassign1.cc
demo1.cc destructor.cc operatorassign2.cc
Default, copy and move constructors are available, as are overloaded
standard and move assignment operators.
const.
swap member using raw memory swapping then always
provide a (brief) explanation why it can be used for your class. If you can't
fully explain why fast swapping is allowed, then don't use it. If your
explanation is unclear or inorrect your exercise is probably rated 0.
+=), as they can be implemented using those
compound operators.
Do that even if promotions are prevented: in the end using a single design principle that always works is easier to use that having to ask yourself time and again whether to use this or that principle.
operator[]. Avoid using
complex filenames for overloaded operators.
Although by itself there's nothing wrong with filenames like
operatorindex1.cc and operatorindex2.cc, such names are kind
of long. Since they're all referring to operators, opindex1.cc,
opindex2.cc (or even opidx1.cc, opidx2.cc) is shorter and equally
informative.
When the same operator has multiple overloads then add an eoln comment specifying which operator is found in which file to the operator's declaration.
Maybe also avoid silly names like operatorplusequal.cc containing
operator+='s implementation: there's nothing about equality
here. Use shorter names not using `equal' like opaddis.cc (and
opadd.cc for the free binary operator). Of course, opequal.cc
is a great filename containing operator=='s implementation.
friend.
== and != only one overloaded
operator needs to be a friend. The other one can negate the outcome of
the friend operator.
namespace fs = filesystem;
so it's clear to the reader dat when you're using, e.g., copy,
it's a function living in the filesystem namespace: fs::copy.
error_code object and pass it as
argument to the fs-function
std::exception_ptr.
main function try block) it's advised to
mention the name of your program before showing the exception's text.
That way (e.g., when your program is called from another program) it's
clear which program caused the exception.
[an error occurred while processing this directive]