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 entityavoid this type of comment:
offset = tmp.tellp(); // offset hold tmp's current offsetThe 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 strCluttering 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 ...
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 indexThis 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 -sNotes:
g++ -o program *.o */*.o -L. -lmylib -sRelative 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.
:
, 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.ccDefault, 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.
[an error occurred while processing this directive]