Brent’s C/C++ cheat sheet.
I'm often asked about syntactical and semantical snags or obscurities, so I
thought I'd summarize the common quick reference stuff here.
Containment by Reference vs. Containment by Pointer:
When the contained object is subject to swapping, or could possibly refer to
nothing at all (null), then the proper technique is to use a pointer member.
However, when the object to be contained has a permanent relationship with the
object containing it, then the proper technique is to use Containment by
Reference:
class Part
{
public:
//the colon up to the '{}' is
//the "member initialization list"
Part(const PartNumber& thePartNumber):
itsPartNumber(thePartNumber) {};
private:
const PartNumber& itsPartNumber;
}
Although 'thePartNumber' has a lifecycle separate from objects of Part, to
avoid faulty code and bugs, 'thePartNumber' must exist at least as long as
the container instance exists. Uses of the Member Initialization List: (1) To
call a non-default constructor of an associated super-class (2) to initialize a
Containment by Reference variable (3) to avoid unecessary calls to assignment
operators (a performance gain).
Nested STL:
When nesting STL containers, make sure to leave a gap between successive '<'
or '>' characters, like this:
//closing symbols are '> >' instead of '>>'
map<int, list<string> > myMap;
Without the gap, the compiler will assume a call to operator>>() or
operator<<() is being made, and so will not compile.
STL with MSVC 6.0
An oversight with the Visual Studio application prevents it from gracefully
handling the long naming schemes resulting from compiling STL. To prevent
a deluge of warning messages, place the following pragmas at the top of each
#include file that is hounded with such warnings:
#pragma warning (disable:4786)
#pragma warning (disable:4503)
Indefinte function args:
A function that accepts an unknown number of parameters uses the elipses:
//this example requires at least a string.
void printf(char *,...);
Function pointers:
These are declared almost exactly the same way you declare a function. Here is a
global function pointer that takes a string argument and returns a float:
float (*foo)(char *);
Here is a function pointer that points to a function that returns int and has
two arguments:
int (*foo)(float, char *);
To simplify declarations, a typedef for the above function pointer would be:
typedef int (*FOO)(float, char *);
Now the word "FOO" can be used as follows:
//myFunc is a function pointer.
FOO myFunc;
//Advanced: declare a function that returns a
//function pointer of type 'FOO', while taking two
//parameters - one of which being a function pointer
//of type 'FOO'.
FOO boo(char *, FOO);
Calling a function pointed to by a function pointer can be done like this:
(*ap)(2.5, "hello")
However, most compilers are smart enough to accept simply:
ap(2.5, "hello")
...the above will successfully compile and run. To declare an array of function
pointers, do this:
FOO anarray[] = {&func1, &func2, &func3);
Then call one of these arrayed functions like this:
(*anarray[1])(2.5, "hello");
Now for the stalwart, the mother of all function pointer examples. The following
pointer 'ptrFunction' is being type cast as a function pointer AND
simultaneously being called on the spot. The function it is being cast to takes
three parameters (PortableServer_Servant, char *, CORBA_Environment*) and
returns a char *. The actual arguments being sent in the call are in the final
parenthetical (pObject, greetingStr, pEnv).
myResult=(
(
char *(*)
(PortableServer_Servant,char *,CORBA_Environment *ev)
)
ptrFunction
)
( pServantObject, greetingStr, pEnv );
Const keyword:
Used in a formal argument list, it indicates that there is no intention on
altering the value referred to:
void foo(const float&, const int*);
Used after the formal argument list, it indicates that the method will NOT alter
object state (this example allows for one of the arguments to be altered,
however):
void foo(float&, const int*) const;
void boo() const {};
A const member function can be called for a const object; an ordinary member
function cannot. For further details on this, see The C++ Programming Language
by Bjarne Stroustrup.
Virtual Destructors:
A destructor knows how much space to free based on the type of the atom being
deleted. However if Polymorphic Containment is being used (Liskov Substitution
Principle) - then the wrong amount of data may be freed. e.g.:
class employee {
// no virtual declaration, not good!
~employee();
};
class manager : public employee {
int level;
};
void f() {
employee* p = new manager;
delete p; //manager::level will not be freed.
}
Thus, the destructor needs to be declared as Virtual. More on this topic can be
found on page 577 of The C++ Programming Language (2nd edition). However, there
is one good reason NOT to have a destructor declared virtual. If you have a
class that will have no children, and you need for it to occupy as little RAM
as possible (embedded system limitation?), ensuring that no function is virtual
(including the destructor) will prevent a VTABLE from being made for that
class.
Deleting an Array:
If you “new” an array[], then you must “delete” an array:
delete foo[];
This isn’t really an issue if the type is native (e.g. array of ints), but if
the array contains objects, “delete” will not know to first “delete” each
object in the array before freeing the memory, and that can cause a significant
memory leak.
Physical Issues:
Lakos reminders: A “component” is the physical side of a logical class. That is,
it refers to the .h and .c file. A “package” refers to a collection of these
components that are justified by tight logical and physical dependencies (I
often think of this as a “lib” or “dll” but I wonder nowadays if I should think
that way anymore – perhaps yes for test isolation purposes). That is, they are
cohesive with each other and loosely coupled from other packages. A “uses in
the interface” relation refers to in-name or in-size dependencies in the
(public) interface (e.g. definition of a class, or signature of a free
function). The expression “uses in the implementation”, a weaker form of
dependency, refers to any dependencies (including “uses in the interface”), but
is over-shadowed by “uses in the interface.”
The dependency notation Lakos uses depicts the “interface” or “implementation”
distinctions, with the “in-name” and “in-size” being hardly ever used (an
“in-name” dependency is depicted - in Lakos' notation - by using a dashed line
with either of the other two dependency notations). For a class, an ISA or HASA
(by value) always require an “include” in the header. A HOLDSA (by reference)
may or may not cause a compile dependency (i.e. require an #include), and may
not even require a link dependency. Here is an example causing no dependency
whatever:
//util.h
#ifndef UTIL_H
#define UTIL_H
class SomeType; //dependency "in name" only
class Util {
public:
//function returns pointer to SomeType
SomeType *myFunc(SomeType *obj);
private:
SomeType *m_myThing;
}
#endif
------------------
//util.c
#include “util.h”
//some useless code...
SomeType *Util::myFunc(SomeType *obj) {
someUndefinedGlobalMethod(obj);
m_myThing=obj;
return obj;
}
In other words, dependencies that are “in-name” only have no compile or link
dependencies (Lakos 248). I get the impression from Lakos (p250) that there is
no such (practical) thing as a simultaneous “in-name” and “uses in the
implementation” interface. It stands to reason.
Include statements always cause a compile dependency whether it is needed or
not. Use “include” rather than “extern” statements to acquire needed
definitions (for other such rules, see Lakos section 3.2). Some free tools for
analyzing compile dependencies: gmake, mkmf, cdep (Lakos, p 134).
If class B substantively depends on A, and class C depends substantively on B
and A directly, there is a temptation to have C merely “include” B and let B’s
“include” of A take care of the dependency C has on A. There are two reasons
not to do this. First, if B no longer needs to depend on A and so B’s “include”
of A is removed, then one must alter code in C anyway (by adding the explicit
“include” of A). Second, when using a (simple) dependency analyzer tool (one
that only reports on the basis of the “includes” that it sees), it will report
interdependencies as being less byzantine than they really are.

Copyright © 2002-2003 Brent Arias. All Rights Reserved. Please read our
Terms and Conditions.
|