Object Lessons From Lippman, C++ Object Model / PDF

Object Lessons From Lippman, C++ Object Model / PDF






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

0 commentaires: