Object Lessons From Lippman, C++ Object Model / PDF
Introduction
In C, a data abstraction and the operations
that perform on it are declared separately—that is, there is no
language-supported relationship between data and functions. We speak of this method
of programming as procedural, driven by a set of algorithms divided into
task-oriented functions operating on shared, external data. For example, if we
declare a struct Point3d, such as the following:
typedef struct point3d
{
float x;
float y;
float z;
} Point3d;
the operation to print a particular Point3d
might be defined either as a function
void
Point3d_print( const Point3d *pd )
{
printf(”( %g, %g, %g )”, pd->x, pd->y, pd->z );
}
or, for efficiency, as a preprocessor
macro:
#define Point3d_print( pd ) \
printf(”( %g, %g, %g )”, pd->x, pd->y, pd->z );
Or it may be directly implemented within
individual code segments:
void
my_foo()
{
Point3d *pd = get_a_point();
...
/* print the point directly ... */
printf(”( %g, %g, %g )”, pd->x, pd->y, pd->z );
}
Similarly, a particular coordinate member
of a point is accessed either directly:
Point3d pt;
pt.x = 0.0;
or through a preprocessor macro:
#define X( p, xval ) (p.x) = (xval);
...
X( pt, 0.0 );
In C++, Point3d is likely to be implemented
either as an independent abstract data type (ADT):
class Point3d
{
public:
Point3d( float x = 0.0,
float y = 0.0, float z = 0.0 )
: _x( x ), _y( y ), _z( z ) {}
float x() { return _x; }
float y() { return _y; }
float z() { return _z; }
void x( float xval ) { _x = xval; }
// ... etc ...
private:
float _x;
float _y;
float _z;
};
inline ostream&
operator<<( ostream &os, const Point3d &pt )
{
os << ”( ” << pt.x() << ”, ”
<< pt.y() << ”, ” << pt.z() << ” )”;
};
or as a two- or three-level class
hierarchy:
class Point {
public:
Point( float x = 0.0 ) : _x( x ) {}
float x() { return _x; }
void x( float xval ) { _x = xval; }
// ...
protected:
float _x;
};
class Point2d : public Point {
public:
Point2d( float x = 0.0, float y = 0.0 )
: Point( x ), _y( y ) {}
float y() { return _y; }
void y( float yval ) { _y = yval; }
// ...
protected:
float _y;
};
class Point3d : public Point2d {
public:
Point3d( float x = 0.0, float y = 0.0, float z = 0.0 )
: Point2d( x, y ), _z( z ) {}
float z() { return _z; }
void z( float zval ) { _z = zval; }
// ...
protected:
float _z;
};
Moreover, either of these implementations
may be parameterized, either by the type of the coordinate:
template < class type >
class Point3d
{
public:
Point3d( type x = 0.0,
type y = 0.0, type z = 0.0 )
: _x( x ), _y( y ), _z( z ) {}
type x() { return _x; }
void x( type xval ) { _x = xval; }
// ... etc ...
private:
type _x;
type _y;
type _z;
};
or by both the type and number of
coordinates:
template < class type, int dim >
class Point
{
public:
Point();
Point( type coords[ dim ] ) {
for ( int index = 0; index < dim; index++ )
_coords[ index ] = coords[ index ];
}
type& operator[]( int index ) {
assert( index < dim && index >= 0 );
return _coords[ index ]; }
type operator[]( int index ) const
{ /* same as non-const instance */ }
// ... etc ...
private:
type _coords[ dim ];
};
inline
template < class type, int dim >
ostream&
operator<<( ostream &os, const Point< type, dim > &pt )
{
os << ”( ”;
for ( int ix = 0; ix < dim-1; ix++ )
os << pt[ ix ] << ”, ”
os << pt[ dim–1];
os << ” )”;
}
These are obviously not only very different
styles of programming, but also very different ways of thinking about our
programs. There are many more or less convincing arguments for why the data
encapsulation of an ADT or class hierarchy is better (in the software
engineering sense) than the procedural use of global data such as that in C
programs. Those arguments, however, are often lost on programmers who are
charged with getting an application up and running quickly and efficiently. The appeal of C is both its leanness and
its relative simplicity.
The C++ implementations of a 3D point are
more complicated than their C counterpart, particularly the template instances.
This doesn’t mean they are not also considerably more powerful or, again in a
software engineering sense, better. But being more powerful or better is not
necessarily a convincing argument for their use.
Section : Layout Costs for Adding Encapsulation
An obvious first question a programmer
might ask while looking at the transformed Point3d implementations under C++
concerns the layout costs for adding encapsulation. The answer is that there
are no additional layout costs for supporting the class Point3d. The three
coordinate data members are directly contained within each class object, as
they are in the C struct. The member functions, although included in the class
declaration, are not reflected in the object layout; one copy only of each
non-inline member function is generated. Each inline function has either zero
or one definition of itself generated within each module in which it is used.
The Point3d class has no space or runtime penalty in supporting encapsulation.
As you will see, the primary layout and access-time overheads within C++ are
associated with the virtuals, that is,
- the
virtual function mechanism in its support of an efficient run-time
binding, and
- a virtual
base class in its support of a single, shared instance of a base class
occurring multiple times within an inheritance hierarchy.
There is also additional overhead under
multiple inheritance in the conversion between a derived class and its second
or subsequent base class. In general, however, there is no inherent reason a
program in C++ need be any larger or slower than its equivalent C program.
Section 1.1: The C++ Object Model
In C++, there are two flavors of class data
members—static and nonstatic—and three flavors of class member functions—static,
nonstatic, and virtual. Given the following declaration of a class Point:
class Point
{
public:
Point( float xval );
virtual ~Point();
float x() const;
static int PointCount();
protected:
virtual ostream&
print( ostream &os ) const;
float _x;
static int _point_count;
};
how is the class Point to be represented
within the machine? That is, how do we model the various flavors of data and
function members?
Section : A Simple Object Model
Our first object model is admittedly very
simple. It might be used for a C++ implementation designed to minimize the
complexity of the compiler at the expense of space and runtime efficiency. In
this simple model, an object is a sequence of slots, where each slot points to
a member. The members are assigned a slot in the order of their declarations.
There is a slot for each data or function member........
Object Lessons From Lippman, C++ Object Model / PDF