/********************************************\
| 欢迎转载, 但请保留作者姓名和原文链接, 祝您进步并共勉! |
\********************************************/
C++对象模型(3) - An Object Distinction
作者: Jerry Cat
时间: 2006/04/23
链接: http://www.cppblog.com/jerysun0818/archive/2006/04/24/6114.html
1.3 An Object Distinction
-------------------------
intercede in a and b:在a和b间进行调解
The most common inadvertent mixing of idioms occurs when a concrete instance of a base class, such as
Library_materials thing1;
is used to program some aspect of polymorphism:
// class Book : public Library_materials { ...};
Book book;
// Oops: thing1 is not a Book!
// Rather, book is "sliceD" — thing1就是个只保留book的上半身的残废东西
// thing1 remains a Library_materials
thing1 = book;
// Oops: invokes
// Library_materials::check_in()
thing1.check_in();
rather than a pointer or reference of the base class:
// OK: thing2 now references book, 因为基类和派生类的布局是"基部重合在一起的", 派生类还是超集哩!
// 基类在"上"(低址处), 派生类多出的部分紧接着"连"在下面; 引用(本质上是指针)和指针对这两种数据类型
// 有类似汇编中word ptr 和 dword ptr的关系, 它俩的首址是相同的. 编译器会自动鉴别基类和子类从而调整
// 类似word ptr 和 dword ptr的这种类的"类型寻址"操作
// 而且Scott Meyer说过它们是一种"is a"的关系:"The derived is a base class"
// 向上(基类方向)转换没问题的, 向下转换一般不可 - 简直"无中生有"嘛! 但MFC中对动态类对象CDerived(用
// DECLARE_DYNCREATE宏 和 IMPLEMENT_DYNCREATE宏在程序运行时而非编译动态生成)倒可用DYNAMIC_DOWNCAST
// 宏来完成将指向CBase的指针Downcast成指向它:
// CDerived * pDerived = DYNAMIC_DOWNCAST(CDerived, pBase); //CBase *pBase;
// 原型为DYNAMIC_DOWNCAST( class, pointer )
Library_materials &thing2 = book;//本质是地址,用起来象对象! 对象别名也,从这角度就是对象了嘛^_^
// OK: invokes Book::check_in()
thing2.check_in();
只有指针和引用才能"救多态"!
Although you can manipulate a base class object of an inheritance hierarchy either directly or indirectly, only the indirect manipulation of the object through a pointer or reference supports the polymorphism necessary for OO programming. The definition and use of thing2 in the previous example is a well-behaved instance of the OO paradigm. The definition and use of thing1 falls outside the OO idiom; it reflects a well-behaved instance of the ADT paradigm. Whether the behavior of thing1 is good or bad depends on what the programmer intended. In this example, its behavior is very likely a surprise.
// represent objects: uncertain type
Library_materials *px = retrieve_some_material();
Library_materials &rx = *px;
// represents datum: no surprise
Library_materials dx = *px;
it can never be said with certainty what the actual type of the object is that px or rx addresses. It can only be said that it is either a Library_materials object or a subtype rooted by Library_materials class. dx, however, is and can only be an object of the Library_materials class. Later in this section, I discuss why this behavior, although perhaps unexpected, is well behaved.
Although the polymorphic manipulation of an object requires that the object be accessed either through a pointer or a reference, the manipulation of a pointer or reference in C++ does not in itself necessarily result in polymorphism! For example, consider
// no polymorphism
int *pi;
// no language supported polymorphism
void *pvi;
// ok: class x serves as a base class
x *px;
多态只存在于
In C++, polymorphism exists only within individual public class hierarchies. px, for example, may address either an object of its own type or a type publicly derived from it (not considering ill-behaved casts). Nonpublic derivation and pointers of type void* can be spoken of as polymorphic, but they are without explicit language support; that is, they must be managed by the programmer through explicit casts. (One might say that they are not first-class polymorphic objects.)
The C++ language supports polymorphism in the following ways:
1. Through a set of implicit conversions, such as the conversion of a derived class pointer to a pointer of its public base type:
shape *ps = new circle();
2. Through the virtual function mechanism:
ps->rotate();
3. Through the dynamic_cast and typeid operators:
if ( circle *pc = dynamic_cast< circle* >( ps )) ...//象MFC中DYNAMIC_DOWNCAST和DECLARE_DYNCREATE,
//IMPLEMENT_DYNCREATE, IsKindOf(RUNTIME_CLASS(class))的组合拳
// example for CObject::IsKindOf
/* BOOL IsKindOf( const CRuntimeClass* pClass ) const; */
CAge a(21); // Must use IMPLEMENT_DYNAMIC or IMPLEMENT_SERIAL
ASSERT( a.IsKindOf( RUNTIME_CLASS( CAge ) ) );
ASSERT( a.IsKindOf( RUNTIME_CLASS( CObject ) ) );
// example for RUNTIME_CLASS
/* RUNTIME_CLASS( class_name ) */
Use this macro to get the run-time class structure from the name of a C++ class.
RUNTIME_CLASS returns a pointer to a CRuntimeClass structure for the class specified by class_name. Only CObject-derived classes declared with DECLARE_DYNAMIC, DECLARE_DYNCREATE, or DECLARE_SERIAL will return pointers to a CRuntimeClass structure.
CRuntimeClass* prt = RUNTIME_CLASS( CAge );
ASSERT( lstrcmp( prt->m_lpszClassName, "CAge" ) == 0 );
=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=
The memory requirements to represent a class object in general are the following:
1.) The accumulated size of its nonstatic data members
2.) Plus any padding (between members or on the aggregate boundary itself) due to alignment constraints (or simple efficiency)
3.) Plus any internally generated overhead to support the virtuals
The memory requirement to represent a pointer, [2] however, is a fixed size regardless of the type it addresses. For example, given the following declaration of a ZooAnimal class:
[2]Or to represent a reference; internally, a reference is generally implemented as a pointer and the object syntax transformed into the indirection required of a pointer.
class ZooAnimal {
public:
ZooAnimal();
virtual ~ZooAnimal();
// ...
virtual void rotate();
protected:
int loc;
String name;
};
ZooAnimal za( "Zoey" );
ZooAnimal *pza = &za;
a likely layout of the class object za and the pointer pza is pictured in Figure 1.4. (I return to the layout of data members in Chapter 3.)
Figure 1.4. Layout of Object and Pointer of Independent Class
The Type of a Pointer:
=-=-=-=-=-=-=-=-=-=-=
But how, then, does a pointer to a ZooAnimal differ from, say, a pointer to an integer or a pointer to a template Array instantiated with a String?
ZooAnimal *px;
int *pi
Array< String > *pta;
In terms of memory requirements, there is generally no difference: all three need to be allocated sufficient memory to hold a machine address (usually a machine word). So the difference between pointers to different types rests neither in the representation of the pointer nor in the values (addresses) the pointers may hold. The difference lies in the type of object being addressed. That is, the type of a pointer instructs the compiler as to how to interpret the memory found at a particular address and also just how much memory that interpretation should span:
An integer pointer addressing memory location 1000 on a 32-bit machine spans the address space 1000—1003.
The ZooAnimal pointer, if we presume a conventional 8-byte String (a 4-byte character pointer and an integer to hold the string length), spans the address space 1000—1015.
Hmm. Just out of curiosity, what address space does a void* pointer that holds memory location 1000 span? That's right, we don't know. That's why a pointer of type void* can only hold an address and not actually operate on the object it addresses.
So a cast in general is a kind of compiler directive. In most cases, it does not alter the actual address a pointer contains. Rather, it alters only the interpretation of the size and composition of the memory being addressed.
Adding Polymorphism
=-=-=-=-=-=-=-=-=-=
Now, let's define a Bear as a kind of ZooAnimal. This is done, of course, through public inheritance:
class Bear : public ZooAnimal {
public:
Bear();
~Bear();
// ...
void rotate();
virtual void dance();
// ...
protected:
enum Dances { ... };
Dances dances_known;
int cell_block;
};
Bear b( "Yogi" );
Bear *pb = &b;
Bear &rb = *pb;
What can we say about the memory requirements of b, pb, and rb? Both the pointer and reference require a single word of storage (4 bytes on a 32-bit processor). The Bear object itself, however, requires 24 bytes (the size of a ZooAnimal [16 bytes] plus the 8 bytes Bear introduces). A likely memory layout is pictured in Figure 1.5.
Figure 1.5. Layout of Object and Pointer of Derived Class
Okay, given that our Bear object is situated at memory location 1000, what are the real differences between a Bear and ZooAnimal pointer?
Bear b;
ZooAnimal *pz = &b;
Bear *pb = &b;
Each addresses the same first byte of the Bear object. The difference is that the address span of pb encompasses the entire Bear object, while the span of pz encompasses only the ZooAnimal subobject of Bear.
pz cannot directly access any members other than those present within the ZooAnimal subobject, except through the virtual mechanism:
// illegal: cell_block not a member
// of ZooAnimal, although we ``know''
// pz currently addresses a Bear object
pz->cell_block;
// okay: an explicit downcast
(( Bear* )pz)->cell_block;
// better: but a run-time operation
if ( Bear* pb2 = dynamic_cast< Bear* >( pz ))
pb2->cell_block;
// ok: cell_block a member of Bear
pb->cell_block;
When we write
pz->rotate();
the type of pz determines the following at compile time:
The fixed, available interface (that is, pz may invoke only the ZooAnimal public interface)
The access level of that interface (for example, rotate() is a public member of ZooAnimal)
The type of the object that pz addresses at each point of execution determines the instance of rotate() invoked. The encapsulation of the type information is maintained not in pz but in the link between the object's vptr and the virtual table the vptr addresses (see Section 4.2 for a full discussion of virtual functions).
So, then, why is it that, given
Bear b;
ZooAnimal za = b;
// ZooAnimal::rotate() invoked
za.rotate();
the instance of rotate() invoked is the ZooAnimal instance and not that of Bear? Moreover, if memberwise initialization copies the values of one object to another, why is za's vptr not addressing Bear's virtual table?
The answer to the second question is that the compiler intercedes in the initialization and assignment of one class object with another. The compiler must ensure that if an object contains one or more vptrs, those vptr values are not initialized or changed by the source object .
子类是基类, 基类非子类. 儿子是老子(生的), 老子非儿子(生的).
The answer to the first question is that za is not (and can never be) a Bear; it is (and can never be anything but) a ZooAnimal. Polymorphism, the potential to be of more than one type, is not physically possible in directly accessed objects. Paradoxically, direct object manipulation is not supported under OO programming. For example, given the following set of definitions:
{
ZooAnimal za;
ZooAnimal *pza;
Bear b;
Panda *pp = new Panda;
pza = &b;
}
one possible memory layout is pictured in Figure 1.6.
Figure 1.6. Memory Layout of Sequence of Definitions
Assigning pz the address of either za, b, or that contained by pp is obviously not a problem. A pointer and a reference support polymorphism because they do not involve any type-dependent commitment of resources. Rather, all that is altered is the interpretation of the size and composition of the memory they address.
Any attempt to alter the actual size of the object za, however, violates the contracted resource requirements of its definition. Assign the entire Bear object to za and the object overflows its allocated memory. As a result, the executable is, literally, corrupted, although the corruption may not manifest itself as a core dump.
When a base class object is directly initialized or assigned with a derived class object, the derived object is sliced to fit into the available memory resources of the base type. There is nothing of the derived type remaining. Polymorphism is not present, and an observant compiler can resolve an invocation of a virtual function through the object at compile time, thus by-passing the virtual mechanism. This can be a significant performance win if the virtual function is defined as inline.
多态是面向对象OO的实质
To summarize, polymorphism is a powerful design mechanism that allows for the encapsulation of related types behind an abstract public interface, such as our Library_materials hierarchy. The cost is an additional level of indirection, both in terms of memory acquisition and type resolution. C++ supports polymorphism through class pointers and references. This style of programming is called object-oriented.
ADT抽象数据类型是基于对象OB
C++ also supports a concrete ADT style of programming now called object-based (OB)—nonpolymorphic data types, such as a String class. A String class exhibits a nonpolymorphic form of encapsulation; it provides a public interface and private implementation (both of state and algorithm) but does not support type extension. An OB design can be faster and more compact than an equivalent OO design. Faster because all function invocations are resolved at compile time and object construction need not set up the virtual mechanism, and more compact because each class object need not carry the additional overhead traditionally associated with the support of the virtual mechanism. However, an OB design also is less flexible.
posted on 2006-04-24 03:45
Jerry Cat 阅读(681)
评论(0) 编辑 收藏 引用