Resumes
Brent Arias
Roger Arias
Contact Brent
Tech & Math
Language Arts


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.


This site is optimized for at least 800x600 resolution (hi-color) viewing with a browser that supports style sheets.
Contact Webmaster