Chapter 15 Object-Oriented Programming

Object-oriented programming is based on three fundamental concepts:data abstraction, inheritance, and dynamic binding. In C++ we use classes for data abstraction and class derivation to inherit one class from another: A derived class inherits the members of its base class(es). Dynamic binding lets the compiler determine at run time whether to use a function defined in the base or derived class.


15.1 -- OOP: An Overview

NOTE: In C++, dynamic binding happens when a virtual function is called through a reference (or a pointer) to a base class.The fact that a reference (or pointer) might refer to either a base- or a derived-class object is the key to dynamic binding. Calls to virtual functions made through a reference (or pointer) are resolved at run time: The function that is called is the one defined by the actual type of the object to which the reference (or pointer) refers.


15.2 -- Defining Base and Derived Classes

The virtual keyword appears only on the member-function declaration inside the class. The virtual keyword may not be used on a function definition that appears outside the class body.


A derived object may access the protected members of its base class only through a derived object. The derived class has no special access to the protected members of base type objects.


With one exception, the declaration of a virtual function in the derived class must exactly match the way the function is defined in the base.That exception applies to virtuals that return a reference (or pointer) to a type that is itself a base class. A virtual function in a derived class can return a reference (or pointer) to a class that is publicly derived from the type returned by the base-class function.

For example, the Item_base class might define a virtual function that returned an  Item_base*. If it did, then the instance defined in the  Bulk_item class could be defined to return either an  Item_base* or a  Bulk_item*.


By default, function calls in C++ do not use dynamic binding. To trigger dynamic binding, two conditions must be met: First, only member functions that are specified as virtual can be dynamically bound. By default, member functions are not virtual; nonvirtual functions are not dynamically bound.Second, the call must be made through a reference or a pointer to a base-class type.

Because every derived object contains a base part, we can bind a base-type reference to the base-class part of a derived object. We can also use a pointer to base to point to a derived object:

     // function with an Item_base reference parameter
     double print_total(const Item_base&, size_t);
     Item_base item;           // object of base type
     // ok: use pointer or reference to Item_base to refer to an Item_base object
     print_total(item, 10);    // passes reference to an Item_base object
     Item_base *p = &item;     // p points to an Item_base object

     Bulk_item bulk;           // object of derived type
     // ok: can bind a pointer or reference to Item_base to a Bulk_item object
     print_total(bulk, 10);    // passes reference to the Item_base part of bulk
     p = &bulk;                // p points to the Item_base part of bulk

This code uses the same base-type pointer to point to an object of the base type and to an object of the derived type. It also calls a function that expects a reference to the base type, passing an object of the base-class type and also passing an object of the derived type. Both uses are fine, because every derived object has a base part.

Because we can use a base-type pointer or reference to refer to a derived-type object, when we use a base-type reference or pointer, we don't know the type of the object to which the pointer or reference is bound: A base-type reference or pointer might refer to an object of base type or an object of derived type. Regardless of which actual type the object has, the compiler treats the object as if it is a base type object.Treating a derived object as if it were a base is safe, because every derived object has a base subobject. Also, the derived class inherits the operations of the base class, meaning that any operation that might be performed on a base object is available through the derived object as well.

NOTE:The crucial point about references and pointers to base-class types is that the  static type,the type of the reference or pointer, which is knowable at compile time and the  dynamic type,the type of the object to which the pointer or reference is bound, which is knowable only at run time may differ.


The fact that the static and dynamic types of references and pointers can differ is the cornerstone of how C++ supports polymorphism.


In some cases, we want to override the virtual mechanism and force a call to use a particular version of a virtual function. We can do so by using the scope operator:

Item_base *baseP = &derived;
// calls version from the base class regardless of the dynamic type of baseP
double d = baseP->Item_base::net_price(42);


If a base class defines a static member there is only one such member defined for the entire hierarchy.Regardless of the number of classes derived from the base class, there exists a single instance of each static member.static members obey normal access control: If the member is private in the base class, then derived classes have no access to it. Assuming the member is accessible, we can access the static member either through the base or derived class. As usual, we can use either the scope operator or the dot or arrow member access operators.

struct Base {
         static void statmem(); // public by default
};
struct Derived : Base {
         void f(const Derived&);
};
void Derived::f(const Derived &derived_obj)
{
        Base::statmem();      // ok: Base defines statmem
        Derived::statmem();   // ok: Derived in herits statmem
        // ok: derived objects can be used to access static from base
        derived_obj.statmem();     // accessed through Derived object
        statmem();                 // accessed through this class
}

