Odds and Ends
- Static member data
-
Each instance of a class with a static data member sees the same value
for that member.
// trace.h - class definitions for tracing class
#ifndef traceh_
#define traceh_
class Trace {
public:
Trace(char *);
Trace(){fn=0;};
~Trace();
friend void TraceOn();
friend void TraceOff();
private:
static bool Trace_flag;
char *fn;
};
#endif //traceh_
The static value gets initialized only once, in the definition file.
// trace.cc - tracing class functions
#include <stdlib.h>
#include <stdio.h>
#include <iostream.h>
#include <string.h>
#include "trace.h"
bool Trace::Trace_flag=false;
Trace::Trace(char * name)
{
cerr << "T:entering <" << name << ">" << endl;
fn=strdup(name);
} // Trace constructor
Trace::~Trace()
{
if(fn != 0) {
cerr << "T:leaving <" << fn << ">" << endl;
free(fn);
}
} // Trace destructor
void
TraceOn()
{
Trace::Trace_flag=true;
} // TraceOn
void
TraceOff()
{
Trace::Trace_flag=false;
} // TraceOFF
The friend functions are needed to alter the private data. And so you don't
need to create an instance just to set the data.
const references
All C and C++ args are passed by value. However, C++ has added pass
by reference. It is recommended to use this for almost all parameters as
it can speed things up.
int foo(int a) // (A)
int foo(int& a) // (B)
In (A), since a copy of a is being made when the function is called, the
contructor must be called. And when it exits, the destructor must be called.
The effect of pass by value (no alteration of the original) can be achieved
by
int foo(const int& a) // (C)
This allows the compiler to push just an address on the stack and prevents
the function from altering it.
copy constructors
When passing an object as a parameter and doing it by value, the bits
of the object are passed from the callers environment to the called functions
environment. The local var is a bit copy of the original. This isn't always
desirable. One example is that it copies the state of static members and
you might not want this. So you must write a special constructor called
a copy constructor to handle this. You add a member function that looks
like:
mycc(const myclass&);
This will be called any time a class instance is passed by value. Examples
include:
foo(myclass a) {};
myclass x;
x= bar(z); // copies return value to x
operator overloading
Overloaded operators are simply another name for a function call. the expression
'a+b' is another way of saying '+(a,b)'. You can overload a variety of
operators in C++, both unary and binary. The overloaded operators must
operate on a user type. You cannot redefine what it means to add integers
by overloading the '+' operator. A unary operator function takes no arguments
and a binary operator takes one. This is because the object on the left
side is the other argument. So 'a+b' is more like 'a.+(b)'. Here is an
example involving string concatenation.
class string {
public:
char * operator+(const string str)
{
char *result= new[this->length() + str.length() + 1];
strcpy(result,this->value);
strcat(result,str.value);
return(result);
}
private:
char value[32];
}
iostream overloading
You can create your own insertors by overloading the << operator.
To print a name with a label for a class you created:
ostream &
operator<<(ostream& os, const myclass &t)
{
os << "myclass:name is " << t.name << endl;
return os;
} // << operator
increment and decrement overloading
It is possible to overload the ++ and -- operators. You can even differentiate
between pre and post increment. The compiler ass the prefix call,
++a;
as a call to
operator++(a)
However, a postfix call is seen differently:
a++;
operator++(a,int)
The compiler provides the dummy second argument.
Pure virtual functions
Virtual functions are a method of providing that the overridden versions
of a base class method get executed. Often the base class method is instantaited
so there is always something to run. However, you may want to build the
base class as an abstract class. This would have the interface and the
data, but no function. All the methods are virtual functions and they have
no code. These are defined by setiing them to 0.
virtual void foo() const = 0;
The function is defined by cannot be executed. It must be overridden by
the derived class.
Error handling
-
C error handling
There are several error handling techniques in C. Functions return
error codes or save them in global error variables like errno. Signals
can be sent and received on error. Can clash with other applications. long
jumps take the program out of the current contect and into another. This
has all the classic problems of gotos as well as not cleaning up if it
can.
-
catch and throw
In C++, a function can detect an error and throw an object containing
information about the error out into the world. Seomwhere, some other function
catches it and processes the error.
-
throw
The syntax for a throw is:
throw classname(args);
This creates an instance of this class and effectively returns it from
the function, even if that isn't the return type of the function. Usually
the context of thrown errors is a try block. The code looks like:
try {
// some code that generates errors
}
catch(thrown_type1 var) {
// catcher code
}
catch(thrown_type2 var) {
// other catcher code
}
Only one catch clause is needed for each exception type, even if there
are multiple throw statements in the try block. C++ supports the termination
model. The assumption that the code throws an exception because things
are so bad there is no help. If you want to construct the catchers so they
try to fix the problem, put the try block inside a while loop.
exception specification
To tell the user what the function is likely to throw as exceptions,
there is an extention of the function definition syntax.
void foo() throw( toobig,badpointer);
A function definition without a throw clause indicates that any exception
can be thrown.
void foo() throw();
Means hat no execptions will be thrown. Specifying the execeptions helps
the user know what to do if they want to put a try loop around your code.
generic catchers can be used.
catch(...) { }
This should be put at the end of the catcher list to pick up any you missed.
If you can't figure out what to do, putting a call to throw with no arguments
in a catcher will cause the exception to be re-raised to be picked up in
the next outer try block. To make sure everything is clean, when you leave
the scope of the code that threw the exception, all objects that have been
succesfully constructed, are destructed.
When to use
Use them for non-local errors. If you can handle it, do it. Use heirarchies
of execptions to help classify the error. use reference parameters in catchers.
Wrap a simple try block with a generic catcher around code and refine it
as needed. Good to throw exceptions during construction so execution doesn't
continue with incomplete code. Don't use exceptions in destructors. scope
changes call destructors and you might get in a loop.