!!!Chapter 15 Object-Oriented Programming (15.3 ~ 15.5)

15.3 Conversions and Inheritance

There is automatic conversion from a reference to a derived type to a reference to its base type. (or pointer)

There is no automatic conversion from reference to base to reference to derived. (or pointer)

For conversions of objects (not reference or pointer), we can usually use an object of a derived type to initialize or assign an object of the base type. But there is no direct conversion from an object of a derived type to an object of the base type.

15.3.1 Derived-to-Base Conversions

Conversion to a Reference is Not the Same as Converting an Object

When we pass an object to a function expecting a reference, the reference is bound directly to that object. Although it appears that we are passing an object, the argument is actually a reference to that object. The object itself is not copied and the conversion doesn't change the derived-type object in any way.

When we pass a derived object to a function expecting a base-type object, then the base portion of that derived object is copied into the parameter.

We need understand the difference between converting a derived object to a base-type reference and using a derived object to initialize or assign a base-type object.

Using a Derived Object to Initialize or Assign a Base Object

When we use a derived-type object to initialize or assign a base object, there are two possibilities.

1. the base class might explicitly define what it means to copy or assign an object of the derived type to an object of the base type:

class Derived;
class Base {
public:
    Base (const Derived&);
    Base &operator= (const Derived&);
};
This method is uncommon

2. base class usually define (explicitly or implicitly) their own copy constructor and assignment operator. There members take a parameter that is a (const) reference to the base type. Because there is a conversion from reference to derived to reference to base, these copy-control members can be used to initialize or assign a base object from a derived object:

Item_base item;
Bulk_item bulk;
Item_base item(bulk);
item = bulk;
When we call the Item_base copy constructor or assignment operator on an object of type Bulk_item, the following steps happen;

1. The Bulk_item object is converted to a reference to Item_base, which means only that an Item_base reference is bound to the Bulk_item object.

2. The reference is passed as an argument to the copy constructor or assignment operator.

3. Those operators use the Item_base part of Bulk_item to initialize and assign, respectively, the members of the Item_base on which the constructor or assignment was called.

4. Once the operator completes, the object is an Item_base. It contains a copy of the Item_base part of the Bulk_item from which it was initialized or assigned, but the Bulk_item parts of the argument are ignored.

Accessibility of Derived-to-Base Conversion

???

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.

To determine whether the conversion to base is accessible, consider whether a public member of the base class would be accessible. If so, the conversion is accessible; otherwise, it is not.

15.3.2 Conversions from Base to Derived

There is no automatic conversion from the base class to a derived class:

Item_base base;
Bulk_item* bulkp = &base;    //error
Bulk_item& bulkRef = base;   //error
Bulk_item bulk = base;       //error
The restriction on converting from base to derived exists even when a base pointer or reference is actually bound to a derived object:

Bulk_item bulk;
Item_base *itemP = &bulk;      // OK: dynamic type is Bulk_item
Bulk_item *bulkP = itemp;      // error:cannot convert base to derived
The compiler looks only at the static types of the pointer or reference to determine whether a conversion is legal.

If we are sure the conversion from base to derived is safe, we can use static_case or dynamic_cast!

15.4 Constructors and Copy Control

When we construct, copy, assign, or destroy an object of derived type, we also construct, copy, assign, or destroy those base-class subobjects.

Constructors and the copy-control members are not inherited; each class defines its own constructors and copy-control members. If we don't define the constructor or copy-control members for the derived class, it will use the synthesized one.

15.4.1 Base-Class Constructors and Copy Control

Constructors and copy control for base classes that are not themselves a derived class are largely unaffected by inheritance. Like any other member, constructors can be made protected or private.

15.4.2 Derived-Class Constructors

Each derived constructor initializes its base class in addition to initializing its own data members.

The Synthesized Derived-Class Default Constructor

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.

Defining a Default Constructor

Since Bulk_item has members of built-in type, we should define our own default constructor:

class Bulk_item : public Item_base {
public:
    Bulk_item () : min_qty (0), discount (0.0) {}
};
The constructor initializer also implicitly invokes the Item_base default constructor to initialize its base-class part.

First, the Item_base part is initialized using the Item_base default constructor. After the Item_base constructor finishes, the members of the Bulk_item part are initialized, and the body of the constructor is executed.

Passing Arguments to a Base-Class Constructor

A derived constructor indirectly initializes the members it inherits by including its base class in its constructor initializer list:

class Bulk_item : public Item_base {
public:
    Bulk_item (const std::string& book, double sales_price, std::size_t qty = 0, 
               double disc_rate = 0.0) : Item_base(book, sales_price), min_qty(qty), 
               discount(disc_rate) {}
};
The constructor initializer list supplies initial values for a class' base class and members. It does not specify the order in which those initializations are done. The base class is initialized first and then the members of the derived class are initialized in the order in which they are declared.

Using Default Arguments in a Derived Constructor

Only an Immediate Base Class May Be Initialized

A class may initialize only its own immediate base class. An immediate base class is the class named in the derivation list.

If A > B > C. we can use C to initialize B, and use B to initialize A.

E.G. P 583

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Refactoring

Refactoring involves redesigning a class hierarchy to move operations and/or data from one class to another. Refactoring happens most often when classes are redesigned to ad new functionality or handle other changes in that application's requirements.

Refactoring is common in OO application. It is noteworthy that even though we changed the inheritance hierarchy, code taht uses the Bulk_item or Item_base classes would not need to change. However, when classes are refactored, or changed in any other way, any code that uses those classes must be recompiled.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Respecting The Base-Class Interface

15.4.3 Copy Control and Inheritance

A derived class may use the synthesized copy-control members described in Chapter 13.

Whether a class needs to define the copy-control members depends entirely on the class' own direct members. A base class might define its own copy control while the derived uses the synthesized versions or vice versa.

Defining a Derived Copy Constructor

If a derived class explicitly defines its own copy constructor or assignment operator, that definition completely overrides the defaults. The copy constructor and assignment operator for inherited classes are responsible for copying or assigning their base-class components as well as the members in the class itself.

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 initialize/
};
Base(d) will convert the derived object d to a reference to its base part and invokes the base-class copy constructor.

If we omit the Base(d), we will use the Base default constructor to initialize the base part! That means the base part of the object is use default value while the derived part is copied from the target object.

Derived-Class Assignment Operator

As copy constructor, if the derived class defines its own assignment operator, then that operator must assign the base part explicitly:

// Base::operator= (const Base &) not invoke automatically
Derived &Derived::operator=(const Derived &rhs)
{
    if (this != &rhs ) {
        Base::operator=(rhs);
        // do something
    }
    return *this;
} 
The assignment operator must, as always, guard against self-assignment.

Derived-Class Destructor

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 invokes aautomatically
    ~Derived() {...}
};
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.

15.4.4 Virtual Destructors

When we delete a pointer that points to a dynamically allocated object, we might delete a pointer to the base type that actually points to a derived object.

If we delete a pointer to base, then the base-class destructor is run and the members of the base are cleaned up. If the object is a derived type, then the behavior is undefined.To ensure that the proper destructor is run, the destructor must be virtual in the base class:

class Item_base {
public:
    // no work, but virtual destructor needed
    // if base pointer that points to a derived object is ever deleted
    virtual ~Item_base() {}
};
Normally, we should define constructor and destructor for every class!

Like other virtual functions, the virtual nature of the destructor is inherited.

If the destructor in the root class of the hierarchy is virtual, then the derived destructors will be virtual as well.

A derived destructor will be virtual whether the class explicitly defines its destructor or uses the synthesized destructor.

The root class of an inheritance hierarchy should define a virtual destructor even if the destructor has no work to do.

Constructor and Assignment Are Not Virtual

Constructors cannot be defined as virtual.

Making the assignment operator virtual is likely to be confusing because a virtual function must have the same parameter type in base and derived classes.