15.3 -- Conversions and Inheritance

As we've seen, every derived object contains a base part, which means that we can execute operations on a derived object as if it were a base object. Because a derived object is also a base, there is an automatic conversion from a reference to a derived type to a reference to its base type(s). That is, we can convert a reference to a derived object to a reference to its base subobject and likewise for pointers.

Base-type objects can exist either as independent objects or as part of a derived object. Therefore, a base object might or might not be part of a derived object.As a result, there is no (automatic) conversion from reference (or pointer) to base to reference (or pointer) to derived.

The situation with respect to conversions of objects (as opposed to references or pointers) is more complicated. Although we can usually use an object of a derived type to initialize or assign an object of the base type, there is no direct conversion from an object of a derived type to an object of the base type.


Like an inherited member function, the conversion from derived to base may or may not be accessible. Whether the conversion is accessible depends on the access label specified on the derived class' derivation.


15.4 -- Constructors and Copy Control

Derived constructors are affected by the fact that they inherit from another class. Each derived constructor initializes its base class in addition to initializing its own data members.


A derived-class synthesized default constructor differs from a nonderived constructor in only one way: In addition to initializing the data members of the derived class, it also initializes the base part of its object. The base part is initialized by the default constructor of the base class.

For our Bulk_item class, the synthesized default constructor would execute as follows:

1.Invoke the Item_base default constructor, which initializes the isbn member to the empty string and the price member to zero.
2.Initialize the members of Bulk_item using the normal variable initialization rules, which means that the qty and discount members would be uninitialized.


A class may initialize only its own immediate base class.


Classes that contain only data members of class type or built-in types other than pointers usually can use the synthesized operations. No special control is required to copy, assign, or destroy such members. Classes with pointer members often need to define their own copy control to manage these members.


If a derived class defines its own copy constructor, that copy constructor usually should explicitly use the base-class copy constructor to initialize the base part of the object:

class Base { /* ... */ };
class Derived: public Base {
     public:
         // Base::Base(const Base&) not invoked automatically
         Derived(const Derived& d):
              Base(d) /* other member initialization */ { /*... */ }
};


The destructor works differently from the copy constructor and assignment operator:The derived destructor is never responsible for destroying the members of its base objects. The compiler always implicitly invokes the destructor for the base part of a derived object. Each destructor does only what is necessary to clean up its own members.

class Derived: public Base {
     public:
         // Base::~Base invoked automatically
         ~Derived()    { /* do what it takes to clean up derived members */ }
};

Objects are destroyed in the opposite order from which they are constructed: The derived destructor is run first, and then the base-class destructors are invoked, walking back up the inheritance hierarchy.


Of the copy-control members, only the destructor should be defined as virtual. Constructors cannot be defined as virtual. Constructors are run before the object is fully constructed. While the constructor is running, the object's dynamic type is not complete.


NOTE: If a virtual is called from inside a constructor or destructor, then the version that is run is the one defined for the type of the constructor or destructor itself.


15.5 -- Class Scope under Inheritance

A derived-class member with the same name as a member of the base class hides direct access to the base-class member.


A member function with the same name in the base and derived class behaves the same way as a data member: The derived-class member hides the base-class member within the scope of the derived class. The base member is hidden,even if the prototypes of the functions differ.


Key Concept: Name Lookup and Inheritance

Understanding how function calls are resolved is crucial to understanding inheritance hierarchies in C++. The following four steps are followed:

1.Start by determining the static type of the object, reference, or pointer through which the function is called.
2. Look for the function in that class. If it is not found, look in the immediate base class and continue up the chain of classes until either the function is found or the last class is searched. If the name is not found in the class or its enclosing base classes, then the call is in error.
3. Once the name is found, do normal type-checking to see if this call is legal given the definition that was found.

4.Assuming the call is legal, the compiler generates code. If the function is virtual and the call is through a reference or pointer, then the compiler generates code to determine which version to run based on the dynamic type of the object. Otherwise, the compiler generates code to call the function directly.


15.6 -- Pure Vitrual Functions

Defining a virtual as pure indicates that the function provides an interface for sub-sequent types to override but that the version in this class will never be called.

An attempt to create an object of an abstract base class is a compile-time error.


A class containing (or inheriting) one or more pure virtual functions is anabstract base class. We may not create objects of an abstract type except as parts of objects of classes derived from the abstract base.





  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值