Você está na página 1de 180

Page 1 - 1

Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 1
Unit 1: C++ basics
C++ Fundamentals
Alcatel-Lucent Bell Labs
Software Productivity & Quality Tools Dept.
http://www.stc.lucent.com/~dmm/intro
C++ basics an overview of the most significant C++ features for C programmers.
Page 1 - 2
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 2
Outline
C++ as a better C
using the C language subset of C++
C++ for data abstraction
defining and using classes
C++ for object oriented programming
most of the system is in classes
some classes are defined through inheritance
These three styles of C++ programming have been in use for many years.
C++ code written by experienced C developers is often restricted to the C subset of
C++.
Data abstraction programming is often done in the creation of small reusable C++
class libraries.
Object oriented programming requires some commitment to object oriented analysis
and design work: it is difficult to simply write object oriented code without doing
some planning and preparation.
Page 1 - 3
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 3
C++ is a better C
Features added to C:
better type checking
function overloading (several functions with the same name, but
different arguments)
inline functions (increases efficiency of small functions)
constants (replaces some uses of C preprocessor; defines a contract
between function writers and users)
Using C++ as a better C has a very small learning curve, and some significant benefits.
Most C source files can be compiled with a C++ compiler after making some minimal
changes:
C++ requires all functions to be declared before they are used, so some extra
function declarations (or #includes of header files) might be necessary
C++ function definitions use the ANSI C syntax [function arguments are
declared within the parentheses after the function name]
Benefits:
You dont need to run lint to find functions called with incorrect arguments.
Page 1 - 4
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 4
Better type checking in C++
The following example C program will compile and link, but it wont
work correctly:
void stringcopy(s1, s2)
char *s1, *s2;
{
while ((*s1++ = *s2++) != 0) {}
}
main(argc, argv)
int argc;
char **argv;
{
char *mystring = "a test";
char b[50], c;
stringcopy(b, mystring); /* this line is OK */
stringcopy(b, c); /* error at runtime */
stringcopy(b); /* error at runtime */
}
Traditional C compilers dont check the number or types of arguments. In fact, since the
definition of a function might be done in a separately compiled source file, there is no
automatic link between a functions declaration and its use.
Some tools (such as lint) can give warnings about these kinds of problems in C programs, but
the programmer must have the discipline to use the tools consistently.
Page 1 - 5
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 5
Better type checking in C++
The C++ version of the program will cause compile-time errors,
making it much easier to find bad function calls:
void stringcopy(char *s1, const char *s2)
{
while ((*s1++ = *s2++) != 0) {}
}
int main(int argc, char **argv)
{
char *mystring = "a test";
char b[50], c;
stringcopy(b, mystring); /* this line is OK */
stringcopy(b, c); /* error at compile time */
stringcopy(b); /* error at compile time */
return (0);
}
In C++, the compiler always checks each function call to insure that the arguments match the
declaration. If the function is defined in a separate source file, the file that uses the function
still must contain a declaration of the function:
extern void stringcopy(char *, const char *);
int main(int argc, char **argv)
{
....
stringcopy(b, mystring);
....
}
Most programmers who create C++ libraries will build a header file that contains the
declarations of multiple functions, so the user of the library doesnt have to type in every
function declaration.
Page 1 - 6
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 6
C++ for data abstraction
The major construct in C++ is the class:
a collection of data and functions
some members of a class are private
private data is available only to member functions
private functions can only be called by other member functions
The implementation details of a class are hidden from the class user,
who only sees the interface
initialize
top
push
pop
3
7
11
13
items
stackPointer
class Stack
A class is a packaging technique: a way to combine a data structure and the functions that
manipulate it. Classes can be used to implement abstract data types.
Page 1 - 7
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 7
Examples of classes
class Stack {
public:
/* member functions are declared here */
void initialize();
int top();
int pop();
void push(int);
private:
/* class data is usually private */
int items[10];
int stackPointer; /* the index of the first empty array cell */
};
int main() {
Stack myStack;
myStack.initialize();
myStack.push(7);
myStack.push(11);
int i = myStack.top();
myStack.pop();
return (0);
}
This page shows the declaration of the Stack class. This is similar to the declaration of a C
struct, but it contains function declarations in addition to the data declarations. The definitions
of the functions of the Stack class are shown on the next page.
The declaration of the Stack class is usually put into a .h file (such as Stack.h).
The Stack class is used in the main program in this example. The myStack.initialize()
function will invoke the initialize() function on the myStack object.
Page 1 - 8
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 8
Examples of classes (continued)
/* member functions are defined separately.. .. */
void Stack::initialize() {
/* any data member can be used in the definition
of a member function */
this->stackPointer = 0; /* stackPointer = 0 is also legal */
}
int Stack::top() {
return (this->items[this->stackPointer-1]);
}
int Stack::pop() {
this->stackPointer--;
return (this->items[this->stackPointer]);
}
void Stack::push(int newValue) {
/* we should first check for overflow */
this->items[this->stackPointer] = newValue;
this->stackPointer++;
}
This page shows the definitions of the functions declared on the previous page. The variable
this is a pointer to an object of class Stack: the object that the function was called with.
int main() {
Stack stk;
stk.initialize();
return (0);
}
void Stack::initialize() {
this->stackPointer = 0;
// in the call to this function in the main program
// above, this will point to stk, and this function
// will set the stackPointer field within that
// structure to 0.
}
Page 1 - 9
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 9
What is this?
Each member function will operate on an object.
The actions of the member function can access and/or modify the internal
state of the object.
The internal state is a structure containing the data in the class declaration.
Each member function call has an extra function argument: a pointer to the
internal structure.
void Stack::push(int newValue) {
this->stackPointer = newValue;
}
void push(struct Stack *this, int newValue) {
this->stackPointer = newValue;
}
Stack my_stack;
my_stack.push(100);
struct Stack my_stack;
push(&my_stack, 100);
C++:
C:
equivalent
In C++, attributes can be accessed within a member function using the "this->" syntax. A
member function is really equivalent to having a C function with an extra argument at the
beginning: a member function operates on an object, and the "this" pointer tells which object
to operate on.
The examples on the previous pages are following a common coding standard: to always
use the this-> syntax when referring to data members within a member function. Other
coding standards require programmers to use a naming convention to help people who are
reading the code distinguish which variables are data members: one common technique is to
require all data members to begin with m_ (such as m_items or m_stackPointer).
Page 1 - 10
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 10
What is this?
A call to a member function puts the pointer to the objects state on the stack, then all
of the function arguments.
The member function can access the contents of the objects internal state using the
this-> syntax. The member functions can also call other member functions on the
same object using the this-> syntax.
You dont always have to use the this-> syntax: within a member function, any
mention of a data member will automatically be treated as a reference to one of the
fields within this.
initialize
top
push
pop
3
7
11
13
items
stackPointer
class Stack
this->stackPointer
this->top()
this->items
Use "this" to refer to data
or functions within the
current object
Within a member function, "this" is usually optional the compiler will figure out that you are
accessing some member data when you refer to "items" or "stackPointer" within the member
function:
int Stack::pop() {
this->stackPointer--;
return (this->items[this->stackPointer]);
}
is equivalent to:
int Stack::pop() {
stackPointer--;
return (items[stackPointer]);
}
Page 1 - 11
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 11
Implementation options
There are several good alternative implementations of the Stack class
(without changing the public interface):
stackPointer could be a pointer instead of an integer
void Stack::initialize() {
this->stackPointer = &(this->items[0]);
}
items could be a dynamically allocated array
class Stack {
public: ....
private:
int *items;
int *stackPointer;
};
void Stack::initialize() {
this->stackPointer = this->items =
(int *) malloc(10 * sizeof(int));
}
The main concept behind data encapsulation: the structure of the data within the class
should be immaterial to the user of the class. The class user cant access the data directly if
the data is all defined in the private section. The class user can only call the public functions
in the class.
The author of the class might decide to change the internal data structure and/or the algorithms
used in the implementation of the class, but so long as the interface stays the same, the user of
the class shouldnt care.
Page 1 - 12
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 12
More implementation options
The Stack class could be implemented as a linked list:
void Stack::initialize() {
this->stackPointer = 0;
}
void Stack::push(int newValue) {
StackNode *sptr = new StackNode;
sptr->value = newValue;
sptr->next = this->stackPointer;
this->stackPointer = sptr;
}
int Stack::pop() {
int retval = 0;
if (this->stackPointer != 0) {
retval = this->stackPointer->value;
this->stackPointer = this->stackPointer->next;
}
return (retval);
}
For this code to work, the application needs to have a definition of the StackNode structure:
struct StackNode {
int value;
StackNode *next;
};
In C++, a struct is a class with all data in the public section by default.
Page 1 - 13
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 13
Common choices for data abstraction
In a large project, any interface used by many developers
database interface
user interface
interprocess/interprocessor communication
One or more experts can build the data abstraction, other
programmers can use them.
the experts worry about the implementation details
the other programmers will have an easy-to-use interface
it will be easier to change database management systems or user interface
technology
Data abstractions are often built by specialists. This is one practical way to leverage the skills
of an expert: have him/her create a class that everyone else on a software development project
can use.
Page 1 - 14
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 14
C++ constructors and destructors
C++ class objects can have special initialization and cleanup functions
these functions are called automatically at block entry and exit
#include <stdio.h>
#include <string>
// the string class has constructors and a destructor
void f() {
std::string message("hello, world"); /* message initialized */
int len = message.length();
int i;
for (i = 0; i < len; i++) {
std::string sub_string; /* sub_string set to empty */
sub_string = message.substr(0, i);
fputs(sub_string.c_str(), stdout);
/* the destructor for sub_string is called
before the next iteration */
}
/* the destructor for "message" is called before returning */
}
int main() { f(); return (0); }
Constructors and destructors will be discussed in more detail in Unit 2.
Page 1 - 15
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 15
Object oriented programming
C++ supports object oriented programming (but it doesnt force you to
program in an object oriented style)
in an object-oriented system, most of the code is in classes
new data types can be built from existing data types (using inheritance)
C++ virtual functions allow run-time selection of a function
In an object oriented program, almost all of the code is in classes. In contrast, it is possible to
use data abstraction programming to just create a small set of central C++ classes (less than
20% of the total code), which are used from non-class code that comprise the rest of the
system.
Object oriented development is more complex, and requires a bigger investment in planning
and training. Most of the developers need to have some class authoring skills, and it is
necessary to have a good overall object model early in the project that serves as a plan. This
can help prevent the creation of spaghetti classes.
Page 1 - 16
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 16
Inheritance
A derived class has all of the data members and functions of the base class
new data and functions can be added
base class functions can be redefined in the derived class
Example: Rental car
derived classes: Rental van, Rental sports car
common functions: customer_pickup, customer_return, clean, fill_with_gas
functions with different implementations for different classes:
compute_customer_charges, get_daily_cost
special functions: is_convertible (special function for Rental sports car)
Rental car
Rental van Rental sports car
Derived classes are sometimes called subclasses. Base classes are sometimes called
superclasses.
Page 1 - 17
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 17
Object oriented analysis and design
Object oriented programming changes the way you do analysis and design
since most of the system will be implemented in terms of classes, the
design process should try to produce high-level class descriptions
some techniques and notations: CRC cards, UML class diagrams,
C++ header files
object oriented analysis is an attempt to describe the problem domain n
terms of classes
Why is object oriented programming helpful in many situations? It is easier to
modify and extend a system that is designed in terms of objects
changes are more localized than in structured design
data abstractions are easier to use (and reuse)
extensions can be made by inheritance without changing existing code
Page 1 - 18
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 18
Summary
C++ is a versatile programming language that supports three different
styles of programming
its a better C
it supports data abstraction
it supports object oriented programming
Page 1 - 19
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 19
C++ syntax
In K&R C, a function declaration merely specifies the return type. If a
function is not declared, the compiler assumes that the function returns
an int.
/* C-style function declarations */
int strcmp();
FILE *fopen();
double sqrt();
In ANSI C, a function declaration can specify the argument types:
/* ANSI C-style function declarations */
int strcmp(char *, char *);
FILE *fopen(char *, char *);
double sqrt(double);
C++ function declarations have the same syntax as ANSI C function
declarations.
K&R stands for Brian Kernighan and Dennis Ritchie the authors of the original C
textbook The C Programming Language (Prentice-Hall, 1978).
Note that an ANSI C compiler will accept both the K&R C and the ANSI C function
declarations. The ANSI C language committee decided to do this so that old C code would be
portable to new C compilers.
C++ is not backward compatible to C. The main areas where it is incompatible is in function
declarations and definitions.
Page 1 - 20
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 20
Overloaded functions
C++ permits the declaration of several different functions with the
same name but different argument types:
extern double sqrt(double);
extern int sqrt(int);
int main() {
double d1;
int i2;
d1 = sqrt(2.0); // calls sqrt(double)
i2 = sqrt(144); // calls sqrt(int)
return (0);
}
The C++ compiler follows a complex set of function matching rules
to determine which function signature matches best.
arithmetic promotions are done first (e.g., char short int long)
standard conversions are next (such as int float)
finally, user-defined conversions are tried (such as single argument
constructors)
One reason that all functions must be declared before they are used in C++ is to support
overloaded functions and automatic type conversions in function calls. The C++ compiler
needs to know all of the function signatures for functions of the same name that it has to
choose from when it tries to determine the right function to call.
Page 1 - 21
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 21
Differences
There are two special function declarations in ANSI C and in C++:
int f(void); /* no arguments */
int g(...); /* unspecified arguments */
The first declaration says that the function never takes an argument. The
compiler will give an error message if the function f() is called with one or
more arguments.
The second declaration says that the function can take any number of
arguments of any type.
The following declaration is interpreted differently in ANSI C and in
C++:
int h();
/* this means int h(...) in ANSI C */
/* but it means int h(void) in C++ */
Some C library developers will create header files that contain function declarations that will
work with either C or C++, like this:
#ifdef __cplusplus
extern "C" {
#endif
struct timeval {
time_t tv_sec;
long tv_usec;
};
/* declarations of functions */
#ifdef __cplusplus
extern int adjtime(struct timeval *, struct timeval *);
#else
extern int adjtime();
#endif
#ifdef __cplusplus
}
#endif
Page 1 - 22
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 22
Function definitions
In C++, the ANSI C syntax is used in function definitions:
/* file openfile.c */
#include <stdio.h>
int openOutputFile(char *filename) {
FILE *f;
f = freopen(filename, "a", stdout);
return (f != NULL);
}
There are no differences in function definition syntax between ANSI C and C++. ANSI C
compilers will accept old K&R-style function definitions.
Page 1 - 23
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 23
More about functions
In C++, it is required to have a declaration of a function before the function is
used.
/* file addfile.c */
#include <stdio.h>
int main(int argc, char **argv) {
int sum = 0;
FILE *f = fopen(argv[1], "r");
char buf[512];
while (fgets(buf, 511, f) != NULL) {
sum += atoi(buf); // line 8
}
printf("total = %d\n", sum);
return (0);
}
The compiler will give an error message for line 8, because the atoi() function
is not declared before it is used.
This error message will go away if we put in #include <stdlib.h>
Some functions are declared in string.h, unistd.h, io.h, and other files
Page 1 - 24
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 24
Class declarations
A class declaration in C++ is like a structure declaration in C
the labels public, private, and protected are added to indicate access rights
both function declarations and data declarations are permitted
/* file Myclass.h */
#include <string>
class Myclass {
public:
void setdistance(int); // member functions
void setlocation(int);
int getdistance();
void print();
private:
int distance; // member data
std::string location;
};
Dont forget the semicolon at the end of the class declaration
It is possible to define some functions in the private section, but in that case, they could only
be called from other member functions of the same class.
It is also possible to define some member data in the public section, but good C++
programmers dont do this. They want the class to encapsulate the data, to prevent it from
being modified without any controls.
Page 1 - 25
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 25
Defining member functions
Each member function must be declared in the class declaration and
implemented in a source file
/* file Myclass.c */
#include <Myclass.h>
#include <string>
void Myclass::setdistance(int d) {
this->distance = d;
}
void Myclass::setlocation(char *loc) {
this->location = loc;
}
int Myclass::getdistance() {
return this->distance;
}
void Myclass::print() {
printf("distance=%d, location=%s\n",
this->distance, this->location.c_data());
}
The double-colon operator is called the scope resolution operator in C++. The name before
the double-colon is always a class name, and the name after the double-colon is most often a
function name. In these examples, this syntax is used to show which class the function being
defined belongs to.
Page 1 - 26
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 26
Calling class member functions
When calling a class member function
the class object appears first followed by a dot
then the function name
and the argument list in parentheses
Myclass loc1;
loc1.setlocation("Whippany");
loc1.setdistance(12);
Each member function can access and change the data members in the
class object that it operates on.
Page 1 - 27
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 1 - 27
Overloaded operators
All of the conventional operators can be redefined for class types in
C++.
The operators are redefined by writing a function:
int operator==(Myclass m1, Myclass m2) {
int retval = 0;
if (m1.location == m2.location && m1.distance == m2.distance) {
retval = 1;
}
return retval;
}
Binary operators take two arguments, unary operators take one
argument
You can redefine arithmetic operators, the assignment operator (the
first argument must be a class), comparison operators, or any other
C++ operator
Overloaded operators can be called in two ways:
Myclass mval1, mval2;
....
if (mval1 == mval2) { // this calls the operator==()
// function
....
}
if (operator==(mval1, mval2)) { // an uglier way, but
// still a correct way,
// to call the same function
....
}
Page 2 - 1
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 1
Unit 2: Constructors and Destructors
C++ Fundamentals
Alcatel-Lucent Bell Labs
Software Productivity & Quality Tools Dept.
http://www.stc.lucent.com/~dmm/intro
Page 2 - 2
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 2
Constructors
Constructors are used in four places in C++
defining default values for objects of a class
initializing some or all of the data fields of an object
making a copy of an object
this gets called automatically to copy function arguments onto the
stack
conversion of one type to another
this is the most mysterious use of constructors: objects are converted
like magic, sometimes when you least expect it
All C++ class authors need to know how to define the appropriate constructors for the classes
that they build.
Page 2 - 3
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 3
Constructor usage
class Myclass { .... };
extern void f(Myclass mval);
Myclass m1;
int main() {
Myclass m2;
Myclass m3(38, WH");
Myclass m4 = "MH";
Myclass m5 = m3;
f(m3);
Myclass *mptr = new Myclass(450, "CB");
m2 = m3;
return (0);
}
m1 and m2 use the default constructor
m3 uses a constructor that initializes some of the data fields
m4 uses a conversion constructor
the m5 declaration and the f(m3) call use a copy constructor
mptr points to an object allocated and initialized from the heap (dynamic
memory allocation using the malloc() function)
An important fact about declarations in C++: declarations of built-in types are just like C, but
declarations of class types will cause the compiler to insert calls to a constructor function at
the point of the declaration.
C++ declarations (unlike in C) that are within a function or a block are not required to be
located at the beginning of the block: they can be intermixed with executable statements.
Note that there are two equivalent options for the declaration of m4:
Myclass m4 = "MH";
Myclass m4("MH");
Both of these declarations will cause the one-argument constructor to be invoked.
Page 2 - 4
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 4
Defining constructors
The constructors used in the previous slide can be declared this way:
class Myclass {
public:
Myclass();
Myclass(int, const char *);
Myclass(const char *);
Myclass(const Myclass &);
private:
// private data declared here ....
};
The name of the constructor is always the same as the name of the class
Constructors dont have a return value
Some constructors will be generated automatically:
If you dont define any constructors, you get two generated for free: the
default constructor (with no arguments) and the copy constructor (with const
Myclass & argument)
If you define any constructors, the copy constructor will still be generated
automatically, but the default constructor will not be generated
The automatically-generated default constructor will call the default constructor for each data
member of the class. For data members that are built-in types (int, char *, double, etc.), the
automatically-generated default constructor will leave them uninitialized.
The automatically-generated copy constructor will call the copy constructor for each data
member of the class. For data members that are built-in types, the copy constructor will copy
the bits from one object to another.
Page 2 - 5
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 5
Initialization within a constructor function
Two ways to write a constructor definition: assignment vs.
initialization syntax:
#include <string>
class Myclass {
public:
Myclass(int, const char *);
private:
int distance;
std::string location;
};
Myclass::Myclass(int d, const char *loc) {
this->distance = d; // assignment syntax example
this->location = loc;
}
or you can write it this way:
Myclass::Myclass(int d, const char *loc) :
distance(d), location(loc) // initialization syntax example
{}
The assignment syntax version is more readable for C programmers, but the initialization
syntax is often the preferred style. In some cases, initialization style is more efficient, and in
other cases, initialization style is the only one that will compile.
Page 2 - 6
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 6
The default constructor
The default constructor is the constructor with no arguments
Myclass::Myclass() : distance(0), location("") { }
The default constructor is used in four circumstances:
when you declare a class object with no arguments
Myclass m1;
when you create an object from the heap without giving any arguments
Myclass *mptr1 = new Myclass;
when you create an array of objects from the heap
Myclass *marrayptr = new Myclass[100];
in the constructor of another class
class Newclass {
public:
Newclass();
private:
Myclass loc;
};
Newclass::Newclass() {
// Myclass default constructor
// will be invoked before the body
// of the Newclass constructor
this->loc.setdistance(35);
}
Page 2 - 7
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 7
Some simple rules for constructors
Always initialize every data member in the class in every constructor
you can use either assignment syntax or initialization syntax
C built-in types (int, char*, etc.) dont have default values, so if your
class contains an int, make sure you initialize it in every constructor
In a derived class constructor, let the base class constructor initialize as
much as it can
you will probably need to use initialization syntax
If a constructor allocates memory, be sure that the memory is
deallocated somewhere (usually in the destructor)
Classes that allocate their own memory usually define the copy
constructor, rather than relying on the automatically-generated copy
constructor
It is important to initialize data members in your constructors (especially built-in types like int
and pointers) because an uninitialized field in a data structure may cause a failure somewhere
else in the class code:
if you are lucky, the use of the garbage data or pointer will cause the program to stop
executing or dump core
if you are unlucky, the use of the bad data will cause other data within your system to
be silently corrupted
Page 2 - 8
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 8
Constructors in derived classes
class Myclass { .... };
class Travelclass : public Myclass {
public:
Travelclass();
Travelclass(int d, const char *loc, const char *method);
Travelclass(int d, const char *loc);
private:
string transport_method;
};
Travelclass::Travelclass() : Myclass(), transport_method("") {}
Travelclass::Travelclass(int d, const char *loc, const char *m) :
Myclass(d, loc), transport_method(m) {}
Travelclass::Travelclass(int d, const char *loc) :
Myclass(d, loc),
transport_method(d > 100 ? "airplane" : "car") {}
void main() {
Travelclass d1(250, "Boston", "train");
Travelclass d2(450, "Columbus");
}
This slide shows the syntax for creating derived classes in C++.
Constructors are not inherited in C++, but each derived class constructor must specify which
base class constructor will be used to initialize the data members in the base class part of the
new derived class object. The initialization syntax is used to do the specification of the
argument list for the base class constructor.
If you dont specify a base class constructor in the initialization list of the derived class
constructor, the compiler will invoke the zero-argument (default) constructor in the base class.
If the base class doesnt have a default constructor, then you will get a compile-time error
when you try to compile the derived class constructor.
Page 2 - 9
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 9
Destructors
There can many constructors for a class, but only one destructor.
You usually dont call a destructor: the call is generated by the C++
compiler
at the end of the block or function (for automatic on the stack objects)
in the exit() function (for static objects)
when you call the delete operator (for dynamic objects)
An example destructor:
Myclass::~Myclass() { }
Note that this destructor doesnt need to explicitly call the destructors for
any of the data fields in Myclass
the compiler will automatically add the destructor calls for any class
objects contained in Myclass just before the return at the end of the
destructor.
The C++ compiler inserts calls to the default constructor for any object that is going out of
scope at the end of a block or function. It is a bad idea to use goto statements that bypass
the end of a block that contains the declaration of an object with a destructor: most compilers
will give a warning that this might cause problems. You must be very careful with the
placement of gotos in C++ functions.
Page 2 - 10
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 10
When should a destructor do anything?
The main job of a destructor is to release any resources that have been
acquired by the object, either in the constructor or in other functions.
The destructor should:
close any files that were opened
unlock any databases that were locked
free any dynamic memory that was allocated
Destructors can also contain error printing that can be used to help
find memory utilization problems
you might add instrumentation code to all constructors and destructors to
keep a global object count: add one in each constructor, deduct one in
each destructor
Destructors are useful for a bunch of instrumentation functionality, not just the closing of
files and the cleanup of dynamically allocated memory.
Page 2 - 11
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 11
Summary
Constructors are useful for setting initial values and doing dynamic
memory allocation in C++ classes
The default constructor and the copy constructor are automatically
generated, but you sometimes need to define your own
A destructor function should release all of the resources that have been
attached to the object in the constructor and in other class functions
Page 2 - 12
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 12
Inheritance and virtual functions
Inheritance is one way to build new datatypes from existing datatypes
building extensions or variations of other C++ classes
Declaring a virtual function causes the compiler to postpone the
binding of a function call to an actual function definition
Inheritance and virtual functions make it easier to create flexible
object-oriented software
Page 2 - 13
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 13
Inheritance
You can create a derived class from a base class using inheritance:
class BasicEmployee {
protected:
string name, id_number;
float salary;
float bonus;
public:
BasicEmployee(string, string);
void set_salary(float);
};
class ManagementEmployee : public BasicEmployee {
private:
float budget;
public:
ManagementEmployee(string, string);
void set_budget(float);
};
A management employee has all the basic attributes and actions, but also has
budget information and actions.
The data structure defined by the derived class will contain all of the data from the base class
plus the new data fields defined in the derived class. For example, the ManagementEmployee
class will have five data attributes: name, id_number, salary, bonus, and budget.
The public interface (the set of public functions) for the ManagementEmployee class will
contain all of the functions declared in the base class (except for the constructors and
destructors) and the functions that are declared in the derived class. For example, the
ManagementEmployee class will have three functions defined: the ManagementEmployee
constructor, the set_salary(float) function, and the set_budget(float) function.
Page 2 - 14
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 14
Why inheritance?
It saves typing
There is a single point of change for many basic attributes and actions (so it is
easier to fix bugs)
The structure of the software is now parallel to the structure of the problem
domain
An existing datatype can be extended with new data
The new derived class can redefine functions defined in the base class
class BasicEmployee {
public:
virtual void set_salary(float);
....
};
class ManagementEmployee : public BasicEmployee {
public:
void set_salary(float); // this function overrides the basic function
....
};
Inheritance is useful for putting the common parts of a design in one place. This reduces the
chances of mistyping a field name or a function definition that would ordinarily need to be
repeated in several structures.
Both bug fixes and upgrades to basic class behavior can be handled more cleanly, because all
of the common stuff is in one place.
Derived classes are often used to add new features to an already-working implementation. The
philosophy is: Dont make changes to a working system. Extend the system instead.
Page 2 - 15
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 15
More inheritance examples
Derived class may reuse some data from the base class
class LocalCall {
public:
char local_digits[max_local_digits + 1];
char *get_number() { return &local_digits[0]; }
};
class NonlocalCall : public LocalCall {
public:
char country_code[max_country_digits + 1];
char area_code[max_area_code_digits + 1];
char *get_number() { more complicated implementation }
};
int main() {
LocalCall c1;
NonlocalCall c2;
char *num1 = c1.get_number();
char *num2 = c2.get_number();
char *num3 = c2.LocalCall::get_number();
}
What is going on here? There are two classes defined in this code LocalCall and
NonlocalCall.
The data section of LocalCall contains a single character array (to hold the telephone number
for a local call).
The data section of NonlocalCall actually contains three character arrays the two arrays
specified in the NonlocalCall class declaration plus the array defined in the base class.
When you call a member function, the identity of the get_number() function that is called
depends on the actual type of the object c1.get_number() will call LocalCall::get_number(),
whereas c2.get_number() will call NonlocalCall::get_number().
If you define a function in the derived class with the same name as a function in the base class,
that base class function becomes inaccessible unless you use the explicit scope in the function
call the LocalCall::get_number() syntax.
Page 2 - 16
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 16
More inheritance examples
A derived class object actually contains a base class object
class LocalCall {
};
class NonlocalCall : public LocalCall {
};
5827086 local_digits:
5827086 local_digits:
1 country_code:
908 area_code:
a LocalCall object
a NonlocalCall object
A NonlocalCall object contains everything that is in a LocalCall object plus the extra
attributes that are declared that are special for the NonlocalCall.
Always remember inheritance means there can be both common data and common
operations between the base class and the derived class.
Page 2 - 17
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 17
Virtual function syntax
Any normal member function or destructor can be declared as virtual
This declaration changes some of the code generation rules for the
calls to that function
Example:
class BasicEmployee {
public:
virtual void set_salary(float);
};
class ManagementEmployee : public BasicEmployee {
public:
void set_salary(float); // also a virtual function
// because base is virtual
};
Virtual is a special keyword in C++ -- it triggers a special set of code generation rules for
function calls through pointers and references.
Virtual is not required it is OK to use inheritance without defining base class functions to be
virtual. In some designs, virtual functions arent needed, so C++ gives the developer the
option to declare classes that will always use conventional member function calls.
Note: in Java, all class member functions are virtual by default.
Page 2 - 18
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 18
Virtual functions
One important pointer rule in C++: a base class pointer can point to
any derived class object
BasicEmployee *bptr1, *bptr2;
bptr1 = new BasicEmployee("Aaron Anthony", "1358282");
bptr2 = new ManagementEmployee("Betsy Blake", "1484646");
ManagementEmployee *mptr1;
mptr1 = new ManagementEmployee("Nancy Ng", "1500819");
The new operator is one way to create a dynamically-allocated object in C++. Remember that
new will allocate space for the object, call the constructor, and return a pointer to the new
object. Sometime later in the application, someone will have to call delete to free the object.
Page 2 - 19
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 19
Virtual functions
The C++ compiler treats virtual functions in a special way:
if the function is called through a pointer, the code that is generated by the
compiler will check the actual type of the object
BasicEmployee *bptr1, *bptr2;
bptr1 = new BasicEmployee("Aaron Anthony", "1358282");
bptr2 = new ManagementEmployee("Betsy Blake", "1484646");
bptr1->set_salary(65000.0); // virtual function call
bptr2->set_salary(85000.0); // virtual function call
BasicEmployee b("Carol Charles", "1653131");
b.set_salary(68000.0); // normal function call
C++ language rules permit a base class pointer to point to a derived
class object
so it is easy to define a set of objects of different types
a program might call the same operation on each object, but it will
actually make calls to different functions for the same operation name
Polymorphism is the word that is used by many object oriented technology experts to refer
to the way that C++ function calls might map to different function implementations.
this is actually a case of run time polymorphism, because the compiler cant
determine which function to call
This is also referred to as dynamic binding, because the connection between the function call
and the code that will be executed is dynamic: determined by the executing program rather
than the compilation system (which is the usual case with static binding).
Virtual functions calls are translated into a small block of code that determines the correct
function to call based on a special internal table that the object points to. There is a small
amount of extra execution-time overhead for a virtual function call compared to a regular
function call.
Page 2 - 20
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 20
Implementation of virtual functions
If a class contains one or more virtual functions, each object will
contain a special pointer that is initialized in the constructor:
This virtual function pointer (vptr) will point to a table of virtual
function pointers for that class (vtbl) that was set up at compile time
The code generated for each function call that could be polymorphic
will use the vptr to get to the vtbl to find the right function to call
Mary :BasicEmployee
Harry : MgmtEmployee
Carol : BasicEmployee
Sarah : MgmtEmployee
void set_department() {
BasicEmployee *eptr;
eptr = get_first_empl();
while (eptr) {
eptr->set_salary(x);
eptr = get_next();
}
}
internal secret
pointer (vptr)
pointers to
Employee
operations
pointers to
Manager
operations
All of the information needed to implement virtual functions is available at compile time.
The vtbl (virtual function table) can be built by the compiler. One vtbl is built for each class
that contains virtual functions. It contains pointers to each of the virtual functions, in the order
in which the functions are declared in the class declaration.
Each constructor will initialize the vptr to point to the correct vtbl.
Each call to a virtual function will generate code that uses the vptr to find the vtbl. Then the
pointer to the correct function is at an offset within the vtbl that is known at compile time.
Page 2 - 21
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 21
Virtual functions replace
switch statements
When are virtual functions useful?
applications that work with a family of similar classes
examples: computer graphics, hardware configuration, process
control, inventory, knowledge representation, ...
in a traditional implementation, there would
be a lot of code like this:
switch (sptr->tag_field) {
case BOARD_MAIN:
initialize_main(sptr);
break;
case BOARD_CHANNEL:
initialize_channel_board(sptr);
break;
case BOARD_CLOCK:
initialize_clock_board(sptr);
break;
}
in an OO implementation, this
code might look like this:
sptr->initialize();
// each board class has
// its own initialize()
// function
Object oriented programming with virtual functions and dynamic binding has two advantages
over traditional coding:
the code is more compact (and easier to follow)
it is much easier to add a new variation (a new graphical object, a new board type, a
new process element, and so on) without having to find all of the places where the
type tag is used in the application
Page 2 - 22
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 2 - 22
When should a destructor be virtual?
Virtual destructor example: Virtual is required when you might
do a delete on a Derived object
through a Base*
the virtual declaration arranges
the code generation to get the
correct destructor based on the
actual type of the object
int main() {
Base *baseptr;
baseptr = new Base;
....
delete baseptr;
baseptr = new Derived("logfile");
....
delete baseptr;
return (0);
}
// two classes with related ops
class Base {
public:
virtual ~Base() {}
....
};
class Derived : public Base {
public:
Derived(const char *filename) {
this->f = fopen(filename, "r");
}
~Derived() { // must close log
if (this->f != NULL)
fclose(this->f);
}
....
private:
FILE *f;
};
Virtual destructor declarations are usually made in a base class where the derived classes
might contain extra data attributes. The virtual function call mechanism will be described
later in this unit: in this case, it ensures that the destructor that is called will be the appropriate
destructor for the actual object that was created.
Page 3 - 1
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 3 - 1
Unit 3: Inline functions
C++ Fundamentals
Alcatel-Lucent Bell Labs
Software Productivity & Quality Tools Dept.
http://www.stc.lucent.com/~dmm/intro
Page 3 - 2
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 3 - 2
Inline functions
Inline functions are useful
they are good for defining tiny functions
they can improve the efficiency of a program
Any function can be defined as an inline function
you can make ordinary functions and member functions into inline
functions
the treatment of inline function definitions is compiler-dependent
the compiler may expand the inline function definition many times in
a program at any place where the function is called
the compiler is also permitted to convert an inline function definition
into a static function definition
be careful with creating too many inline functions: inline expansion
of a function and multiple static function definitions can waste a lot of
memory space
Inline function declarations in C++ are sometimes said to be a hint to the compiler. They
are never an absolute command to change the code generation technique: many compilers will
convert the inline function into a local static function if the compiler thinks it would be
better.
In C programs, some programmers use preprocessor macros to get the same effect as C++
inline functions. Preprocessor macros arent as flexible as inline functions:
you can define overloaded inline functions: several inline functions that have the
same name but different arguments
an inline function is treated just like a regular function by the C++ compiler when
applying the argument type checking, function matching, and argument conversion
rules
Page 3 - 3
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 3 - 3
How to write an inline function
There are two ways to do it
use the inline keyword in the function definition
put a function definition inside a class declaration
#include <string>
inline int max(int a, int b) { // ordinary inline function
if (a >= b) return a;
else return b;
}
class Book { // both set functions are inline
public:
void set_author(const std::string &a) { this->author = a; }
void set_title(const std::string &);
private:
std::string author;
std::string title;
};
inline void Book::set_title(const std::string &t) {
this->title = t;
}
The second syntax (defining the inline member function outside of the class declaration) is
slightly preferred, because the programmer has more control over deciding which order the
inline function definitions will appear in the source file.
Page 3 - 4
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 3 - 4
Style points for inline functions
Inline functions should always be defined in a .h file
Inline virtual functions are OK
It is OK for an inline function to contain if statements and function
calls, but some compilers dont expand inline functions that contain a
loop
EDG compiler wont inline a function with a loop
G++ will inline a loop (with O option) and so will Microsoft C++ (with
/Ob1 option)
Why are inline functions usually defined in a .h file? They are much less useful in a .c
file, because they can then only be used within that .c file.
Page 3 - 5
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 3 - 5
Accessor and modifier functions
Many class definitions have accessor functions that retrieve part of the data
structure encapsulated by the class:
class Book {
public:
std::string get_author() const;
std::string get_title() const;
private:
std::string author;
std::string title;
};
inline std::string Book::get_author() const {
return this->author;
}
inline std::string Book::get_title() const {
return this->title;
}
Similarly, modifier functions that set only one data field in the class are easy to
implement as inline functions
Page 3 - 6
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 3 - 6
Frequently called functions
A common procedure for improving performance of an application program is
to introduce inline functions
Look for opportunities to factor an existing function into two parts
a simple inline part
a more complex non-inline part
Example: loading a buffer
class Buffer {
public:
void load_value(char c);
void dump_buffer();
private:
char buf[512];
char *cur;
};
inline void Buffer::load_value(char c) {
if (cur == &buf[512]) dump_buffer(); // dump_buffer() resets cur
*cur++ = c;
}
In the example above, the dump_buffer() function wont be called very often. Defining the
load_value() function as an inline function ought to result in some execution-time savings.
Page 3 - 7
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 3 - 7
Cost-benefit analysis
Using inline functions is a tradeoff between space and time
the code is usually larger (the same code is repeated many times)
the execution time is shorter (by avoiding the cost of putting arguments on
the stack and returning a return value)
The benefit of using an inline function depends on 5 factors:
1. the object code size of the function
2. the object code size of the stacking, jumping, and cleanup code
3. the execution time of the stacking, jumping, and cleanup code
4. the number of places in the program where the function is called
5. the average number of times the function is actually executed in a single
run of the program
The space cost is [(1) (2)] (4)
The time savings is (3) (5)
Note that for very small functions (if the quantity (1)(2) is negative), the space cost can be
negative.
These numbers are sometimes difficult to collect. The values of (3) and (5) require some
dynamic analysis: (3) can be measured by running small pieces of code multiple times and
measuring the time it takes, and (5) might be determined through the use of some program
instrumentation, such as a profiler. (1), (2), and (4) are mostly static information: some
compilers, browsers, or other static analysis tools can determine these values.
Most developers dont even try to collect and do a thorough analysis of these items, but they
use the estimated size of a function and the number of times it will be executed to help select
which functions to convert to inline.
Page 3 - 8
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 3 - 8
Using an inline function that
hasnt been defined yet
When developing a library, it is important to be sure that the inline
functions are defined in the correct order.
if there is a forward reference to inline function g() in the body of inline
function f(), the compiler might have trouble inlining everything
the compiler might force the generation of a static copy of the function
g(), which will be called from each inline expansion of function f()
the creation of a static copy will occur with the EDG compiler and the
G++ compiler
the Microsoft Visual C++ compiler will still be able to inline function
g() in the expansion of function f()
You should compile your programs with warnings turned on in order
to find these problems:
for G++, compile with -Winline; for EDG, compile with -r
Note that it is silly to make a const declaration for a non-pointer argument: the function
body is manipulating a copy of the argument anyway, so who cares if it is modified?
Page 3 - 9
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 3 - 9
Example
#include <memory.h>
class SimpleStr {
public:
SimpleStr();
SimpleStr(int str_length);
inline void clrbuf();
private:
char *stringbuffer; // pointer to dynamic memory area
int length;
};
inline SimpleStr::SimpleStr(){
this->stringbuffer = 0;
}
inline SimpleStr::SimpleStr(int str_length) : length(str_length) {
this->stringbuffer = new char[str_length];
clrbuf(); // forward reference to an inline function - not good
}
inline void SimpleStr::clrbuf() {
if (this->stringbuffer != 0)
memset(this->stringbuffer, 0, this->length);
}
Some compilers have trouble with this example. If they are generating some of their internal
inline function information in a one pass fashion, then the forward reference from one inline
function to another (the SimpleStr(int) constructor calling SimpleStr::clrbuf()) might cause the
compiler to generate a static copy of the SimpleStr::clrbuf() function.
Page 3 - 10
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 3 - 10
Inline constructors
Remember that constructors automatically call the default
constructor for each data member in a class
unless you specify otherwise using the member initialization syntax
This means that constructors can be deceptively complex:
class Myclass {
public:
Myclass();
private:
int distance;
std::string location;
};
inline Myclass::Myclass() { this->distance = 0; }
The Myclass constructor will also call the default string constructor on
the data member location
Constructors are almost always more complex that they look. Some software development
projects have adopted a no inline constructors rule, because they are afraid that their
developers will misguess the space overhead caused by defining inline constructors.
Page 3 - 11
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 3 - 11
Inline virtual functions
It is usually harmless to declare a virtual function to be inline.
The compiler will create exactly one non-inline copy of the function (in
the same object file where it puts the virtual function table for the class)
class Employee {
public:
virtual float get_bonus() const;
protected:
float salary;
static float bonus_rate;
}
inline float Employee::get_bonus() const {
return (this->salary * Employee::bonus_rate);
}
However, it is a good idea to have at least one non-inline virtual function.
Most compilers will generate the virtual function table in the file where
the first non-inline virtual function is defined
Page 3 - 12
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 3 - 12
Debugging inline functions
You may decide to cancel expansion of inline functions when you
want to use a source-level debugger on your program
This will permit breakpoints to be set within inline functions
How to cancel inlining:
Microsoft Visual C++: use the /Ob0 option
G++: dont compile with the O option
EDG: use either +d or --no_inlining option
HP aCC compiler: use +d or +inline_level 0 option
An alternative: you can use the preprocessor to selectively control the
expansion of inlines
see pp. 370-371 of Advanced C++ Programming Styles and Idioms by
Jim Coplien for one way to do this
In the selective inlining scheme, the class developer uses a macro such as INLINE in the
definition of the class, and the class user has the option of compiling their code with either
DINLINE=inline or DINLINE=
depending if they want inlining completely on or off, or the developer can insert lines like
#define INLINE inline
#include "Xclass.h"
#undef INLINE
#define INLINE
#include "Yclass.h"
to have more selective control over inlines in different sections of the application.
Page 3 - 13
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 3 - 13
Summary
Inline functions are good for simple operations
You can sometimes divide an operation into an easy part and a more
complex part
Use compiler options that help find non-inline inlines
Inline constructors are more complex than they look
Inline virtual functions are OK
Page 4 - 1
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 1
Unit 4: Const and references
C++ Fundamentals
Alcatel-Lucent Bell Labs
Software Productivity & Quality Tools Dept.
http://www.stc.lucent.com/~dmm/intro
Page 4 - 2
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 2
Const
Const is useful in five places
defining arbitrary constants (integers and strings)
declaring function arguments that are pointers
this is the #1 use of const: it is essential for all C++ programmers to learn
this use of const
const member functions
a corollary of const function arguments
const return value from a function (not very important)
const data member (not very important)
All C++ programmers need to understand the meaning of const in function arguments and
const member functions (the second and third items on the list above).
Page 4 - 3
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 3
Const declarations
It is good programming style to use const on declarations of strings that
wont change during the program:
static const char *filename = "main.c";
const char *error_message1 = "can't open file %s\n";
Const can also substitute for #define for integer constants
const int maxvalue = 1000;
int histogram[maxvalue];
Normal const declarations have file scope. It is possible to make a const
declaration that has class scope, but not all older C++ compilers support
this:
class Myclass {
public:
static const int bufsize = 256;
/* or enum {bufsize = 256}; for older compilers */
char bigbuf[bufsize];
};
int bigbuf2[Myclass::bufsize];
Some C++ programmers prefer to use the preprocessor for defining arbitrary constants in
application programs. Preprocessor macros, using the #define syntax, can be used for both
char string and integer constants:
#define FILENAME "main.c"
#define ERR_MESS1 "can't open file %s\n"
#define MAXVALUE 1000
#define MYCLASS_BUFSIZE 256
The primary advantage of using #define to create these definitions is that it is easy to override
the initial values defined in a header file with a new set of values defined within another
source file.
Page 4 - 4
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 4
Using const for pointers
Const is often used to restrict the kinds of operations that can be performed on
a pointer. This comes up most often when declaring the arguments to a
function:
// copy source to result
void strcpy(char *result, const char *source) {
while (*result++ = *source++) { }
}
// bigptr points to an array of Big objects -- findmatch() finds
// the first element in the array where bigptr->key matches value
const Big *findmatch(const Big *bigptr, int value, int max) {
for ( ; max > 0; max--) {
if (bigptr->key == value) {
return (bigptr);
}
bigptr++;
}
return ((const Big *) 0);
}
In the first example:
The result argument is non-const: the pointer can be used to scribble data in the
array that the calling program hands to the strcpy function.
On the other hand, the source argument is const, which means that the
implementation of strcpy is not permitted to store anything in the memory region that
source points to.
In the second example:
The bigptr argument points to the first item in an array of Big objects. Since this
is declared as a const pointer in the function definition, the findmatch function is not
permitted to modify any of the fields in the Big structures: it can only look at the
values.
Page 4 - 5
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 5
Examples where const wont work
The C++ compiler will give an error message when you use a const pointer
incorrectly:
// make a string all capitals
void capitalize(const char *str) {
// this should be capitalize(char *str)
while (*str) {
*str = toupper(*str); // compile error
str++;
}
}
// incrementlist() adds one to the key field of all the objects
void incrementlist(const Big *bigptr, int max) {
// this should be incrementlist(Big *bigptr, int max)
for ( ; max > 0; max--) {
bigptr->key++; // compile error
bigptr++;
}
}
In the first example:
The capitalize function is breaking the rules. The str argument is declared as a
const pointer, but the *str = toupper(*str) statement is assigning a new value to one of
the characters of the character array.
In the second example:
The incrementlist function is breaking the rules. The bigptr argument is declared
as a const pointer, but the bigptr->key++ statement is modifying one of the fields in
elements of the array pointed to by bigptr.
In both cases, the compiler gives an error, which alerts the function authors that they are
violating the contract that they have specified for the users of the functions.
Page 4 - 6
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 6
Calling functions with
const pointer arguments
/* from file string.h */
extern "C" {
char *strcpy(char *, const char *);
int strcmp(const char *, const char *);
}
/* file main.c */
const char *myaddress = "600 Mountain Ave.";
char buffer[100];
char *p1 = &buffer[0];
char *p2 = &buffer[50];
void g() {
strcpy(p1, myaddress); // OK
strcpy(p2, p1); // OK - can call a function that takes a
// const char * with a char *
strcpy(myaddress, p2); // compile error - strcpy() might overwrite
int i = strcmp(myaddress, p2); // OK
}
A const pointer argument is more permissive than a non-const pointer argument: it
allows the function to be called with either a const or a non-const pointer
This slide shows the function users side: in this case, the functions that take more const
pointer arguments are more open to being called with pointers to either const or non-const
objects.
For C++ pointers, there are actually 4 different possibilities in the declaration. The use of
const determines what kinds of assignments are legal...
If the declaration is: Then p = newp is: And *p = c is:
char *p; OK OK
char *const p; illegal OK
const char *p; OK illegal
const char *const p; illegal illegal
Note that when you use some C/C++ compilers (for example, the GNU gcc/g++ compiler), it
is a runtime error to make changes to the contents of a literal string because they are
allocated in text space rather than data space. There is still an advantage of using the const
char * declaration, because an attempt to modify the string will be a compile-time error.
Page 4 - 7
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 7
Const member function
A member function always has an extra argument: the this pointer
Example of an ordinary member function:
void Big::setkey(int data) { this->key = data; }
in this case, the this pointer is a non-const pointer so the internal
implementation of this function looks like this:
void setkey(Big *this, int data) { this->key = data; }
But there is a special syntax for accessor functions (functions that
dont modify the internal state of the object)
A const member function has a const at the end:
int Big::getkey() const { return this->key; }
in this case, the this pointer is a const pointer
int getkey(const Big *this) { return this->key; }
C++ class member functions are always either accessor functions or modifier functions.
An accessor function (also known as a const member function) is only permitted to look at
information in the objects data structure, but it cant change anything. A modifier function
(also known as a non-const member function) can and probably will make changes to the
internal state of an object.
Page 4 - 8
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 8
Const checklist
Look for all arbitrary integer constants consider whether they should
be defined with either #define or const int
Look for literal character strings that will not be modified during the
course of execution most of these should be declared as const char *
Look at all pointer arguments if the function argument points to a
region of memory that will not be updated during the execution of the
function, it should be changed to a const pointer argument.
Look at all the member functions if the function never makes any
changes to any of the data within the data section of the class, it should
be changed to a const member function
Note that it is silly to make a const declaration for a non-pointer argument: the function
body is manipulating a copy of the argument anyway, so who cares if it is modified?
Page 4 - 9
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 9
Returning a const pointer
There is another situation where const is sometimes used.
a function returns a pointer into some private memory space that the calling
function should not modify
class Myclass {
public:
const char *datastart() const;
private:
static const int arraysize = 100;
char localarray[arraysize];
};
inline const char *Myclass::datastart() const {
return (&this->localarray[0]);
}
int main() {
Myclass m1;
strcpy(m1.datastart(), "address"); // compile error
const char *p = m1.datastart(); // OK
return (0);
}
Normally, the secret internal details within a class are supposed to be completely inaccessible,
but sometimes it is efficient and convenient to allow application code to read some of the data
within a class structure. It is usually best to use a const pointer rather than an ordinary
pointer, so that the application code cant inadvertently modify the internal state of the object.
Note that the Myclass::datastart() function has two consts in its declaration: the first const
tells the compiler that the pointer that it returns will point to a memory region that shouldnt be
modified. The const at the end of the declaration tells the compiler that the implementation of
the Myclass::datastart() function will not make any changes to the internal state of the Myclass
object.
Page 4 - 10
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 10
Const data member
A const data member is member data that must be initialized at
construction time and cant be modified thereafter:
class Myclass {
public:
Myclass(const char *val);
private:
const int length; // length and data will be
const string data; // initialized in constructors
};
Myclass::Myclass(const char *val) :
length(strlen(val)),
data(val)
{}
Const data members arent used very often. They are useful for declaring data fields in the
class that are set once in the constructor and never changed: the compiler will be able to
enforce the rule that the state is never changed.
Notice that in C++ constructors, const data members must be initialized with initialization
syntax. Assignment syntax wont work: the value is already const by the time the control
flow reaches the opening bracket of the constructor function definition.
Page 4 - 11
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 11
Breaking the rules
Sometimes it is necessary to use a cast on a function argument. This occurs
most often when you need to make a temporary change:
int compare_firstname(const char *s, const char *target) {
const char *p = s;
while (isalpha(*p)) p++; // scan to end of the first name
char tmp = *p;
*(char *)p = '\0'; // or *(const_cast<char*>(p)) = '\0';
int retval = strcmp(s, target);
*(char *)p = tmp; // or *(const_cast<char*>(p)) = tmp;
return (retval);
}
const char *names[] = {
"William Miller", "Lloyd Bentsen", "Jack Kemp",
"Joe Liebermann", "John Edwards", 0 };
int main() {
int i;
for (i = 0; names[i] != 0; i++) {
if (compare_firstname(names[i], "Jack") == 0) {
....
}
}
}
C++ shares one important thing with C: it is possible to bend any language rule by using a cast. The
cast tells the compiler I know what Im doing... trust me.
In this example, the compare_firstname function is implemented using the strcmp function. The
compare_firstname function violates the const rules when it overwrites some of the characters in the
subject string s but it changes them back before leaving the function. So we say that
compare_firstname actually maintains the spirit of the const rule: the state of the memory that s
points to is the same before and after the function call (even though it is temporarily modified in
between).
It is considered poor C++ coding style to make wholesale use of casts, but this kind of localized const
rule violation is usually considered OK. It is very important, however, to make sure that the casts are
well documented, so that another programmer can understand the code well enough to not break things
later.
(Important note: This program might not work correctly when compiled with the GNU g++ compiler.
If the program is compiled with standard compile-time flags, the compiler puts any string data (such
as the values in the names array) into a read-only memory area. You may get a run-time error when
you try to modify this data. There is a workaround with g++ -- compile this source code with the -
fwritable-strings flag, which requests that the g++ compiler put the strings in a memory area that can
be modified.)
Page 4 - 12
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 12
References
References in C++ are a way to make an alias for an object. This is
most often of some practical use in function argument declaration
use #1: function arguments that the function will modify
use #2: efficiency to avoid making a copy of a large object
All uses of references other than as function arguments or return values
are potentially dangerous
References have a confusing syntax: the & symbol in a declaration means reference, but
the & symbol in a normal C++ expression means address of.
References are related to pointers, but a reference cant be a substitute for a pointer.
the syntax for dereferencing a reference is a lot different than the syntax for
following a pointer
a reference must be set to refer to an item at the point when it comes into scope, and
it cant be modified to refer to something else until it goes out of scope and comes
into scope again
Page 4 - 13
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 13
Using references in function arguments
You should use references in function arguments where the value of
the argument will be changed within the function and the calling
program needs the changed value
void swap(int &x, int &y) {
int tmp = x;
x = y;
y = tmp;
}
main() {
int one=1, two=2;
swap(one, two); // swap() will modify one and two
}
The arguments are actually passed as addresses
Reference argument declarations are relatively common in C++ applications.
In the C programming language, function parameters are always passed by value: the value
of the argument is copied onto the stack.
In C++ you have a choice. Normal C++ arguments are still passed by value, but if the
declaration of the parameter in the function definition contains an & character, the argument
is passed by reference.
This is almost identical to var parameters in Pascal.
Page 4 - 14
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 14
Using references in function arguments
// incorrect C code
// it swaps on the stack
void swap(int x, int y) {
int tmp = x;
x = y;
y = tmp;
}
main() {
int one=1, two=2;
swap(one, two);
}
// correct C version
// it uses call by pointer
void swap(int *x, int *y) {
int tmp = *x;
*x = *y;
*y = tmp;
}
main() {
int one=1, two=2;
swap(&one, &two);
}
In C, we need to use call by pointer which will put the address of the objects on the stack.
In C++, call by reference reduces the burden on the function developer and the function user
the reference arguments are identified in the function prototype, and the C++ code
generation automatically deals with sending the pointers and dereferencing the pointers
throughout the
Page 4 - 15
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 15
Const reference arguments
You can avoid copying large structures on the stack by using a
reference argument. If the structure isnt modified by the function, the
argument should be const reference.
/* getloc() doesn't modify the "m" argument */
void getloc(const Myclass &m, std::string &s) {
s = m.getlocation();
}
void getloc1(const Myclass &m, std::string &s) {
s = m.getlocation();
if (s == "") {
m.setlocation(default_loc); // compile error
}
}
Note: you cant call a non-const member function on an object passed in
as a const references
Const reference arguments are a common C++ idiom. They provide a combination of
efficiency and syntactic convenience.
functions could be defined to take a const pointer argument, but the caller needs to
remember to use & for the argument in the call, and the callee needs to use * to
dereference the argument
Page 4 - 16
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 16
Reference return value
A function can have a reference return value
this can avoid some extra copying work in nested function calls
this is useful for some standard functions: assignment, I/O functions
class Myclass {
....
Myclass& operator=(const Myclass &); // assignment operation
friend std::ostream& operator<<(std::ostream&, const Myclass &);
};
Myclass& Myclass::operator=(const Myclass &m) {
this->location = m.location;
this->distance = m.distance;
return (*this);
}
std::ostream& operator<<(std::ostream &os, const Myclass &m) {
os << "(" << m.location << ";" << m.distance << ")";
return (os);
}
The declarations that are specified above for the assignment operator and the operator<<()
function are standard C++ idioms.
Page 4 - 17
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 17
Reference return value
The reference return value in the assignment operation makes the
execution of multiple assignments more efficient:
main() {
Myclass m1, m2, m3(35, "Princeton");
m1 = m2 = m3;
// calls m1.operator=(m2.operator=(m3));
}
How does this improve efficiency? It avoids a call to a copy constructor.
If the Myclass::operator=() function were to return a Myclass object
instead of a Myclass reference, the argument to the outer assignment
[the assignment to m1] would be a reference to a temporary Myclass
object.
Instead, it just passes the reference to m3 on to the outer assignment.
Page 4 - 18
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 18
Summary
Const and references are important things to add to your C++ bag of
tricks:
const is most often used in function arguments
references are almost always used only in function arguments and return
values
const is part of the interface definition of a function
you must check all function arguments that are pointers or references,
to determine whether they should be const
references play two roles
they specify arguments which can be modified by the function
efficiency they help avoid copying large structures
Page 4 - 19
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 19
Supplementary notes on const
Question: Which of the following uses of const are OK? Which of
them are probably not needed?
#include <stdio.h>
void print_int_value(const int val){ // const value argument
printf("%d\n", val);
}
void print_str_value(const char *ptr){ // const pointer argument
printf("%s\n", ptr);
}
main() {
const int one=1; print_int_value(one);
const char *one_str = "one"; print_str_value(one_str);
int two=2; print_int_value(two);
char *two_str = "two"; print_str_value(two_str);
}
When const appears in an argument list of a function, you need to think about how this will
be interpreted by the users of that function:
The first const (on the declaration of the argument of the print_int_value() function)
is completely unnecessary and somewhat confusing. Dont do this dont define a
const value argument.
The second const is meaningful it says that this function promises not to modify
the string that is passed in. This is a very good use of const a const pointer
argument.
The other two consts are OK they are local to a function, so it is up to the developer of that
function to decide how to use const.
Page 4 - 20
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 20
Supplementary notes on const
Question: What happens when you define two similar functions?
in this case, it depends on the compiler
g++ compile -time error or link-time error
Sun CC no error: always calls non-const version if both are defined
#include <stdio.h>
void print_int_value(const int val) {
printf("const int = %d\n", val);
}
void print_int_value(int val) {
printf("int = %d\n", val);
}
main() {
const int one=1;
print_int_value(one); // calls the second function!!
int two=1;
print_int_value(two); // calls the second function
}
Can we define two similar functions with different constness of the arguments? It depends.
There is a problem for const value arguments.
This is one of the reasons why many C++ coding standards say dont define functions with
const value arguments.
Page 4 - 21
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 21
Supplementary notes on const
Question: What happens when you define two similar functions?
for pointer arguments, the const is meaningful
#include <stdio.h>
void print_str_value(const char *ptr) {
printf("const str = %s\n", ptr);
}
void print_str_value(char *ptr) {
printf("str = %s\n", ptr);
}
main() {
const char *one_str="one";
print_str_value(one_str); // calls the first function
char *two_str="two";
print_str_value(two_str); // calls the second function
}
On the other hand, two functions with different constness may be defined for functions with
pointer arguments but even then, it is not necessarily needed or desirable. If the function is
really const (that is, if it doesnt modify the value of its arguments), then it should be defined
the first way with a const pointer argument.
Note: some compilers will give you a warning on the initialization char *two_str="two.
Why?
What is the type of a literal string? Is it char* or const char *?
Page 4 - 22
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 22
Supplementary notes on const
Question: Which of the following uses of const are OK? Which of
them are probably not needed?
#include <stdio.h>
main() {
const int array_size=100;
char buffer[array_size];
char *const buffer_start = &buffer[0];
strcpy(buffer_start, "C++ Fundamentals");
}
What is *const??
it is the declaration of a pointer that cant be moved the value of the
pointer is fixed
Both of these uses of const are OK.
The const int within a function is usually fine assuming that the developer of the
function knows that the value will not be changed after the initialization.
In this example, the const int is actually quite useful, because it is permitted to use
this constant in the declaration of the size of an array.
The *const on the buffer_start pointer is a little strange, but OK. It is a bit of local
documentation by the developer of the function declaring that this pointer will never
be moved during the execution of this function.
Page 4 - 23
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 23
Supplementary notes on const
Question: What happens when you define two similar functions?
in function arguments, the *const is not very usefuland it can be confusing
#include <stdio.h>
void print_str_value(const char *ptr) {
printf("const str = %s\n", ptr);
}
void print_str_value(const char *const ptr) {
printf("str = %s\n", ptr);
}
main() {
const char *one_str="one";
print_str_value(one_str); // calls the first function
const char *const two_str="two";
print_str_value(two_str); // ??? depends on the compiler
}
There is a problem for *const arguments. If you define two functions one with a *const
argument and one with an ordinary * argument, you will either get a compile-time or link-time
error (for example with the g++ compiler), or you will get one function that might never be
called.
Some C++ coding standards say dont define functions with *const pointer arguments.
Page 4 - 24
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 24
Supplementary notes on const
Question: What about const references?
#include <stdio.h>
void print_string_value(std::string str) {
printf("str = %s\n", str.c_str());
}
void print_string_value(const std::string str) {
printf("str = %s\n", str.c_str());
}
void print_string_value(std::string &str) {
printf("str = %s\n", str.c_str());
}
void print_string_value(const std::string &str) {
printf("str = %s\n", str.c_str());
}
The first two functions expect that the calling function will invoke the std::string copy
constructor to copy the value onto the stack. The last two functions are call by reference, so
the address of the object will be passed on the stack.
The first const is just like the const int example unnecessary and potentially confusing.
The second const is OK and useful it says that this function promises not to use the
reference to modify the object that was passed in.
Page 4 - 25
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 25
Supplementary notes on const
Question: What about const pointer return value?
#include <stdio.h>
#include <ctype.h>
const char * get_first_space(const char *ptr) {
while (*ptr && isspace(*ptr)) ptr++;
if (isspace(*ptr)) return (ptr);
else return (0);
}
main() {
const char *ptr1 = get_first_space("hello world");
const char *ptr2 = get_first_space("nospaces");
// Cant use get_first_space() to set a non-const char*
// char *ptr3 = get_first_space("hello world"); will be
// a compiler error
}
There is a significant difference between a const pointer return value and a non-const pointer
return value.
The const pointer return value is a read-only pointer if the main program tries to use the
pointer to modify some data, the compiler should give an error.
Remember, the idea of const pointers is to prevent the programmer from inadvertently
modifying characters in a read-only buffer. This means that the compiler error for
char *ptr3 = get_first_space("hello world");
is a good thing because you probably do not want to overwrite a constant string.
Page 4 - 26
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 26
Supplementary notes on const
Question: What about non-const pointer return value?
#include <stdio.h>
#include <ctype.h>
char * get_first_space(char *ptr) {
while (*ptr && isspace(*ptr)) ptr++;
if (isspace(*ptr)) return (ptr);
else return (0);
}
main() {
char buffer[100]; // a buffer that can be modified
strcpy(buffer, "hello world");
const char *ptr1 = get_first_space(buffer);
char *ptr2 = get_first_space(buffer);
*ptr2 = '\0'; // replace the space with a null character
}
Non-const pointer return this is a pointer that can be used for anything. The function that
returned the pointer doesnt care if the data that it points to is overwritten.
It is always OK to assign a non-const pointer to a const pointer so the assignment of ptr1 is
OK in this sample code.
The more common case is ptr2 the application program needs to modify something in the
buffer.
Can you have two overloaded get_first_space() functions? Yes.
The const version will be called whenever you pass in a const char* -- for example:
get_first_space("Bell Labs");
The non-const version will be called whenever you pass in a non-const char* -- for
example, when the argument is either a pointer or an array:
get_first_space(&mybuffer[0]);
Page 4 - 27
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 27
Supplementary notes on const
Question: What about const non-pointer return value?
in function return values, const is unnecessary (and poor style) for value return
#include <stdio.h>
#include <string.h>
const int get_length(const char *ptr) { // poor style
return (strlen(count));
}
int get_length_x(const char *ptr) { // good style
return (strlen(count));
}
main() { // all function calls compile OK
const int length1 = get_length("hello");
int length2 = get_length("goodbye");
const int length3 = get_length_x("hello");
int length4 = get_length_x("goodbye");
}
Const declarations on return types that are ordinary value types (not pointers or references)
are unnecessary. The constness isnt really used anywhere the value cant be changed
between the return within the function definition and the assignment in the main program
where the return value is copied.
C++ coding standards say dont define a non-pointer/non-reference return type to be a
const.
Page 4 - 28
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 4 - 28
Conclusion
It is OK to use const in many places within a function
When you use const for function arguments and function return
types limit the uses of const to const pointers and const references:
// good declarations
void print_int_value(int val);
void print_str_value(const char *ptr);
void print_string_value(std::string str);
void print_string_value(std::string &str);
void print_string_value(const std::string &str);
// bad declarations
void print_int_value(const int val);
void print_str_value(char *const ptr);
void print_str_value(const char *const ptr);
void print_string_value(const std::string str);
Note: The list of bad declarations are not illegal syntax these function declarations will be
accepted by the C++ compiler. It is just not good C++ programming style: const value
arguments and *const arguments could cause confusion to application developers who use
these functions. It is better to use the corresponding good declaration instead.
Page 5 - 1
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 5 - 1
Unit 5: Overloaded operators
C++ Fundamentals
Alcatel-Lucent Bell Labs
Software Productivity & Quality Tools Dept.
http://www.stc.lucent.com/~dmm/intro
Page 5 - 2
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 5 - 2
Overloaded operators
In C++, the definitions of standard built-in operators can be redefined
(actually extended) to work with class types
in some cases, it is useful to create new versions of some of the
arithmetic operators (addition, multiplication, and so on) for your class
it is often necessary to write an implementation of the assignment operator
for your class
if you dont define the assignment operator, an assignment operator
will automatically be defined for your class by the compiler
it is possible to go overboard defining too many overloaded operators
The assignment operator is the most important overloaded operator to know about. It is often
similar to the copy constructor in its implementation, but there are some important differences
between the assignment operator and the copy constructor.
Page 5 - 3
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 5 - 3
Example
/* file Complex.h */
class Complex {
private:
double re, im; // real part and imaginary part
public:
Complex() : re(0.0), im(0.0) {}
Complex(double r) : re(r), im(0.0) {}
Complex(double r, double i) : re(r), im(i) {}
Complex operator+(const Complex &) const;
Complex operator*(const Complex &) const;
};
inline Complex Complex::operator+(const Complex &b) const {
return (Complex(this->re + b.re, this->im + b.im));
}
inline Complex Complex::operator*(const Complex &b) const {
return (Complex(this->re*b.re - this->im*b.im,
this->re*b.im + this->im*b.re));
}
/* example using complex arithmetic ... */
Complex a(2.0, 1.0), b(3.0, -2.0), c;
c = a + b; /* c is now (5.0, -1.0) */
In this example, operator+() and operator*() are defined as member functions of class
Complex. If an application uses the + operator as shown above, this operator will be converted
into the member function call a.operator+(b).
Page 5 - 4
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 5 - 4
Another example
/* file Complex.h */
class Complex {
private:
double re, im;
public:
Complex() : re(0.0), im(0.0) {}
Complex(double r) : re(r), im(0.0) {}
Complex(double r, double i) : re(r), im(i) {}
friend Complex operator+(const Complex &, const Complex &);
friend Complex operator*(const Complex &, const Complex &);
};
inline Complex operator+(const Complex &a, const Complex &b) {
return (Complex(a.re + b.re, a.im + b.im));
}
inline Complex operator*(const Complex &a, const Complex &b) {
return (Complex(a.re*b.re - a.im*b.im,
a.re*b.im + a.im*b.re));
}
In this example, the operator+() and operator*() functions are not member functions. They are
defined as ordinary two-argument functions, so the first argument is the left-hand parameter
and the second argument is the right-hand parameter. The operator+() and operator*()
functions have been declared as friend functions in class Complex so that they can access
private data within the structure.
Most symmetric operators, like operator+() and operator*() are usually defined this way (as
ordinary or friend functions, rather than as member functions). There is one minor technical
advantage here: with this definition, the overloaded operator can be called with a left-hand
parameter that can be converted to class Complex.
Page 5 - 5
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 5 - 5
Assignment operator
The assignment operator for a class is usually defined this way:
Complex& Complex::operator=(const Complex &rhs) {
this->re = rhs.re;
this->im = rhs.im;
return (*this);
}
The automatically generated assignment operator will just do an
assignment of each data member in the class
This is not the right thing to do if the class points to some allocated
memory or other dynamically allocated resource.
Most classes that contain pointers should have a programmer defined
assignment operator (because the automatically generated assignment
operator will be wrong).
The operator=() function for a class must be defined as a member function, not as a friend
function.
It is a good idea to use a const reference argument for operator=() more efficient than a value
argument.
operator=() always returns a value a reference return is OK.
Page 5 - 6
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 5 - 6
More about the assignment operator
The automatically generated assignment operator for a struct in C++
will simply copy each data field:
struct timeval { /* from sys/time.h or winsock.h */
long tv_sec;
long tv_usec;
};
struct timeval t1, t2;
t2 = t1; /* this will copy t1.tv_sec and t1.tv_usec */
Some C++ structs might contain attributes that are class types, so the
automatically generated assignment operator for the struct will call the
assignment operator for each data field:
struct Customer {
std::string name;
std::string address;
};
struct Customer c1, c2;
c2 = c1; /* invokes the string operator=() function twice */
Both of these examples show situations where the automatically generated assignment
operator is OK.
In the first example, since double is a built-in type, the automatically generated assignment
operator will just do a simple copy of the values from one structure to another.
In the second example, the situation is slightly more complicated. The string class (in the C++
standard library) has an operator=() function defined already, so the automatically generated
assignment operator for the Customer struct will invoke that string assignment operator for
each of the two fields.
Page 5 - 7
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 5 - 7
User-defined assignment operator
example
If the class does some memory allocation, the assignment operator ought to be
defined by the class author:
class Customer {
public:
Customer(string nm, string addr);
~Customer();
Customer(const Customer &c); // copy constructor
Customer& operator=(const Customer &c);
void add_item(string);
private:
std::string name;
std::string address;
std::string *item_list; // array of items ordered by the customer
int item_count; // the number of items stored in the list
};
Dennis Mancl
600 Mountain Ave...
name:
address:
item_list:
item_count:
0
This example shows a complex class which contains some dynamically-allocated data. This is
a situation where the class developer needs to take some care
Page 5 - 8
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 5 - 8
User-defined assignment operator
The following standard boilerplate functions should be defined for the
Customer class normal constructor and destructor:
Customer::Customer(string nm, string addr) : name(nm), address(addr) {
this->item_list = new std::string[10];
this->item_count = 0;
}
Customer::~Customer() { delete [] this->item_list; }
The assignment operator needs to be implemented correctly:
Dennis Mancl
600 Mountain Ave...
name:
address:
item_list:
item_count:
2
Patricia Russo
54 rue la Botie...
name:
address:
item_list:
item_count:
3
100 index cards
24 pencils
16 annual reports
16 silver pens
32 croissants
Customer current_customer =
Customer previous_customer =
dont want these
objects to become
memory leaks
This example shows a situation where the automatically generated assignment operator is not
OK. If we forget to define an assignment operator for this class, the automatically generated
assignment operator will just copy each of the data fields.
Suppose that we have two Customer objects: previous_customer and current_customer. If the
previous_customer = current_customer expression is executed, we will have two Customer
objects that point to the same list, which will work OK for a while. Sooner or later, one of the
two Customer objects will go out of scope, so its destructor will be invoked. That destructor
call will return the dynamically allocated string array back to the free list. This will cause a
serious problem if we try to use the other copy of this Customer object. It will also cause a
serious problem when the destructor is invoked on the second Customer object.
Page 5 - 9
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 5 - 9
Continuation of the example
Here is an example of assignment operator and copy constructor definitions:
Customer& Customer::operator=(const Customer &c) {
if (&c != this) { // check for assignment to self
this->name = c.name;
this->address = c.address;
if (this->item_count < c.item_count) { // make array bigger
delete [] this->item_list;
this->item_list = new std::string[c.item_count+5];
}
for (j = 0; j < c.item_count; j++) // copy array values
this->item_list[j] = c.item_list[j];
this->item_count = c.item_count;
}
return *this;
}
Customer::Customer(const Customer &c) :
name(c.name), address(c.address), item_count(c.item_count) {
this->item_list = new std::string[c.item_count+5];
for (j = 0; j < c.item_count; j++) // copy array values
this->item_list[j] = c.item_list[j];
}
With this version of the assignment operator, the Customer class is implemented correctly.
Each Customer object has its own copy of the string array (containing the list of ordered
items), so nothing bad will happen when a destructor is invoked.
The copy constructor must do some of the same operations as the assignment operator. The
major differences between the assignment operator and the copy constructor are:
at the beginning of the assignment operator, the this pointer already points to an
initialized structure that contains the old value, but in the copy constructor, the
structure is being newly initialized
the assignment operator can take a short cut: it can check for self-assignment
the assignment operator must have a return statement at the end, but a copy
constructor doesnt return a value
Page 5 - 10
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 5 - 10
C++ input/output
In C++, it is possible to use C-style input and output functions:
printf("(%lf,%lf)", c.get_real_part(), c.get_imaginary_part());
But it is often better to use the C++ streams I/O
std::cout << c; // use an overloaded operator to print the value
You can define the input extraction and output insertion functions for new
C++ class types by creating overloaded operator>>() and operator<<()
functions:
std::istream& operator>>(std::istream &is, Complex &c) {
.... extract data from the stream, store in "c" ....
return is;
}
std::ostream& operator<<(std::ostream &os, const Complex &c) {
os << "(" << c.re << "," << c.im << ")";
return os;
}
The operator>>() and operator<<() are often defined for new C++ classes. They are not
defined as member functions, but they are usually friend functions.
A simple non-error-checking version of the extractor function for Complex might look like
this:
std::istream& operator>>(std::istream &is, Complex &c) {
is.get(); // skip the left parenthesis
is >> c.re;
is.get(); // skip the comma
is >> c.im;
is.get(); // skip the right parenthesis
return is;
}
A more complex version of the istream extractor for Complex would have code to skip
whitespace and some more thorough error checking.
For more information on iostreams, see C++ IOStreams Handbook by Steve Teale (Addison-
Wesley, 1993) or Standard C++ IOStreams and Locales by Angelika Langer and Klaus Kreft
(Addison-Wesley, 2000).
Page 5 - 11
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 5 - 11
Summary
Overloaded operators are simple to define
they can be defined as member functions or as regular functions
Class authors will often provide a definition for the assignment
operator
if they dont, the C++ compiler will automatically generate a definition of
the assignment operator
Class authors can write input and output operators for their classes
Page 6 - 1
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 1
Unit 6: Introduction to
C++ Standard Libraries
C++ Fundamentals
Alcatel-Lucent Bell Labs
Software Productivity & Quality Tools Dept.
http://www.stc.lucent.com/~dmm/intro
Page 6 - 2
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 2
C++ libraries
C++ libraries are useful, because they can allow you to do many
software tasks without having to build all the basic software yourself.
The C++ Standard Libraries include many useful standard classes,
including:
string
vector
map (associative array)
list (linked list)
The string class and the container classes (vector, map, and list) are often used to help
implement the internal details of domain-specific classes that will be part of a larger system.
These classes contain efficient C++ implementations of important data structures.
Page 6 - 3
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 3
Character strings
A common error in C++ programs is incorrect memory allocation
char *machine = "www";
char *domain = "stc.lucent.com";
char *directory = "C++";
char *cpp_url = malloc(strlen(machine) + strlen(domain) +
strlen(directory) + 7 + 2 + 1; /* 1 for the terminating null char */
strcpy(cpp_url, "http://");
strcat(cpp_url, machine);
strcat(cpp_url, ".");
strcat(cpp_url, domain);
strcat(cpp_url, "/");
strcat(cpp_url, directory);
This program fragment has to be written carefully, to make sure the right number of
bytes of memory are allocated
The application programmer also must remember to free the memory
This example is based on a 1988 C++ presentation by Andrew Koenig. It is still true: the
average C-based library still requires a lot of knowledge of the underlying representation
details by the library user. Many C applications use fixed sized arrays and avoid dynamic
allocation to reduce the amount of work that they must do in setup and cleanup.
Page 6 - 4
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 4
String
The same code can be written using the string class
std::string machine = "www";
std::string domain = "stc.lucent.com";
std::string directory = "C++";
std::string cpp_url;
cpp_url = "http://" + machine + "." + domain + "/" + directory;
The size of a string object is dynamic it can grow as new characters are added to
the end.
The string::operator+() function is an overloaded operator function which performs
string concatenation. You must have at least one string object in an expression for
this operator to be used:
std::string alt_url;
alt_url = std::string("http://") + "www.lucent.com";
With a well-designed C++ class library, the application becomes much simpler. There is still
some dynamic memory allocation going on behind the scenes, but the person who developed
the library has done most of the hard work. The C++ compiler makes sure that the destructor
calls are put in at the right point to clean up objects that are no longer needed.
Page 6 - 5
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 5
String class operations
The string class has a very large public interface 103 public member
functions in the current standard
by the way this is not a good model to follow in designing your own classes
but the C++ standard library was designed by a committee, so they put in
everything they could think of
A string can be assumed to be a dynamic array of characters. The value of a
string can be changed with the following functions:
assignment operator
std::string a = "abc";
a = "def"; // assignment operator
a.assign("xyz"); // assignment function
append() and operator+=() add characters to the end of a string
std::string a = "abc";
a.append("def"); // now a == "abcdef"
a += "g"; // now a == "abcdefg"
The string class in the C++ standard libraries has a large number of functions for creating and
manipulating strings. This unit will only show a subset of the string class functions.
Some operations can be performed in multiple ways: assign() and operator=(); append() and
operator+=() for example.
Page 6 - 6
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 6
String class operations
Inserting and removing will change the length of the internal array
insert() adds new characters in the middle
std::string a = "abcdef";
a.insert(0,'A'); // now a == "Aabcdef"
a.insert(2,"BCD"); // now a = "AaBCDbcdef"
remove() removes characters from the middle
std::string a = "abcdefghijkl";
a.remove(0,3); // now a == "defghijkl"
a.remove(3,3); // now a = "defjkl"
Notice that the insert() and remove() functions work with integer indexes into the array:
The first argument to insert() is an integer index into the array the position where the new
characters will be inserted. a.insert(j,x) means insert x before the jth position in the string.
The remove() function takes a position and a lengtha.remove(j,n) means remove n
characters starting at the jth character in the string.
Page 6 - 7
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 7
More string operations
Getting a character, modifying a character
the operator[]() function can be used to access and modify
individual characters
std::string a = "abc123";
char c = a[1]; // c == 'b'
std::string b = "abc";
b[1] = 'B'; // now b == "aBc"
The second expression works because operator[]() returns a char
reference:
std::string b = "abc";
b[1] = 'B';
// write into string b through the reference
a b c
b
b[1]
The operator[]() function allows the programmer to treat a string just like a character array,
both for looking at individual characters and for making modifications within a string.
Page 6 - 8
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 8
More string operations
The string class has some useful functions for accessing and modifying
parts of a string
substr() functions can be used to access substrings
std::string a = "abc123";
std::string b1 = a.substr(2,2); // b1 = "c1"
std::string b2 = a.substr(2); // b2 = "c123"
replace() can be used to modify a substring
std::string a = "abc";
a.replace(1,0,"123"); // now a == "a123bc"
a.replace(1,3,"45"); // now a = "a45bc"
Substring operations substr() and replace() use integer indexes and substring lengths, just like
remove().
Page 6 - 9
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 9
More useful string functions
There are several useful accessor functions in the string class:
std::string s = "Abc123";
int i = s.length(); // now i == 6;
// find first occurrence of a character
int pos = s.find('1'); // pos == 3
pos = s.find('z'); // pos == -1 (not found)
// find first occurrence of a string
int pos2 = s.find("123"); // pos2 == 3
pos2 = s.find("1234"); // pos2 == -1
std::string url = "www.stc.lucent.com/C++";
std::string machine = url.substr(0, url.find('.'));
std::string directory = url.substr(url.rfind('/')+1);
The find() function can be used to find individual characters or substrings within a string. The
find() function returns an integer index: the position where the character or substring was
found in the target string. The value 1 is returned to indicate a failed search.
Page 6 - 10
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 10
Printing a string
The iostreams package can print strings just like any other datatype:
std::string a = "abc123";
std::cout << "string a = " << a << "\n";
When using the printf() function, you have to use the c_str() member function
of the string class, which returns a const char *:
std::string a = "abc123";
printf("string a = %s\n", a.c_str());
// a better way to print the string
// (if it might contain embedded nulls)
fwrite(a.c_str(), 1, a.length(), stdout);
The string class has both input and output operators that work with the C++ iostreams package.
It is still possible to use the old C-based stdio functions like printf(), by using the special
c_str() accessor function that returns a const char*.
Page 6 - 11
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 11
String comparisons
There is a compare() function for strings that works like the C function
strcmp() function, but it is often clearer to use the overloaded comparison
operators:
void docommand(const string &command) {
if (command == "run") {
run_command();
}
else if (command < "m") {
lookup_command_A_to_L(command);
}
else {
lookup_command_M_to_Z(command);
}
}
main() {
docommand("run");
docommand("open");
docommand("delete");
}
The comparison operators are very simple and easy to read. It is much easier to understand
code written with the string class comparison functions than code written with the C strcmp()
function.
Page 6 - 12
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 12
Strings as return values
String is better for return values than char*:
#include <stdio.h>
std::string create_tempname() {
std::string retval = std::string(tmpnam(NULL)) + ".c";
return (retval);
}
main() {
// main program needs 3 temp files
std::string tempfile1 = create_tempname();
std::string tempfile2 = create_tempname();
std::string tempfile3 = create_tempname();
....
}
It is more convenient to return a string than a char*, because the
create_tempname() function doesnt have to call malloc()
String objects are very useful for specifying return values from many functions. The problem
with returning a char* is that it is much more error prone:
if the returned pointer points to a local data array that was defined in the function,
that memory is allocated from the stack, and it might be overwritten by another
function call before the calling function actually uses it
if the returned pointer points to a static data array, several successive function calls
will probably overwrite important information in that static area
if the returned pointer points to a dynamically allocated array, it will be the
responsibility of the caller to free the memory when it is no longer needed
The string class is much more automatic, because string objects on the stack will have their
destructors called when they are no longer in scope.
Page 6 - 13
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 13
Using strings in other classes
Always consider using a string instead of a char* in a struct or a class
Strings always have a default value (the empty string)
The string class has a copy constructor and an assignment operator, so
you never inadvertently overwrite or delete characters that someone
else is using
The string class has a default constructor that sets the string to a null value. Character arrays
are not as predictable: if you work with an array that is allocated on the stack, initialization is
the programmers responsibility.
The copy constructor and assignment operator make the string class very convenient.
Page 6 - 14
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 14
The vector class
The C++ Standard Libraries include a parameterized class for
building arrays that can grow:
#include <vector>
#include <string>
#include <iostream>
main() {
std::vector<int> a(5); // initial size of the
// array is 5, values are 0
std::vector<std::string> names(5, std::string("$"));
// "$" is the initial value
a[0] = 1; a[1] = 2; a[2] = 3;
a.insert(&a[1], 999); // push some of the values over
names.insert(names.end(), 5, std::string("%"));
// insert 5 new values at the end
}
// at the end, a = [1,999,2,3,0,0]
// and names = [$,$,$,$,$,%,%,%,%,%]
A vector is a flexible array. The memory for the array is allocated from the heap. The array
can be resized at any time using the resize() or reserve() functions. The insert() function will
also increase the size of the vectors array.
The vector class has an overloaded operator[] function, which allows the programmer to access
and/or modify elements within the vector.
The begin() and end() functions return iterators (objects that act like pointers) to the first
element of the vector and one-past-the-end of the vector. These iterators are used to specify
positions within the array for insertion, deletion, and searches.
Page 6 - 15
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 15
More about the vector class
The individual items in a vector are guaranteed to be stored in adjacent
locations in memory
Any calls to the insert(), resize(), or reserve() function may cause the
vector class to allocate new memory and copy the old values to the
new space
this will invalidate any pointers into the old memory space
std::vector<int> b(100);
int *ptr1, *ptr2;
ptr1 = &b[0];
ptr2 = &b[50];
b.reserve(200);
// ptr1 and ptr2 are now probably invalid -- they probably
// point into the middle of a memory region that has been
// returned to the free list
It is more normal to work with offsets within the array instead of pointers. If you get a
pointer into a vector, you shouldnt use that pointer after an insert(), resize(), or reserve() call.
Page 6 - 16
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 16
Associative arrays
The map class is useful for creating simple tables:
#include <map>
#include <string>
#include <iostream>
main() {
using std::string;
std::map<string,string> m;
// initialize the map
m["Adams"] = "Abigail";
m["Madison"] = "Dolly";
m["Roosevelt"] = "Eleanor";
m["Reagan"] = "Nancy";
m["Bush"] = "Barbara";
m["Clinton"] = "Hillary";
m["Bush2"] = "Laura";
// now do a series of
// lookup operations
while (1) {
std::string key;
std::cout << "Enter name:\n";
std::cin >> key;
if (!std::cin) break;
// quit on EOF
std::cout << m[key] << "\n";
}
}
Enter name:
Reagan
Nancy
This example program illustrates one possible use of the map class: creating an associative
table with a string key value. There are many other possible uses.
Page 6 - 17
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 17
The map data structure
Each map object contains a binary tree:
Madison
Dolly
Adams
Abigail
Clinton
Hillary
Bush2
Laura
Roosevelt
Eleanor
Reagan
Nancy
Bush
Barbara
The internal data structure used by the map class is a red-black tree: this is a special kind of
binary tree that reorganizes itself whenever it gets out of balance. This means that the
search time to find a node in the tree is guaranteed to be fast -- O(log n).
Page 6 - 18
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 18
More about the map class
Each of the nodes in the tree contains two data fields
the first data field (the key) is used to decide where in the tree the node
will be placed: you can quickly find a particular key in the tree
the second data field (the value) is just attached to the node, and it is
stored, modified, and/or returned by several map functions
You can use one of the following expressions to look for a node:
m["Reagan"] // returns a reference to the string "Nancy"
m.operator[]("Reagan")
m.find("Reagan") // returns a pointer to the node that contains
// "Reagan" and "Nancy"
If the operator[] function doesnt find the key in the tree, it will
automatically create a new node with a null string in the value part
of the node
The expression m["Reagan"] can be used on either side of an assignment statement: on the
right side it will return the value part of the node, but on the left side it will allow the value
part of the node to be modified.
string name1 = m["Clinton"]; // name1 now is set to Hillary
m["Clinton"] = newvalue; // the value field in the Clinton node
// now has a new value
The find() function is good for looking for a value that might or might not be there:
map<string,string>::iterator mi;
mi = m.find("Smith");
if (mi == m.end()) cout << "entry was not found\n";
We shouldnt use the operator[] function to do this, because it will add a new node with a null
value if it doesnt find the key.
The function m.end() returns a special value that is one past the end of the list. This is the
value that is returned when a call to the m.find() function fails, or when the operator++
function iterates past the end of the table.
Page 6 - 19
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 19
Map iterators
If you define a map iterator, it treats the map as if it were a linear list
(by walking through the tree in alphabetical order):
std::map<string,string>::iterator m_iter;
// start at the beginning
m_iter = m.begin();
while (m_iter != m.end()) {
std::cout << (*m_iter).first << " -- ";
std::cout << (*m_iter).second << "\n"
m_iter++;
}
m_iter
Adams
Bush
Bush2
Clinton
Madison
Reagan
Roosevelt
Abigail
Barbara
Laura
Hillary
Dolly
Nancy
Eleanor
In a map, an iterator always points to a small structure that contains two fields. The first field
is the key value of the map. The second field is the associated data value stored in the map.
Page 6 - 20
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 20
Finding an item in a map
You can find a particular key in a map using the find operation, which
returns an iterator
The ++ and -- operators can be used to move the iterator forward and
backward in the map
std::map<string,string>::iterator m_iter;
// start at "Nancy Reagan"
m_iter = m.find("Reagan");
std::cout << (*m_iter).second << "\n";
// advance to "Eleanor Roosevelt"
m_iter++;
std::cout << (*m_iter).first << " -- ";
std::cout << (*m_iter).second << "\n";
// remove items from the map
m.erase("Adams");
m.erase(m_iter); // remove "Eleanor Roosevelt"
Iterators act like pointers into the map structure. They can be moved forward and backward to
iterate through the entire data structure.
Page 6 - 21
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 21
Multi-level maps
typedef std::map<std::string,std::string> MapSS;
std::map<std::string,MapSS> pres_info;
pres_info["Clinton"]["spouse"] = "Hillary";
pres_info["Clinton"]["VP"] = "Al Gore";
pres_info["Clinton"]["term"] = "1993-2001";
pres_info["Bush2"]["spouse"] = "Laura";
pres_info["Bush2"]["VP"] = "Dick Chaney";
pres_info["Bush2"]["term"] = "2001-2009";
std::map<string,string>::iterator info_iter = pres_info["Bush2"].begin();
for ( ; info_iter != pres_info["Bush2"].end(); info_iter++)
std::cout << (*info_iter).second << "\n";
VP
spouse
term
Al Core
Hillary
1993-2001
VP
spouse
term
Dick Chaney
Laura
2001-2009
Clinton
Bush2
This example illustrates that maps can represent quite complex data structures. The idea of a
multi-level map is very useful in many kinds of applications (such as user interfaces and
database interfaces)
Page 6 - 22
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 22
Linked lists
The list class is yet another container class: this time, the data
structure is a linked list
#include <list>
#include <string>
#include <iostream>
main() {
std::list<int> a(5);
std::list<std::string> names(5, string("$"));
std::list<int>::iterator l_iter;
a.insert(a.end(), 1);
l_iter = a.insert(a.end(), 2);
a.insert(a.end(), 3);
a.insert(l_iter, 999);
names.insert(names.end(), 5, string("%"));
std::list<int>::iterator p1;
std::list<std::string>::iterator p2;
for (p1 = a.begin(); p1 != a.end(); p1++)
std::cout << *p1 << ",";
for (p2 = names.begin(); p2 != names.end(), p2++)
std::cout << *p2 << ",";
std::cout << "\n";
}
The output of this program will be:
0,0,0,0,0,1,999,2,3,$,$,$,$,$,%,%,%,%,%,
Page 6 - 23
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 23
List operations
assignment operator
push_front() and push_back() add new entries to the start or end of the list
std::list<int> a;
a.push_back(2);
a.push_back(3);
a.push_front(1); // a is now [1,2,3]
insert() adds a new entry at a position specified by an iterator
a.insert(a.end(), 4); // a is now [1,2,3,4]
splice() joins two lists
std::list<int> b;
b.push_back(2); b.push_back(3);
a.splice(a.end(), b); // a is now [1,2,3,4,2,3] and b is empty
remove() takes items out of a list that match a specific value, erase() takes out
a single item or a range of items
a.remove(2); // a is now [1,3,4,3]
a.erase(find(a.begin(), a.end(), 3)); // a is now [1,4,3]
a.erase(find(a.begin(), a.end(), 4), a.end()); // a is now [1]
The push_front() and push_back() operation for the list class are very efficient, because the list
is stored as a doubly-linked list. No searching or memory reallocation is needed.
The insert() operation inserts the new node at the position just before the item pointed to by the
first argument. So a.insert(a.begin(),5) will insert a new node before the first node
and a.insert(std::find(a.begin(),a.end(),2),6) will insert a new node just
before the first occurrence of the number 2 in the list.
The find() function is part of the generic functions in the C++ standard libraries. It is a linear
search function that works on arrays, vectors, and lists. The first two arguments are iterators
that specify the bounds of the search and the third argument gives the value to be searched for.
The find() function returns an iterator that refers to the cell where the value was found. It
returns the end iterator if it fails to find the value.
Programs that use the find() function and other generic functions should have an #include
<algorithm> at the beginning.
Page 6 - 24
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 24
More list operations
reverse() flips the order of everything in the list
std::list<int> a;
a.push_back(2);
a.push_back(3);
a.push_back(1); // a is now [2,3,1]
a.reverse(); // a is now [1,3,2]
sort() reorders the list in ascending order
a.sort(); // a is now [1,2,3]
size() reports the number of items in the list
int i = a.size(); // i is now 3
Page 6 - 25
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 25
Iterators
Each container class in the C++ standard libraries has an iterator type
the iterator acts like a pointer: it refers to an element or node within the
container
access the data in the element or node by using the * operator
move the iterator forward and backward in the collection by using the ++
and -- operators
Example iterators:
std::vector<int>::iterator
std::map<std::string,std::string>::iterator
std::list<std::string>::iterator
Iterators are returned by the functions that find things in the collection
Iterators are used as an argument in insertion functions (for vector and
list) and in the erase functions (for vector, list, and map)
An iterator is not intended to be a safe pointer into a container.
Each find() operation may need to compare the return value to obj.end() to see if the
find() succeeded.
The * operator will only return a meaningful value when it actually points to a real
element in the collection
You shouldnt use a less-than comparison to see if the iterator has reached the end:
you should use an equals or not-equals comparison
for (iter = obj.begin(); iter != obj.end(); obj++) ...
Page 6 - 26
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 6 - 26
Summary
The C++ standard libraries contain a number of useful classes
these classes should be the foundation of many C++ applications
strings are used instead of character arrays because it helps sidestep
problems with initialization and memory allocation
vector and list are used to make dynamic data structures that can grow to
any size
map is used to make efficient lookup tables
Page 7 - 1
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 1
Unit 7: Effective C++ class
development
C++ Fundamentals
Alcatel-Lucent Bell Labs
Software Productivity & Quality Tools Dept.
http://www.stc.lucent.com/~dmm/intro
Page 7 - 2
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 2
C++ class developers
A C++ class developer is a different kind of software developer
their job is to design packages that will be used by other software
developers
it is more important to write classes that are clear and understandable
classes that are clever and cryptic are not useful
This unit will present some of the things that good C++ class
developers do as a matter of course
reusing and building on the work of others
writing software packages that are easy to understand and test
creating a solid foundation of software packages for others to build on
C++ class developers work hard to create software packages that will ease the work of other
developers that come afterwards.
Page 7 - 3
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 3
Seven habits of effective
C++ class developers
using existing C++ library classes
existing classes are good component parts
creating class documentation
documenting functionality, side-effects, required environment, and performance
reviewing class designs and implementations
peer reviews help find problems and help transfer technology
building test code
built-in tests can greatly improve reusability
using existing frameworks
frameworks are miniature architectures
using standard design patterns
design patterns are design ideas that have been successful in the past
creating wrapper classes to encapsulate legacy code
create bridges to existing non-OO software
Good C++ class developers will do all seven of these things:
using existing C++ libraries helps to save effort, plus it can make application code
easier for others to understand
class documentation is critical for reuse
the review process helps everyone learn more
test code increases the confidence of other users in the quality of a class
frameworks, design patterns, and wrapper classes help to shape a coherent system
design
Page 7 - 4
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 4
Building on existing
C++ class libraries
Dont reinvent the wheel: dont create your own String class
Use the classes in the official C++ Standard Library
string
iostreams
Standard Template Library
It is a useful exercise when you are learning C++ for the first time to create some simple
classes that implement things like character strings. But in real-world code, it is better to build
on top of standard classes.
Page 7 - 5
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 5
Use strings instead of
character arrays
If you want maximum flexibility in your domain-level classes, you
should use the string class instead of character arrays whenever you
need character data within a class:
/* file Myclass.h */
#ifndef _MYCLASS_H
#define _MYCLASS_H
#include <string>
class Myclass {
public:
Myclass(const char *user, const char *addr);
void addtomessage(const char *text);
void sendmessage();
private:
std::string username; // not char username[50];
std::string address; // not char address[100];
std::string message; // not char message[1024];
};
#endif
The string class has a few advantages over using a character array or a char* that points to
some dynamically-allocated memory:
the string class has constructors and destructors already defined, so the memory is
always initialized to the null string and the memory is always correctly freed when
it is no longer needed
the string class allows the strings length to grow to the length that is needed (so it is
much better than a fixed-sized array)
the string class has a rich set of operations for manipulating character strings
efficiently
Page 7 - 6
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 6
Use iostreams
The iostreams classes are a set of I/O classes
they permit both built-in types and user-defined classes to be input and
output
input and output can be done from files or from memory buffers
/* file formatbuf.c */
#include <string>
#include <sstream>
std::string formatbuf(const char *v1, const char *v2) {
std::ostringstream os; // or: std::ostrstream os;
os << "value1=" << v1 << "\t";
os << "value2=" << v2 << "\n";
std::string retval = os.str();
return (retval);
}
Older C++ implementations usually include the istrstream and ostrstream classes, which
permit input from and output to a character array. (Applications need to include the
strstream.h header file.)
The ANSI C++ standard contains the istringstream and ostringstream classes, which read
characters from and write characters to a string object. (Applications need to include the
sstream header file.)
Page 7 - 7
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 7
Use algorithms and container classes
from C++ Standard Libraries
The ANSI/ISO C++ standards committee adopted the Standard
Template Library (STL) as part of the C++ Standard Libraries.
The STL is a set of standard C++ container classes that can be used to
manipulate arrays, lists, sets, and other collections of objects
the STL libraries use the templates feature of C++
some of the containers are sequence containers: vector and list
some of the containers are associative containers: map and multimap
each container type also has an iterator type
STL also contains generic functions that operate both on arrays and STL
containers
The original reference implementation of the Standard Template Library can be downloaded
from an HP anonymous FTP site: butler.hpl.hp.com. The library source code can be found in
the /stl directory. A more up-to-date implementation of STL (closer to the ANSI C++
standard) has been done by Silicon Graphics: it can be downloaded from their Web site:
http://www.sgi.com/Technology/STL. The STL implementation that is included in the
libraries for the GNU G++ compiler is based on the Silicon Graphics STL code. Another
relatively portable (and free) implementation of STL can be found at http://www.stlport.org.
A good reference book on STL is STL Tutorial and Reference Guide by David R. Musser and
Atul Saini, Addison-Wesley, 1996. Another valuable reference book is Generic Programming
and the STL by Matthew H. Austern, Addison-Wesley, 1999.
Commercial versions of the STL are available from several C++ library companies
(RogueWave, Modena, ObjectSpace, Dinkumware, ).
Page 7 - 8
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 8
STL container classes
STL supports the following container classes:
vector (variable-sized array implemented as a contiguous block of
memory: it is very easy to add a new value on the end, but slow to add
to the beginning or middle)
list (linked list: easy to add new items anywhere, but very slow to get
to the nth element)
deque (similar to vector, but it is easy to add onto both ends)
set (items are not kept in a specific order, multiple inserts of a single
value are ignored)
multiset (a value can be inserted multiple times)
map (associative array)
multimap
This set of classes covers most of the standard data structures used in programming. The
detailed documentation of these classes gives more information about the performance of
some of the standard operations on these collections (such as adding new items, deleting
existing items, finding a specific item, stepping through the collection, and accessing a random
item in the collection).
Page 7 - 9
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 9
STL generic functions
The STL generic functions are template functions: they can operate
on arrays or collections of any type
the C++ compiler will expand the code for the function at compile time
Here are a few examples of generic functions:
int myarray[] = { 2, 3, 5, 7, 17, 13, 11 };
std::fill(&myarray[0], &myarray[7], 999);
this function fills the array with a single value
k = std::count(&myarray[0], &myarray[7], 5);
this function counts the number of items in the array that match the value
std::sort(&myarray[0], &myarray[7]);
this function reorders the elements in ascending order: it uses the operator<
function for class objects
Notice that the three example functions above are called with some pointer arguments that
specify a range of cells within the array. The first argument indicates the first element of the
array to be searched or modified, and the second argument points to one past the end of the
array.
There are about 70 generic functions in STL.
Page 7 - 10
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 10
What makes this work?
Example: the parameterized fill() function
the first two arguments to any call to fill() must be of the same type
the * operation on the first argument must evaluate to something that
you can assign the third argument to
there must be a legal equality comparison operation for the iterator type
// a relatively standard implementation of
// the generic fill() function
template <class ForwardIterator, class T>
void fill(ForwardIterator first,
ForwardIterator last,
const T& value) {
for ( ; first != last; ++first)
*first = value;
}
In this code example, the first and second arguments to the fill() can either be
pointers, or
any class that supports the * and ++ operations, and a reasonable equality test.
It is also necessary to be able to assign a value to the result of the * operation, so a const
pointer or a const_iterator wont work.
In STL lingo, any C++ type that satisfies the conditions in the second bullet item is called a
forward iterator. Many of the search and process operations in the STL generic functions
require that the arguments need to be some kind of iterator.
The STL list class is implemented as a doubly-linked list, so its iterator type is a real class (not
just a pointer) which has simple implementations for the ++ and -- operators (which march
through the list) and the * operator (which returns a reference to the data within the current
node).
Page 7 - 11
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 11
Advantages and disadvantages
of generic functions
Advantages:
eliminates the need for many for loops
improves the quality and efficiency of application programs by employing
well-debugged and fast algorithms for searching and sorting
easy to make changes to internal data structures without changing the
algorithms (you can quickly change from an array to a vector to a list)
Disadvanages:
generic functions hide some of the complexity that you may need to
understand
you can get some cryptic error messages from the compiler when you get
some of the arguments mixed up
the generic functions dont do any bounds checking
Page 7 - 12
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 12
How safe are the generic functions?
The generic functions do not perform precondition tests on their
arguments, so it is possible to do dumb things:
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
int main() {
std::vector<std::string> longvec(100);
std::fill(longvec.begin(), longvec.begin()+50, "pi"); // OK
std::fill(longvec.end(), longvec.begin()+50, "e"); // error
}
// This code will compile without errors, but it isnt right.
// Note that the second call to fill() is an infinite loop.
// We need to make sure that arg1 <= arg2.
There is always a tradeoff between safety and efficiency, and the generic functions in the C++
standard library are mostly concerned with efficiency. They assume that the user has passed in
reasonable arguments to the function.
The debug mode of the STLport version of STL (http://www.stlport.org) attempts to be a
safe version of the STL container classes and generic functions: the code contains extra
checks for the integrity of iterators and it checks some preconditions on each operation.
Page 7 - 13
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 13
Reuse basics
Use standard classes (which will be supported on all standard
compilers) to implement the internals of your classes
You can build new utility classes from standard library classes using
inheritance, but only if the is-a relationship holds
More often, you will use standard classes as the types of data members
within your new classes
The notion of inheriting from standard classes will come up again later, in the section on
Frameworks.
Page 7 - 14
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 14
Class documentation
Class authors spend a lot of effort to make clear, concise, and usable
documentation for their classes
a complete manual page that describes every function in the public
interface of the class
a tutorial that illustrates how the class is used in practical applications
You need two kinds of documents because there are at least two kinds of users. Some need
just the basics (with lots of examples), so they can read the tutorial document to get started.
Others are familiar with the basics, but they want complete information on the public interface,
so they use the manual page.
Page 7 - 15
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 15
Documentation: the manual page
When a class user is writing code that uses a library class, the user
needs several pieces of information:
what header files must be included?
what are the legal constructors for this class? (including the default
constructor and copy constructor, if defined)
what are the operations in the public interface?
accessor functions (dont change the state of the object)
modifier functions (change the inside of the object)
what is the performance of each operation?
The manual page needs to provide this information in a simple format
A good set of class documentation should allow the class users to get the information they
need about the class without needing to look at the source code for the class.
Page 7 - 16
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 16
One manual page format
One manual page format mimics the structure of a UNIX manual page:
name: the name of the class with a one-line description
synopsis: a summary of which header files are needed and which
constructors and public member functions are defined
description: more detail about each constructor and member function
(including performance information)
complexity: overall information about the performance of the class
bugs: unexpected behavior for the user to watch out for
see also: cross reference to other classes and functions
It is important to keep the structure of the manual page simple, because this kind of document
will be referred to often by many class users, and they need to be able to find information
quickly in the document.
Page 7 - 17
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 17
Example manual page
NAME
Stopwatch program execution time measurement
SYNOPSIS of Stopwatch.h
class Stopwatch {
public:
Stopwatch();
~Stopwatch();
Stopwatch(const Stopwatch &s);
Stopwatch& operator=(const Stopwatch &s);
int status() const;
void start();
void stop();
void reset();
double system() const;
double user() const;
double real() const;
};
DESCRIPTION
A Stopwatch is used to time the execution of
software running on a UNIX-based system. The
Stopwatch object can report the system, user, and
real time that has elapsed since the Stopwatch was
started.
Constructors and destructors
Stopwatch()
Creates a stopped Stopwatch (status() == 0)
for which system(), user(), and real() all read 0.
~Stopwatch()
Destructor.
Copy and assign
Stopwatch(const Stopwatch &s)
Stopwatch& operator=(const Stopwatch &s)
Copying or assigning a Stopwatch will copy
all of the internal values in the Stopwatch.
This manual page is in declaration syntax: it shows how each public member function is
declared in the header file. This format shows a lot of detailed information in a compact form.
This Stopwatch class has a relatively simple interface. The manual page is very precise about
what constructors and member functions are part of the public interface. From the synopsis
section, the class user learns the following things:
the header file Stopwatch.h contains the definition of the Stopwatch class
the Stopwatch class has a default constructor (a constructor with zero arguments),
so a declaration like Stopwatch w; will be legal in a program
the Stopwatch class has a public copy constructor and assignment operator, so
applications can assign one Stopwatch object to another and applications can pass
Stopwatch arguments to functions
the start(), stop(), and reset() functions are the only functions that modify the internal
state of the Stopwatch - all of the other operations merely report some information
about the Stopwatchs current state
Page 7 - 18
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 18
Example manual page
DESCRIPTION (continued)
Status
int status() const;
Returns 1 if the Stopwatch is running and 0 if
it is stopped.
Setting operations
void start();
If the Stopwatch is already running, this call
has no effect. If the Stopwatch is stopped, it
starts running and the time measurement
resumes where it left off. Requires a system
call (to the times() function in the UNIX
implementation) if the state changes from
stopped to running.
void stop();
If the Stopwatch is already stopped, this call
has no effect. If the Stopwatch is running, it
stops running and the time measurement is
halted. Requires a system call if the state
changes from running to stopped.
void reset();
Resets system(), user(), and real() to zero. The
status is not affected by this call; that is, if the
Stopwatch is running, it continues running
uninterrupted after resetting, and if it is
stopped, it remains stopped after resetting.
Always requires a system call.
Reading operations
double system() const;
double user() const;
double real() const;
Returns the system time, user time, and real
time, respectively, in units of seconds.
Requires a system call if the Stopwatch is
running.
BUGS
Under SunOS 3.5, real() always returns zero.
The start(), stop(), and reset() operations contain a little bit of performance information. They
explain under what conditions they make a system call (which may have an impact on the
performance of programs that include the Stopwatch class and make lots of calls to these
functions).
Page 7 - 19
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 19
Alternative manual page format
NAME
Stopwatch program execution time measurement
SYNOPSIS
#include <Stopwatch.h>
class Stopwatch {
public:
Stopwatch();
int status() const;
void start();
void stop();
void reset();
double system() const;
double user() const;
double real() const;
};
DESCRIPTION
A Stopwatch is used to time the execution of
software running on a UNIX-based system. The
Stopwatch object can report the system, user, and
real time that has elapsed since the Stopwatch was
started.
Stopwatch w;
Creates a Stopwatch for which system(), user(),
and real() initially read zero and status() returns
zero.
w.start()
If status() is initially nonzero, the call has no
effect. If status() is initially zero, status()
becomes nonzero and measurement resumes.
Requires a system call if status() is initially
nonzero.
This is an alternative manual page style: usage syntax.
the Synopsis section lists the public interface of the class plus the names of the
header files where the interface is defined
the Description section contains sample invocations of the public operations of the
class with a description of the semantics of the operation
The usage syntax format is easier for beginners to understand, but it is somewhat more
complex to read when you want to get detailed interface information. It is necessary to cross-
reference between the Description and Synopsis sections in order to determine which function
arguments are references and which are pointers, and to determine constness information.
Page 7 - 20
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 20
Alternative manual page format (cont.)
DESCRIPTION (continued)
w.stop()
If status() is initially zero, the call has no
effect. If status() is initially nonzero, status()
becomes zero and measurement is halted.
Requires a system call if status() is originally
nonzero.
w.reset()
Resets system(), user(), and real() to zero.
The value of status() is not affected by this
call; that is, if status() is originally nonzero,
measurement continues uninterrupted after
resetting. Requires a system call.
w.user()
Returns the user CPU time in double precision
floating point seconds. Requires a system call
if status() is originally nonzero.
w.system()
Returns the system CPU time in double
precision floating point seconds. Requires a
system call if status() is originally nonzero.
w.real()
Returns the real time in double precision
floating point seconds. Requires a system call
if status() is originally nonzero.
w.status()
Returns 1 if w is running and 0 otherwise.
w1 = w2
The state of Stopwatch w2 is copied to w1.
Stopwatch w1(w2);
The Stopwatch w1 is created with the same
internal state as w2.
BUGS
Under SunOS 3.5, real() always returns zero.
Page 7 - 21
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 21
Design reviews and code reviews
It is especially valuable to have other developers who take the time to
review a design document, a manual page, a class header file, or a
class implementation
design-level documents need to be readable (because they will be read by
many class users)
a design review can be used to help create test cases for unit-level test
at the code level, a review of class header files is an alternative way to do
a design review
reviewing class implementations can often result in some rewriting to take
advantage of existing class libraries
For some kinds of reviews (especially for reviews that will discuss
design issues), a meeting is necessary. For other kinds of reviews
(coding and documentation details), written comments can be enough
A review is not merely an item on the process checklist. It is an integral part of getting the
design, coding, and documentation details right.
Page 7 - 22
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 22
Review tips
Keep the review team small (<=5 reviewers)
Include some of the people who will use the classes on the review
team for designs, manual pages, and header files
Review small pieces of the design or code in each meeting, so you can
keep the time for an individual meeting under 90 minutes
Make sure that each reviewer has the material to be reviewed at least
two days in advance
Make a written record of the issues that come up in the review
Decide at the end of the meeting if a re-review is required
It has been said that the effectiveness of a formal review is inversely proportional to the
number of participants greater than 5. This is probably due to the extra work that the author
of the document or code expends to make a fancy presentation with color graphics instead of
focusing on the real content of the system.
The most effective review teams usually include just four people: the author, another person
from the authors immediate work group, a person from the group who will be using the
document or code, and a person from a completely different area.
The person from the authors group will have the best inside knowledge of where
the skeletons live.
The person from a group who uses the document or code can help identify
unnecessary verbiage or code.
The person from a completely different area may ask some of the critical questions
that everyone else doesnt think of because they are too close to the problem.
Page 7 - 23
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 23
Testing C++ classes
Testing is the last resort in quality control
It is better to spend some effort in the design and coding phases to make
sure that you dont introduce errors, rather than spending lots of effort in
testing and debugging code to take errors out
Writing and using good encapsulated datatypes in C++ makes some kinds
of bugs highly unlikely:
incorrect initialization of a structure (the constructors will initialize
everything in a class if they are properly written)
calling a function with the wrong number and/or wrong types of
arguments
Unit-level tests can be a good adjunct to the class documentation
to provide a formal definition of how the class should really work
unit test may be the most unambiguous class documentation
You cant turn a bad class into a good class by adding tests. On the other hand, a public set of
class-level tests that are delivered with a class (or group of classes) will give users of the class
more confidence in the implementation.
Page 7 - 24
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 24
Testing alternatives
design review
code walkthrough
simple test suites
builtin self-test functions
unit test frameworks
There is no magic wand for testing. There are no tools that you can run on your system that
with pronouce it bug-free. Testing is an intellectual endeavor that requires the tester to
understand what some software is supposed to do and a little bit about how it is supposed to do
it.
Data encapsulation does make testing harder, because the goal in design is to hide as much
as possible. This means that in order to reveal the bugs, you may have to dig deeper into a
complex structure.
The best way to start the digging is with a design review. This kind of review may detect
errors or incomplete statements in the class documentation.
Page 7 - 25
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 25
Inspections, reviews, and walkthroughs
One of the best ways to minimize programming mistakes in the
implementation of a class is to get help during the design phase.
A design review can help you discover subtle algorithm errors.
It can also help you to discover some of the boundary values that you will
want to include in a test suite. For example:
empty string as an input value
deleting the last item from a data structure
overflow and underflow on a buffer
Code reviews are also valuable, because
they can find programming errors early, and
they help force the programmer to write code in a clear, understandable
style
There are two benefits to a design review. First, it helps the designer find problems earlier in
the development process. Second, it makes at least one other person familiar with the design.
The other reviewers may be called in to do code reviews as well, so familiarity with the design
is valuable. In addition, the other reviewers may be aware of existing implementations that
could be reused to implement part of the design.
Boundary conditions are the most important things to test. The design review can help to
define the domain of each of the major components. (In this sentence, the word domain
means the set of permissible input values to a function, which is the mathematical meaning of
domain.)
Page 7 - 26
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 26
Simple test suites
Testing classes is best done using a white box testing technique. That
is, you write the tests based on the implementation, not just the
interface. You need to generate a set of test programs that will
create objects using each one of the constructors
call each function in the public interface with a variety of inputs
make the object enter all of the possible internal states that it can be in
try to execute each line of code at least once
There are a variety of tools to collect information about the test
coverage by adding instrumentation to a program and determining
which lines of the program are executed in a series of test runs
Simple test suites are often written by the same person who develops the class. Its a good
idea to develop the tests at the same time as the code, since this avoids the last-minute testing
panic that occurs in many software development projects.
Page 7 - 27
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 27
Builtin self-test
One way to have a class test itself is to write extra testing functions:
/* file Myclass.h */
#ifndef __MYCLASS_H
#define __MYCLASS_H
class Myclass {
public:
#ifdef DEBUG
static int test_me();
static int
constructor_test();
static int
func1_test();
#endif
.
}
#endif
/* file Myclass_test.c */
#include "Myclass.h"
#ifdef DEBUG
int Myclass::constructor_test() {
int retval = 1;
// test constructors here
return (retval);
}
int Myclass::test_me() {
if (!constructor_test()) return 0;
if (!func1_test()) return 0;
if (!func2_test()) return 0;
return 1; // all tests passed
}
#endif
The extra testing functions could be a group of static member functions that run a small test
suite for each member function in the class.
This testing scheme is relatively easy to implement for low-level classes (classes which arent
build on top of other classes). For high-level classes, you can run the tests for the lower-level
classes first, so the tests for the high-level classes can make the assumption that the lower-level
pieces work correctly.
Page 7 - 28
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 28
When to create unit tests
One development strategy write unit tests for key classes in your system
Should every class have a unit test? No.
It is useful, however, to create unit tests for classes in the following situations:
Your class will be modified frequently in the current software release or in
following releases.
easier to do refactoring of existing code if you have unit tests less risk of
breaking existing functionality
Your class will be widely used in other software applications written by many
developers in your organization.
unit tests improve everyones confidence
Your class will be part of a commercial library.
complete test suite avoids problems in the field
Your class has many internal states.
it may be almost impossible to desk-check the state transitions, so a good
body of unit test code may work better
An unstable or dynamic design is easier to work on if you have unit tests. The tests help you
check if you have accidently broken some existing functionality. See the book Refactoring:
Improving the Design of Existing Code by Martin Fowler for more information.
Unit tests really help make a software module more reusable. You write the tests for two
reasons: to protect yourself from creating problems for other team members, and to give other
members of your team more confidence in your classes.
Unit tests are a big aid in making a robust commercial class library. You can use the tests to
make sure that you know what the class will do with any possible inputs.
Page 7 - 29
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 29
Frameworks
A framework is a collection of reusable classes
a framework user will create new (derived) classes that extend basic
functionality in the classes in the framework
frameworks often contain abstract classes (classes with incomplete
implementations)
the classes in the framework will implement some generic behavior that
will be able to work even for new programmer-defined classes
Example: Microsoft Foundation Classes (MFC)
there are many basic classes in the MFC class library, such as CWnd,
CView, CDocument, CWinApp, and CException
you will add application-specific behavior to classes that are derived from
these framework classes
Frameworks are better than class libraries, because the user of a framework can build
application-specific behavior into new classes that work together with the frameworks classes.
On the other hand, frameworks are harder to build, harder to document, and trickier to use than
a regular class library.
Page 7 - 30
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 30
MFC structure
Most MFC applications are constructed using a development
environment (like the environment Microsoft Visual C++) which helps
create the template for the new derived classes
Most MFC applications deal with at least one document type and at
least one view type
The format and behavior of a dialog box can be specified in a class that
is derived from the MFC CDialog class
/* file Myapp.h */
class CMyappApp :
public CWinApp { .... };
/* file MyappDoc.h */
class CMyappDoc :
public CDocument { .... };
/* file MyappView.h */
class CMyappView :
public CView { .... };
/* file MyAuxDlg.h */
class MyAuxDlg :
public CDialog { .... };
The new derived classes can be built with the assistance of some generator programs
(Microsoft calls them wizards). The generator program will create the template of the
derived class and provide a menu-based interface for specifying some of the functions that
need to be added to the derived classes.
In addition, the generator programs may add some required information to other header files
and/or resource files that will be needed at compile-time.
Page 7 - 31
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 31
MFC example
Example: creating a new dialog box class
/* file MyAuxDlg.h */
class MyAuxDlg : public CDialog {
public:
MyAuxDlg(CWnd *pParent = NULL);
void OnButton1Aux();
protected:
virtual void DoDataExchange(CDataExchange *pDX);
DECLARE_MESSAGE_MAP()
};
/* file MyAuxDlg.cpp */
#include "MyAuxDlg.h"
void MyAuxDlg::OnButton1Aux() { ... handle the event ... }
void MyAuxDlg::DoDataExchange(CDataExchange *pDX) { ... }
BEGIN_MESSAGE_MAP(MyAuxDlg, CDialog)
ON_BN_CLICKED(IDC_BUTTON1_AUX, OnButton1Aux)
END_MESSAGE_MAP()
This slide shows some of the standard boilerplate code that is used in the creation of an
actual derived class that implements a specific kind of dialog box.
The OnButton1Aux() function will be invoked whenever an IDC_BUTTON1_AUX message
is sent by the dialog box. The programmer can decide what application-level information
needs to be updated.
The DoDataExchange() function is another important part of the MFC framework. The
implementation of this function should contain calls to some functions that exchange data
between the screen-based control objects (text fields, check boxes, radio buttons, list boxes,
and other data collection and display objects that appear inside of the dialog box) and the local
variables defined within the MyAuxDlg class. The DoDataExchange() function will be called
automatically to update the local Dialog class variables when the user enters some data, and
the DoDataExchange() function will also help transfer the right data from the MyAuxDlg class
to the control objects whenever the programmer makes a call to the CDialog::UpdateData()
function.
Page 7 - 32
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 32
Design Patterns
Design patterns are small mini-architectures that occur in many
applications
The original application of patterns was in architecture, not in
computer software
Patterns are a useful reuse technique, and they are a good way to
document design ideas
Page 7 - 33
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 33
Patterns: a reuse technique
A pattern is a solution to a problem in a context
the idea originally comes from the architect Christopher Alexander
It is one way to formalize some of the tricks and idioms that are
becoming standard engineering practice
Since 1994, there have been books, articles, conferences, and training
courses on documenting the patterns of software development
design patterns: standard groupings of classes that solve many problems
example: the Observer pattern
organizational patterns: one way of recording successful models of
organizational structure and communication techniques within a software
development organization
Alexander introduced the idea of patterns in his book The Timeless Way of Building (Oxford
University Press, 1979). This book is about patterns in the design of buildings. He describes
some of the ways that combinations of architectural features contribute to the function and
aesthetics of a building, a neighborhood, or a community.
Several practitioners of object oriented technology recognized that the principals of patterns
could also apply to software design issues.
Patterns are not essentially object oriented, although a pattern-based approach to describing
design ideas has become very popular among experts in OO analysis and design. You can
apply patterns to non-OO design, or to other kinds of problems (see Unit 10 for a discussion of
organizational patterns).
Patterns are more than just tricks or idioms: they are an attempt to describe how small
design strategies work together in combination in the design of quality software systems.
There is an annual conference called Pattern Languages of Programming (PLoP) where new
patterns and new ways to apply pattern ideas are exchanged.
Page 7 - 34
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 34
Design example
Problem charter: The system to be built is a capacity meter for a
parking garage. The meter can display two numbers: the number of cars
actually in the garage at the current time and the maximum number of cars
that the garage can hold. Assume the garage has one entrance and one
exit, and there are sensors at each which can signal when a car is entering
or exiting the garage.
0
250
500
Max capacity: 370
Current vehicle count: 280
280
Alternative display choices for a capacity meter
The capacity meter might be implemented in several different ways, depending on the display
technology. The most useful kind of display would be a large, clearly readable sign outside of
the entrance to the parking garage.
Page 7 - 35
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 35
One possible solution
Three classes:
Car counter: responsible for recording the number of cars currently in the
garage
Car sensor: responsible for sensing cars entering and leaving
Meter: responsible for displaying the car counter information
Car counter
Initialize car count
Add to car count
Deduct from car count
Meter
Car sensor
Car is entering
Car is leaving
Car counter
Meter
update
The Meter class is very simple - it just has an update display responsibility (for now).
By the way, there are many possible solutions to this problem. The solution that is presented
here is motivated by the Model-View-Controller interaction that is implemented in many
object oriented systems that control some kind of display.
The Car sensor and the Meter classes correspond to real-world objects that are described in the
problem statement. The Car counter doesnt appear anywhere in the problem statement - it is a
class that has been put into the model to help divide up the responsibilities.
Page 7 - 36
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 36
One design-level scenario
The Car sensor objects dont update the Meter directly: they ask the
Car counter to update its internal state
The car is entering and car is leaving arrows are external events
Car sensor 1
Meter Car sensor 2 Car counter
car is entering
add to car count
update display
update display
deduct from car count
car is leaving
This scenario describes the sequence of interactions between the objects of the system when an
external event occurs (a car enters the garage or leaves the garage).
It is possible to make this model more complicated by adding extra objects. For example, the
Car counter might also communicate with a Gate control object, which will determine whether
the gate should be kept in a down position when the garage is full.
Page 7 - 37
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 37
The Observer pattern
Problem: One object contains some state
information, but several other objects
need to be notified each time that state
information is changed.
Context: You dont want to put everything
in one class, for one of these reasons:
in the next design iteration the main class
wont change, but some of the other
classes might change
the list of objects that need to be notified
is changing at runtime
Solution: The observed object keeps a list
of the objects that want to be notified.
garageCounter : Car counter
car_count = 100
olist : List of Observer objects
Objects to notify when
car_count changes:
gcontrol : GateControl
entrance2Meter : Meter
entrance1Meter : Meter
value : int
In this example, the garageCounter object is the observed object, and the two Meter and one
GateControl objects are the observer objects.
For this solution to work, the Car counter class needs to have register and unregister
operations that can be called by Observer objects that want to be notified. The Observer
objects must have an update operation that the Car counter can call whenever the internal
state of the Car counter is changed.
The Observer pattern helps to reduce the degree of coupling between the classes.
It is very easy to change the number of Meters and GateControls in future system
releases, with no changes at all to the implementation of the Car counter class.
Changes can be made to the internal implementations of Meter and GateControl
without affecting Car counter.
If new Observer classes need to be added in a future release, the Car counter class
still stays unchanged.
Page 7 - 38
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 38
Class diagram for Observer pattern
Some patterns are described
using class diagram
notation
In this example, the
Subject and Observer
classes are abstract classes
Each ConcreteObserver
object (like Meter) needs to
register by calling the
Attach() operation on a
ConcreteSubject object
(like Car counter)
ConcreteSubject
subjectState
GetState()
ConcreteObserver
observerState
Update()
Observer
Update()
observers
for all o in observers {
o ->Update()
}
observerState =
subject->GetState();
return subjectState
1 0..*
Subject
Attach(Observer)
Detach(Observer)
Notify()
One of the advantages of this notation is that it is very easy for the pattern readers to apply
this solution to their problems. The skeleton Class diagram can be practically copied into the
design-level model, and the notes that are attached to some of the classes provide some good
hints on how to write the C++ code to implement some of the key functions.
Page 7 - 39
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 39
What is in a pattern?
A pattern needs to describe the problem and the context precisely
the pattern readers must be able to determine if it can help solve their
problem
the pattern writers often use object oriented analysis/design notation
(such as UML Class diagrams) to describe the problem, the context, and
the solution
But the problem and context have to be general enough
There are several different formats for patterns
Class diagrams with generalized examples are used throughout the Design
Patterns book
Patterns have become a shorthand for designers to talk about
possible solutions
Patterns could be called a literary form. Well-written patterns usually do include some of
the elements of a good novel or short story:
some characters (the classes that participate)
a setting (the context of the problem)
some kind of conflict (the design forces that have to be balanced)
a resolution (the proposed solution to the problem)
The standard documentation style first presented by Gamma (in the Design Patterns book)
always contains a section titled Applicability that spells out the situations in which the
pattern can be applied.
If two designers are already familiar with the vocabulary of Gammas 23 design patterns, it
can be a good way to talk about some design decisions. For example, one designer can tell
another, My ScreenUpdater class is an Observer of the NetworkConfig class. This is much
more concise than saying, I have modified the classes to permit ScreenUpdater objects to
register with a NetworkConfig object for notification every time the NetworkConfig object
changes state.
Page 7 - 40
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 40
Patterns are reusable designs
When you use a pattern, you are applying abstraction:
you are matching your problem with a generalized problem statement
in a pattern (or a group of patterns)
you are weighing the tradeoffs: some patterns give a list of potential
problems in a section labeled consequences
if the pattern works, you benefit from design reuse
There are groups of patterns that work together
a group of patterns that work together is called a pattern language
Good designs and architectures are full of patterns
take the time to look for the patterns you are using
document your patterns so others can use them
you might even bring in a pattern miner to look for patterns in your
existing designs
There is no single book that contains all the patterns you will ever need. You may discover
that you have some patterns in your applications area that arent found in any books yet.
Grady Booch discusses the benefits of pattern documentation during design in his book
Object Solutions (Addison-Wesley, 1996). He believes that there are two tangible benefits
from expending design-time effort on pattern writing:
Patterns capture the architects intent and vision, and so serve to propagate that vision
in a very tangible way.
Patterns provide common solutions to related problems, and thus are necessary for the
creation of simple systems.
That is, if you document some of your design mechanisms as patterns, it might be easier for
other developers to understand the design - they will therefore have an easier time making later
modifications that dont break the design.
Some of the articles and books describe pattern languages for specific domains. These
groups of patterns often contain between 3 and 30 related patterns.
Page 7 - 41
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 41
Wrapper classes
Wrapper classes are one way to do hybrid OO programming
at a high-level, everything looks like a class
but the low-level implementation of some classes might be non-OO
interfaces to legacy code
Examples:
existing transaction processing systems
encapsulation of the interfaces to existing input devices
commercial C-based software packages or application generators
The implementation of a wrapper class might contain some non-C++
code
it is easy to build interfaces between C++ code and C code
Wrapper classes are often used in C++-based applications, in order to exploit existing C-based
software packages. It is possible to use the wrapper classes approach in other OO languages:
for example, Java applications can use the Java Native Interface (JNI) to implement Java
classes with non-Java implementations for some of the class operations.
Page 7 - 42
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 42
Wrapping a C subroutine library
Suppose a legacy application contains a set of message logging
utility routines that need to be used in a new C++ application
The legacy utility routines dont need to be rewritten: they can be
wrapped instead
/* file log_routines.c */
/* Existing C-based logging utilities */
int openlogfile(fname)
char *fname;
{ ... }
int writelogfile(fd, buf)
int fd;
char *buf;
{ ... }
int closelogfile(fd)
int fd;
{ ... }
The following slide will illustrate one possible wrapping strategy for the C library shown
above.
Page 7 - 43
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 43
Wrapper class implementation
/* file Log.h */
/* external declaration of all
of the C functions */
extern "C" {
int openlogfile(const char *);
int writelogfile(int,
const char *);
int closelogfile(int);
}
class Log { // the wrapper class
public:
Log(const char *);
int write(const char *);
~Log();
friend Log& operator<<(Log&,
const char *);
private:
int fd;
Log(const Log &);
Log& operator=(const Log &);
};
/* file Log.c */
/* implementation of wrapper
class operations */
Log::Log(const char *fname) {
fd = openlogfile(fname);
}
int Log::write(const char *msg) {
return (writelogfile(fd, msg));
}
Log::~Log() {
closelogfile(fd);
}
Log& operator<<(Log &lg,
const char *msg) {
writelogfile(lg.fd, msg);
return (lg);
}
This wrapper class contains a single data member (the file descriptor returned by the
openlogfile() function) which is initialized by the constructor. Both of the output functions
make use of the writelogfile() function and the destructor automatically calls the closelogfile()
function.
Notice that the copy constructor and the assignment operator have been declared in the private
section of the Log class. This is one standard technique to make the compiler prohibit
assignment to a Log object or making a copy of a Log object.
Page 7 - 44
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 44
Using a wrapper class
The wrapper class is very easy to use in a C++ application:
/* file main.c */
#include <Log.h>
int main(int argc, char **argv) {
Log errorlog("/tmp/errlog1");
errorlog.write("main program starting\n");
int j = 1;
while (j < argc) {
if (access(argv[j], 0) != 0) {
errorlog << "Cant open file " << argv[j] << "\n";
}
j++;
}
/* do something useful with the command line args */
return (0);
/* the log file is automatically closed by
the destructor */
}
In this example code, the main program creates a Log object immediately and writes
occasional messages to that object. It isnt necessary (in this case) to destroy the Log object,
because it will be automatically destructed at the return statement the destructor call is
automatically inserted by the C++ compiler.
Page 7 - 45
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 45
Commercial wrappers
Many existing commercial C++ class libraries are sets of wrapper
classes around some other software product:
Example: database wrappers (like RogueWaves Dbtools.h++)
wrappers can provide a unified interface to several different similar
software products (Oracle, Sybase, Informix)
Commercial wrapper classes have a lot of value, because they create an easy-to-learn and
easy-to-use interface for some complicated software packages.
The book Designing Scalable Object-Oriented Database Applications by Peter Heinckiens
(Addison-Wesley, 1998) is a useful reference if you plan to use database wrappers such as
Dbtools.h++ to build two- or three-tier software systems. Peter describes techniques for
building solid C++ classes that isolate business logic and user interface details from the
database structure.
Page 7 - 46
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 46
Wrapper rationales
Simplified interfaces
a wrapper class can be used to
create a simplified interface to a
big complex class (or a cluster of
classes)
Simplified documentation
it can be easier to write
documentation for a simple
wrapper class
Incorporating standard C++
classes
a wrapper class can use many of
the standard C++ library classes
(like string, iostream, and the STL
classes in its implementation)
Using wrapper classes in
standard containers
when you turn the data part of a
complex interface into an object,
you can put these wrapper objects
into a list or map
Wrappers help to simplify interfaces and documentation in a large system.
Page 7 - 47
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 47
Wrappers and patterns
The Design Patterns book discusses three kinds of wrappers:
Bridge
Facade
Proxy
Each one of these patterns is used to solve different problems:
Bridge: create an abstraction that has a decoupled implementation
Facade: provide a unified interface to a set of interfaces (possibly from
several different functions or classes within a subsystem)
Proxy: create a special gateway object that controls access to another
object
The Bridge pattern is sometimes known as the Handle-Body or Handle-Representation
idiom. It has been described in many places in the standard C++ literature. Each handle
object forwards all of its operations to a real implementation-level object.
The Facade pattern is a good way to reduce coupling between classes in a large system. It
makes a complex subsystem appear to be a single object to users of the Facade class.
The Proxy pattern is often used to reduce the cost of storing large objects or the cost of doing
slow operations on certain special objects. Proxy objects can be very simple and fast objects
that occasionally delegate complex requests to another bigger or slower object.
Page 7 - 48
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 48
Bridge
The Bridge pattern consists of at least two classes
the interface class (sometimes called the handle class) which will be
used by other programmers
the implementation class (sometimes called the representation class)
which will only be used by the interface class
Each interface class object will contain a pointer to an implementation
object
String objects StringRep object (shared)
2
6
"hello\0"
string length
reference count
In the example above, the String class is the interface class, which will be used by other
programmers. The StringRep is a special internal class that implements the representation
of a string. It implements extra implementation-level things such as reference counts, and it
deals with efficient memory allocation and deallocation.
It is possible to make the Bridge pattern more complicated: the interface class and the
implementation class might each be replaced by large class hierarchies. For example, the
implementation classes might be a class hierarchy that implement different kinds of
windowing systems. The interface classes could be a set of classes that give the user some
simple and/or sophisticated interface to the capabilities of the windowing system hierarchy.
Each of these two hierarchies could change independently of the other.
Page 7 - 49
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 49
Facade
A Facade class wraps a bunch of operations on other classes (or a
bunch of legacy code operations) into a convenient package
application Facade class
pointers to other data
objects within a
subsystem
get_val1()
build_trans2()
do_trans3()
commit_trans4()
application calls
some of the Facade
class operations
scanner
database
interface
parser
formatter
the Facade accesses
some of the internals
of a complex subsystem to
implement its operations
The Facade pattern is different from the Bridge pattern in a couple of ways:
the data section of a Facade class may be more than just a simple pointer
the implementation of the Facade member functions might need to interact with
many classes and/or functions in the subsystem that it is hiding
The Facade pattern is the most commonly used pattern for burying a non-OO legacy
subsystem.
Page 7 - 50
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 50
Proxy
A Proxy object is a stand-in for an object that might be:
located on another machine (example: CORBA objects)
or it might be large and complicated, so you want to defer building it
or it might require a special access method (to check the users
permissions)
client application
network
ORB (server)
CORBA
IDL stubs
IDL skeletons
Interface
Repository
Object
Implementations
CORBA example
In the Proxy pattern, the Proxy object doesnt necessarily have a simple way to access the
information in the class that stands behind the Proxy object: it might involve a complex
communication protocol instead of just following a pointer.
For example, CORBA implementations usually generate client-side stub classes (which are
simple Proxy classes) directly from an IDL specification of a class). The operations of these
stub classes will send messages to the real object implementations that live on a server
machine. The Object Request Broker (ORB) on the client machine knows how to parse the
messages from stub classes. The ORB will pick out the right operations from the CORBA-
generated code (the IDL skeletons and Interface Repository) and the user-constructed code
(the Object Implementations) to return the right information to the client.
Examples of non-distributed-object related Proxy objects include:
bitmap objects (creating or downloading a bitmap might be deferred until the
application knows what section of the bitmap is needed)
sparse matrix objects (you might not compute all elements of an array immediately)
Page 7 - 51
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 51
Recap
This tutorial presented some information about:
using existing C++ library classes
creating class documentation
reviewing class designs and implementations
building test code
using existing frameworks
using standard design patterns
creating wrapper classes to encapsulate legacy code
Which of these habits is most important to creating quality software?
Answer: it depends...
Page 7 - 52
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 52
Learning good habits
Using existing C++ libraries helps in two ways
you become more productive when you build on the work of others
C++ beginners can learn some important design ideas by reading existing
C++ code and documentation
Creating good documentation helps the users of your classes
you may be the next user of your own software, so write simple, clear, and
complete documentation
Reviews
help find mistakes in design, coding, and documentation
help novice C++ers learn good coding style
help find boundary conditions for unit-level tests
spread the understanding of a design from class writers to some class users
Page 7 - 53
Alcatel-Lucent Proprietary
Use pursuant to Company Instructions
C++ Fundamentals
Alcatel-Lucent Bell Labs
page 7 - 53
More good habits
Writing unit-level tests for your own classes
makes them more reusable / makes the code easier to extend later
helps to check the documentation
Frameworks help with reuse
the best way to organize reusable classes
Patterns can be used profitably at the design level
existing proven design ideas
a good way to communicate design idioms with other designers
Wrapper classes
permit new code to interface with legacy code
make software easier to document / minimizes coupling between
subsystems

Você também pode gostar