15.4.5 Virtuals in Constructors and Destructors

The type of an object during construction and destruction affects the binding of virtual functions.

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 desctructor itself.

15.5 Class Scope under Inheritance

Under inheritance, the scope of the derived class is nested within the scope of its base classes.

It is this hierarchical nesting of class scopes under inheritance that allows the members of the base class to be accessed directly as if they are members of the derived class:

Bulk_item bulk;
cout << bulk.book();
book will be resolved as follows:

1. bulk is an object of the Bulk_item class. The Bulk_item class is searched for book. That name is not found.

2. Because Bulk_Item is derived from Item_Base, the Item_Base class is searched next. The name is found.

15.5.1 Name Lookup Happens at Compile Times

The static type of an object, reference , or pointer determines the actions that the object can perform:

class Disc_item : public Item_base {
public:
    std::pair<size_t, double> discount_policy() const
    { return std::make_pair(quantity, discount); }
    //...
};

We can access discount_policy only through an object, pointer, or reference of type Disc_item or a class derived from Disc_item.

15.5.2 Name Collisions and Inheritance

We need to care which actual class contains the member when a base- and derived-class member share the same name.

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

struct Base {
    Base() : mem(0) {}
protected:
    int mem;
};
struct Derived : Base {
    Derived (int i) : mem(i) {}
    int get_mem() { return mem; }
protected:
    int mem;        // hides mem in the base
};

Derived d(42);
cout << d.get_mem() << endl;       //prints 42

Using the Scope Operator to Access Hidden Members

We can access a hidden base-class member by using the scope operator:

struct Derived : Base {
    int get_base_mem() {return Base::mem; }
};
When designing a derived class, it is best to avoid name collisions with members of the base class whenever possible.

15.5.3 Scope and Member Functions

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

struct Base {
    int memfcn();
};

struct Derived : Base {
    int memfcn(int);      //hides memfcn in Base
};
Derived d; Base b; 
b.memfcn();          //ok
d.memfcn(10);        //ok
d.memfcn();          //error: memfcn with no argument is hidden
d.Base::memfcn();    //ok: calls Base::memfcn
Recall that functions declared in a local scope do not overload functions defined at global scope. Similarly, functions defined in a derived class do not overload members defined in the base. When the function is called through a derived object, the arguments must match a version of the function defined in the derived class. The base class functions are considered only if the derived does not define the function at all.

Overloaded Functions

If the derived class redefines any of the overloaded members, then only the one(s) redefined in the derived class are accessible through the derived type.

If a derived class wants to make all the overloaded versions available through its type, then it must either redefine all of them or none of them.

If the derived class only wants to redefine some of the overloaded functions, it can use theusingdeclaration. P.G 594

15.5.4 Virtual Functions and Scope

Virtual functions must have the same prototype in the base and derived classes, Otherwise, there would be no way to call the derived function from a reference or pointer to the base type.

class Base {
public:
    virtual int fcn();
};

class D1 : public Base {
public:
    // hides fcn in the base; this fcn is not virtual
    int fcn(int);      //parameter list differs from Base
    // D1 inherits definition of Base::fcn()
};

class D2 : public D1 {
public:
    int fcn(int);    //nonvirtual function hides D1::fcn(int)
    int fcn();       //redefine virtual fcn from Base
}; 
D1 has two functions named fcn.

D2 redefines both functions that it inherits.

Calling a Hidden Virtual through the Base Class

When we call a function through a base-type reference or pointer, the compiler looks for that function in the base class and ignores the derived classes:

Base bobj;  D1 d1obj;  D2 d2obj;
Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj;
bp1->fcn();    //ok: virtual call, will call Base::fcn
bp2->fcn();    //ok: virtual call, will call Base::fcn
bp3->fcn();    //ok: virtual call, will call D2::fcn 
All three pointers are pointers to the base type, so all three calls are resolved by looking in Base to see if fcn is defined.

How function calls are resolved

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.





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值