您的位置:首页 > 移动开发 > Objective-C

《C++ Primer第五版》读书笔记(13)-Object-Oriented Programming

2014-03-21 18:32 211 查看

15.1 OOP: An Overview


The key ideas in object-oriented programming are data abstraction, inheritance, and dynamic binding. Using data abstraction, we can define classes that separate interface from implementation. Through inheritance, we can define
classes that model the relationships among similar types. Through dynamic binding, we can use objects of these types while ignoring the details of how they differ.

Inheritance
A derived class must specify the class(es) from which it intends to inherit. It does so in a class derivation list, which is a colon followed by a comma-separated list of base classes each of which may have an optional access
specifier:
class Bulk_quote : public Quote { // Bulk_quote inherits from Quote
public:
double net_price(std::size_t) const override;
};

Dynamic Binding
In C++, dynamic binding happens when a virtual function is called through a reference (or a pointer) to a base class.

15.2 Defining Base and Derived Classes

15.2.1 Defining a Base Class

Base classes ordinarily should define a virtual destructor even if the class otherwise has no need for a destructor. The destructor must be virtual if a pointer to a base is ever deleted when
it actually addresses a derived-class object.

Member Functions and Inheritance

The derived class needs to override the definition it inherits from the base class, by providing its own definition. The base class defines as virtual those functions it expects its derived classes to override. When we call a virtual function through a pointer
or reference, the call will be dynamically bound.

The virtual keyword appears only on the declaration inside the class and may not be used on a function definition that appears outside the class body. A function that is declared as virtual in the base class is implicitly virtual in the derived classes as well.

Member functions that are not declared as virtual are resolved at compile time, not run time. Thus, there is no question as to which function to run when we call isbn().

15.2.2 Defining a Derived Class

A derived class must specify from which class(es) it inherits. It does so in its class derivation list, which is a colon followed by a comma-separated list of names of previously defined classes. Each base class name may be preceded by an optional access specifier,
which is one of public, protected, or private.

A derived class must declare each inherited member function it intends to override.

When the derivation is public, the public members of the base class become part of the interface of the derived class as well.

Derived classes frequently, but not always, override the virtual functions that they inherit. If a derived class does not override a virtual from its base, then, like any other member, the derived class inherits the version defined in its base class.

The new standard lets a derived class explicitly note that it intends a member function to override a virtual that it inherits. It does so by specifying override after the parameter list, or after the const or reference qualifier(s) if the member is a const
or reference function.

Derived-Class Objects and the Derived-to-Base Conversion

Because a derived object contains subparts corresponding to its base class(es), we can use an object of a derived type as ifit were an object of its base type(s). In particular, we can bind a base-class reference or pointer to the base-class part of a derived
object.

 


Quote item;  //  object of base type

Bulk_quote bulk;  //  object of derived type

Quote *p = &item;  //  p points to a Quote object

p = &bulk;  //  p points to the Quote part of bulk

Quote &r = bulk;  //  r bound to the Quote part of bulk

This conversion is often referred to as the derived-to-base conversion. As with any other conversion, the compiler will apply the derived-to-base conversion implicitly.

Derived-Class Constructors

Although a derived object contains members that it inherits from its base, it cannot directly initialize those members. Like any other code that creates an object of the base-class type, a derived class must use a base-class
constructor to initialize its base-class part.

The base-class part of an object is initialized, along with the data members of the derived class, during the initialization phase of the constructor.

Bulk_quote(const std::string& book, double p,std::size_t qty, double disc) :Quote(book, p), min_qty(qty), discount(disc) { }

// as before

};

Using Members of the Base Class from the Derived Class
A derived class may access the public and protected members of its base class. Although it can assign to those members, it generally should not do so. Like any other user of the base class, a derived class should respect the interface of its base class
by using a constructor to initialize its inherited members.

Inheritance and static Members

If a base class defines a static member (§7.6, p. 300), there is only one such member defined for the entire hierarchy. Regardless of the number of classes derived from a base class, there exists a single instance of each static member.

Assuming the member is accessible, we can use a static member through either the base or derived:

void Derived::f(const Derived &derived_obj)

{
Base::statmem();  // ok: Base defines statmem
Derived::statmem(); // ok: Derived inherits statmem
// ok: derived objects can be used to access static from base

    derived_obj.statmem(); // accessed through a Derived object

    statmem();  // accessed through this object

}

Declarations of Derived Classes

A derived class is declared like any other class (§7.3.3, p. 278). The declaration contains the class name but does not include its derivation list:

class Bulk_quote : public Quote; // error: derivation list can't appear here

class Bulk_quote;   // ok: right way to declare a derived class

Defination:

class Base { /* ...*/ } ;

class D1: public Base { /* ...*/ };

class D2: public D1 { /* ...*/ };

In this hierarchy, Base is a direct base to D1 and an indirect base to D2.

Preventing Inheritance

Under the new standard, we can prevent a class from being used as a base by following the class name withfinal:

class NoDerivedfinal { /*  */ }; // NoDerived can't be abase class

class Base { /*  */ };

// Last is final; we cannot inherit from Last

class Last final : Base { /*  */ }; //Last can't be a base class

class Bad : NoDerived { /*  */ };  // error: NoDerived is final

class Bad2 : Last { /*  */ };  // error: Last is final

15.2.3 Conversions and Inheritance

Understanding conversions between base and derived classes is essential to understanding how object-oriented programming works in C++.

Classes related by inheritance are an important exception: We can bind a pointer or reference to a base-class type to an object of a type derived from that base class.

The fact that we can bind a reference (or pointer) to a base-class type to a derived object has a crucially important implication: When we use a reference (or pointer) to a base-class type, we don’t know the actual type of the object to which the pointer or
reference is bound. That object can be an object of the base class or it can be an object of a derived class.

Static Type and Dynamic Type

When we use types related by inheritance, we often need to distinguish between the static type of a variable or other expression and the dynamic type of the object that expression represents.

The static type of an expression is always known at compile time—it is the type with which a variable is declared or that an expression yields. The dynamic type is the type of the object in memory that the variable or expression represents. The dynamic type
may not be known until run time.

There Is No Implicit Conversion from Base to Derived ...

Quote base;

Bulk_quote* bulkP = &base;  // error: can't convert base to derived

Bulk_quote& bulkRef = base; // error: can't convert base to derived

The compiler has no way to know (at compile time) that a specific conversion will be safe at run time. The compiler looks only at the static types of the pointer or reference to determine whether a conversion is legal.

If the base class has one or more virtual functions, we can use a dynamic_cast to request a conversion that is checked at run time. Alternatively, in those cases when we know that the conversion from base to derived is safe, we can use a static_cast to override
the compiler.

...and No Conversion between Objects

The automatic derived-to-base conversion applies only for conversions to a reference or pointer type. There is no such conversion from a derived-class type to the base class type. 

Nevertheless, it is often possible to convert an object of a derived class to its base-class type. However, such conversions may not behave as we might want.When we initialize or assign an object of a base type from an object of a derived type, only the base-class
part of the derived object is copied, moved, or assigned. The derived part of the object is ignored.

15.3 Virtual Functions

As we’ve seen, in C++  dynamic binding happens when a virtual member function is called through a reference or a pointer to a base-class type (§15.1, p. 593). Because we don’t know which version of a function is called until
run time, virtual functions must always be defined. Ordinarily, if we do not use a function, we don’t need to supply a definition for that function (§6.1.2, p. 206). However, we must define every virtual function, regardless of whether it is used, because
the compiler has no way to determine whether a virtual function is used.

Calls to Virtual Functions May Be Resolved at Run Time

When a virtual function is called through a reference or pointer, the compiler generates code to decide at run time which function to call. The function that is called is the one that corresponds to the dynamic type of the object bound to that pointer or reference.

Key Concept: Polymorphism in C++

The key idea behind OOP is polymorphism. Polymorphism is derived from a Greek word meaning “many forms.” We speak of types related by inheritance as polymorphic types, because we can use the “many forms” of these types while ignoring the differences among them.
The fact that the static and dynamic types of references and pointers can differ is the cornerstone of how C++ supports polymorphism.

When we call a function defined in a base class through a reference or pointer to the base class, we do not know the type of the object on which that member is executed. The object can be a base-class object or an object of a derived class. If the function
is virtual, then the decision as to which function to run is delayed until run time. The version of the virtual function that is run is the one defined by the type of the object to which the reference is bound or to which the pointer points.

On the other hand, calls to nonvirtual functions are bound at compile time. Similarly, calls to any function (virtual or not) on an object are also bound at compile time. The type of an object is fixed and unvarying—there is nothing we can do to make the dynamic
type of an object differ from its static type. Therefore, calls made on an object are bound at compile time to the version defined by the type of the object.

Virtual Functions in a Derived Class

When a derived class overrides a virtual function, it may, but is not required to, repeat the virtual keyword. Once a function is declared as virtual, it remains virtual in all the derived classes.

With one exception, the return type of a virtual in the derived class also must match the return type of the function from the base class. The exception applies to virtuals that return a reference (or pointer) to types that are themselves related by inheritance.
That is, if D is derived from B, then a base class virtual can return a B* and the version in the derived can return a D*. However, such return types require that the derived-to-base conversion from D to B is accessible.

The final and override Specifiers

It is legal for a derived class to define a function with the same name as a virtual in its base class but with a different parameter list. The compiler considers such a function to be independent from the base-class function. In such cases, the derived version
does not override the version in the base class. In practice, such declarations often are a mistake—the class author intended to override a virtual from the base class but made a mistake in specifying the parameter list.

Finding such bugs can be surprisingly hard. Under the new standard we can specify override on a virtual function in a derived class. Doing so makes our intention clear and (more importantly) enlists the compiler in finding such problems for us. The compiler
will reject a program if a function marked override does not override an existing virtual function:

struct B {

virtual void f1(int) const;

virtual void f2();

void f3();

};

struct D1 : B {
void f1(int) const override; // ok: f1 matches f1 in the base
void f2(int) override; // error: B has no f2(int) function
void f3() override;  // error: f3 not virtual. only a virtual function can be overridden
void f4() override;  // error: B doesn't have a function named f4

}

We can also designate a function as final(感觉在向java学习啊). Any attempt to override a function that has been defined as finalwill be flagged as an error:

struct D2 : B {
// inherits f2() and f3() from B and overrides f1(int)
void f1(int) const final; // subsequent classes can't override f1 (int)

};

struct D3 : D2 {
void f2();  // ok: overrides f2 inherited from the indirect base,B
void f1(int) const; // error: D2 declared f2 as final

};

final and override specifiers appear after the parameter list (including any const or reference qualifiers) and after a trailing return.

Virtual Functions and Default Arguments

Virtual functions that have default arguments should use the same argument values in the base and derived classes.

Circumventing the Virtual Mechanism
In some cases, we want to prevent dynamic binding of a call to a virtual function; we want to force the call to use a particular version of that virtual. We can use the scope operator to do so.

Why might we wish to circumvent the virtual mechanism? The most common reason is when a derived-class virtual function calls the version from the base class. If a derived virtual function that intended to call its base-class version omits the scope operator,
the call will be resolved at run time as a call to the derived version itself, resulting in an infinite recursion.

15.4
Abstract Base Classes


Pure Virtual Functions(有没有JAVA的interface的神韵)

Unlike ordinary virtuals, a pure virtual function does not have to be defined. We specify that a virtual function is a pure virtual by writing = 0 in place of a function body.The = 0 may appear only on the declaration of a virtual function in the class body.
Itis worth noting that we can provide a definition for a pure virtual. However, the function body must be defined outside the class. That is, we cannot provide a function body inside the class for a function that is = 0.

class Disc_quote : public Quote {

public:
Disc_quote() = default;
Disc_quote(const std::string& book, double price, std::size_t qty, double disc):Quote(book, price),quantity(qty), discount(disc) { }

double net_price(std::size_t) const = 0;

protected:
std::size_t quantity = 0; //  purchase size for the discount to apply
double discount = 0.0;  //  fractional discount to apply

};

Classes with Pure Virtuals Are Abstract Base Classes

We may not create objects of a type that is an abstract base class.

A Derived Class Constructor Initializes Its Direct Base Class Only

Key Concept: Refactoring

Redesigning programs to collect related parts into a single abstraction, replacing the original code with uses of the new abstraction. Typically, classes are refactored to move data or function members to the highest common point in the hierarchy to avoid code
duplication. Refactoring involves redesigning a class hierarchy to move operations and/or data from one class to another. Refactoring is common in object-oriented applications.

15.5 Access Control and Inheritance
protected Members

The protected specifier can be thought of as a blend of privateand public:

•Like private, protected members are inaccessible to users of the class.

•Like public, protected members are accessible to members and friends of classes derived from this class.

In addition, protectedhas another important property:

•A derived class member or friend may access the protected members of the base class only through a derived object. The derived class has no special access to the protected members of base-class objects.

class Base {

protected:
int prot_mem;  // protected member

};

class Sneaky : public Base  {
friend void clobber(Sneaky&);  // can access Sneaky::prot_mem
friend void clobber(Base&);  // can't access Base::prot_mem
int j;  // j is private by default

};

// ok: clobber can access the private and protected members in Sneaky objects

void clobber(Sneaky &s) { s.j = s.prot_mem = 0; }

// error: clobber can't access the protected members in Base

void clobber(Base &b) { b.prot_mem = 0; }

If derived classes (and friends) could access protected members in a base-class object, then our second version of clobber(that takes a Base&)would be legal. That function is not a friend of Base, yet it would be allowed to change an object of type Base; we
could circumvent the protection provided by protectedfor any class simply by defining a new class along the lines of Sneaky.

To prevent such usage, members and friends of a derived class can access the protected members only in base-class objects that are embedded inside a derived type object; they have no special access to ordinary objects of the base type.

public, private, and protected Inheritance

The purpose of the derivation access specifier is to control the access that users of the derived class—including other classes derived from the derived class—have to the members inherited from Base:

class Base {

public:
void pub_mem();  // public member

protected:
int prot_mem;  // protected member

private:
char priv_mem;  // private member

};

struct Pub_Derv : public Base {
// ok: derived classes can access protected members
int f() { return prot_mem; }
// error: private members are inaccessible to derived classes
char g() { return priv_mem; }

};

struct Priv_Derv : private Base {
// private derivation doesn't affect access in the derived class
int f1() const { return prot_mem; }

}

Pub_Derv d1;  //  members inherited from Base are public

Priv_Derv d2;  //  members inherited from Base are private

d1.pub_mem();  //  ok: pub_mem is public in the derived class

d2.pub_mem();  //  error: pub_mem is private in the derived class

Both Pub_Derv and Priv_Derv inherit the pub_memfunction. When the inheritance is public, members retain their access specification. Thus, d1can call pub_mem. In Priv_Derv, the members of Baseare private; users of that class may not call pub_mem.

The derivation access specifier used by a derived class also controls access from classes that inherit from that derived class:

struct Derived_from_Public : public Pub_Derv {
// ok: Base::prot_mem remains protected in Pub_Derv
int use_base() { return prot_mem; }

};

struct Derived_from_Private : public Priv_Derv {
// error: Base::prot_mem is private in Priv_Derv
int use_base() { return prot_mem; }

};

Protected Inheritance:In protected inheritance, the protected and public members of the base class are protected members of the derived class.

Public inheritance:The public interface of the base class is part of the public interface of the derived class.

Key Concept: Class Design and protected Members

In the absence of inheritance, we can think of a class as having two different kinds of users: ordinary users and implementors. Ordinary users write code that uses objects of the class type; such code can access only the public
(interface) members of the class. Implementors write the code contained in the members and friends of the class. The members and friends of the class can access both the public and private(implementation) sections.

Under inheritance, there is a third kind of user, namely, derived classes. A base class makes protectedthose parts of its implementation that it is willing to let its derived classes use. The protected members remain inaccessible to ordinary user code; private
members remain inaccessible to derived classes and their friends.

Like any other class, a class that is used as a base class makes its interface members public. A class that is used as a base class may divide its implementation into those members that are accessible to derived classes and those that remain accessible only
to the base class and its friends. An implementation member should be protectedif it provides an operation or data that a derived class will need to use in its own implementation. Otherwise, implementation members should be private.

Friendship and Inheritance

Just as friendship is not transitive (§7.3.4, p. 279), friendship is also not inherited. Friendship is not inherited; each class controls access to its members.

Exempting Individual Members

class Base {

public:
std::size_t size() const { return n; }

protected:
std::size_t n;

};

class Derived : private Base {  //  note: private inheritance

public:
// maintain access levels for members related to the size of the object
using Base::size;

protected:
using Base::n;

};

Because Derived uses private inheritance, the inherited members, size and n, are (by default) private members of Derived. The using declarations adjust the accessibility of these members. Users of Derived can access the size member, and classes subsequently
derived from Derived can access n.

Default Inheritance Protection Levels

class Base { /* ...  */ };

struct D1 : Base { /* ...  */ };  // public inheritance by default

class D2 : Base { /* ...  */ };  // private inheritance by default

It is a common misconception to think that there are deeper differences between classes defined using the structkeyword and those defined using class. The only differences are the default access specifier for members and the default derivation access specifier.
There are no other distinctions.

A privately derived class should specify private explicitly rather than rely on the default. Being explicit makes it clear that private inheritance is intended and not an oversight.

15.6 Class Scope under Inheritance

Class Scope under Inheritance

Name Collisions and Inheritance

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

Using the Scope Operator to Use Hidden Members

struct Derived : Base {
int get_base_mem() { return Base::mem; }
// ...

};

Best Practices :Aside from overriding inherited virtual functions, a derived class usually should not reuse names defined in its base class.

As Usual, Name Lookup Happens before Type Checking

If a member in a derived class (i.e., in an inner scope) has the same name as a baseclass member (i.e., a name defined in an outer scope), then the derived member hides the base-class member within the scope of the derived
class. The base member is hidden even if the functions have different parameter lists:

struct Base {
int memfcn();

};

struct Derived : Base {

int memfcn(int);  // hides memfcn in the base

};

Derived d; Base b;

b.memfcn();  //  calls Base::memfcn

d.memfcn(10);  //  calls Derived::memfcn

d.memfcn();  //  error: memfcn with no arguments is hidden

d.Base::memfcn(); //  ok: calls Base::memfcn

Overriding Overloaded Functions

As with any other function, a member function (virtual or otherwise) can be overloaded. A derived class can override zero or more instances of the overloaded functions it inherits. If a derived class wants to make all the overloaded
versions available through its type, then it must override all of them or none of them.

Sometimes a class needs to override some, but not all, of the functions in an overloaded set. It would be tedious in such cases to have to override every base-class version in order to override the ones that the class needs to specialize.

Instead of overriding every base-class version that it inherits, a derived class can provide a using declaration (§15.5, p. 615) for the overloaded member. A using declaration specifies only a name; it may not specify a parameter list. Thus, a using declaration
for a base-class member function adds all the overloaded instances of that function to the scope of the derived class. Having brought all the names into its scope, the derived class needs to define only those functions that truly depend on its type. It can
use the inherited definitions for the others.

15.7 Constructors and Copy Control

15.7.1 Virtual Destructors
The primary direct impact that inheritance has on copy control for a base class is that a base class generally should define a virtual destructor. The destructor needs to be virtual to allow objects in the inheritance hierarchy to be dynamically allocated.

So long as the base class destructor is virtual, when we deletea pointer to base, the correct destructor will be run:

Quote *itemP = new Quote;  //  same static and dynamic type

delete itemP;  //  destructor for Quote called

itemP = new Bulk_quote;  //  static and dynamic types differ

delete itemP;  //  destructor for Bulk_quote called

Destructors for base classes are an important exception to the rule of thumb that if a class needs a destructor, it also needs copy and assignment (§13.1.4, p. 504). A base class almost always needs a destructor, so that it can make the destructor virtual.
If a base class has an empty destructor in order to make it virtual, then the fact that the class has a destructor does not indicate that the assignment operator or copy constructor is also needed.

Virtual Destructors Turn Off Synthesized Move

The fact that a base class needs a virtual destructor has an important indirect impact on the definition of base and derived classes: If a class defines a destructor—even if it uses = default to use the synthesized version—the compiler will not synthesize
a move operation for that class.

15.7.2 Synthesized Copy Control and Inheritance

The synthesized copy-control members in a base or a derived class execute like any other synthesized constructor, assignment operator, or destructor: They memberwise initialize, assign, or destroy the members of the class itself. In addition, these synthesized
members initialize, assign, or destroy the direct base part of an object by using the corresponding operation from the base class.

It is worth noting that it doesn’t matter whether the base-class member is itself synthesized (as is the case in our Quotehierarchy) or has a an user-provided definition. All that matters is that the corresponding member is accessible (§15.5, p. 611) and that
it is not a deleted function.

Base Classes and Deleted Copy Control in the Derived

The way in which a base class is defined can cause a derived-class member to be defined as deleted:

•If the default constructor, copy constructor, copy-assignment operator, or destructor in the base class is deleted or inaccessible (§15.5, p. 612), then the corresponding member in the derived class is defined as deleted, because the compiler can’t use the
base-class member to construct, assign, or destroy the base-class part of the object.

•If the base class has an inaccessible or deleted destructor, then the synthesized default and copy constructors in the derived classes are defined as deleted, because there is no way to destroy the base part of the derived object.

•As usual, the compiler will not synthesize a deleted move operation. If we use = default to request a move operation, it will be a deleted function in the derived if the corresponding operation in the base is deleted or inaccessible, because the base class
part cannot be moved. The move constructor will also be deleted if the base class destructor is deleted or inaccessible.

Move Operations and Inheritance

Because lack of a move operation in a base class suppresses synthesized move for its derived classes, base classes ordinarily should define the move operations if it is sensible to do so. Once it defines its move operations, it must also explicitly define the
copy versions as well.

15.7.3 Derived-Class Copy-Control Members

When a derived class defines a copy or move operation, that operation is responsible for copying or moving the entire object, including base-class members. Unlike the constructors and assignment operators, the destructor is responsible only for destroying the
resources allocated by the derived class. Similarly, the base-class part of a derived object is destroyed automatically.

Defining a Derived Copy or Move Constructor

When we define a copy or move constructor (§13.1.1, p. 496, and §13.6.2, p. 534) for a derived class, we ordinarily use the corresponding base-class constructor to initialize the base part of the object.

class Base { /* ...  */ } ;

class D: public Base {

public:
// by default, the base class default constructor initializes the base part of an object
// to use the copy or move constructor, we must explicitly call that
// constructor in the constructor initializer list
D(const D& d): Base(d)  // copy the base members
/* initializers for members of D*/ { /* ...  */ }
D(D&& d): Base(std::move(d)) // move the base members
/* initializers for members of D*/ { /* ...  */ }

};

Derived-Class Assignment Operator
// Base::operator=(const Base&) is not invoked automatically

D &D::operator=(const D &rhs)

{
Base::operator=(rhs); // assigns the base part
// assign the members in the derived class, as usual,
// handling self-assignment and freeing existing resources as appropriate
return *this;

}

Calls to Virtuals in Constructors and Destructors
If a constructor or destructor calls a virtual, the version that is run is the one corresponding to the type of the constructor or destructor itself.

15.7.4 Inherited Constructors

Under the new standard, a derived class inherits its base-class constructors by providing a using declaration that names its (direct) base class.

class Bulk_quote : public Disc_quote {

public:
using Disc_quote::Disc_quote; // inherit Disc_quote's constructors
double net_price(std::size_t) const;

};

Ordinarily,a using declaration only makes a name visible in the current scope. When applied to a constructor, a usingdeclaration causes the compiler to generate code. The compiler generates a derived constructor corresponding to each constructor in the base.
That is, for each constructor in the base class, the compiler generates a constructor in the derived class that has the same parameter list.

Bulk_quote(const std::string& book, double price, std::size_t qty, double disc):Disc_quote(book, price, qty, disc) { }

Characteristics of an Inherited Constructor

Unlike using declarations for ordinary members, a constructor using declaration does not change the access level of the inherited constructor(s).

Moreover, a using declaration can’t specify explicit or constexpr. If a constructor in the base is explicit or constexpr, the inherited constructor has the same property.

15.8 Containers and Inheritance

When we use a container to store objects from an inheritance hierarchy, we generally must store those objects indirectly. Put(Smart) Pointers, Not Objects, in Containers

vector<shared_ptr<Quote>>basket;

basket.push_back(make_shared<Quote>("0-201-82470-1", 50));

basket.push_back(make_shared<Bulk_quote>("0-201-54848-8", 50, 10, .25));

// calls the version defined by Quote; prints 562.5, i.e., 15 * $50 less the discount

cout << basket.back()->net_price(15) << endl;

Just as we can convert an ordinary pointer to a derived type to a pointer to an base-class type (§15.2.2, p. 597), we can also convert a smart pointer to a derived type to a smart pointer to an base-class type. Thus, make_shared<Bulk_quote>returns a shared_ptr<Bulk_quote>object,
which is converted to shared_ptr<Quote> when we call push_back. As a result, despite appearances, all of the elements of baskethave the same type.

15.9 Key Concept: Inheritance versus Composition

The design of inheritance hierarchies is a complicated topic in its own right and well beyond the scope of this language Primer. However, there is one important design guide that is so fundamental that every programmer should be familiar with it.

When we define a class as publicly inherited from another, the derived class should reflect an “Is A” relationship to the base class. In well-designed class hierarchies, objects of a publicly derived class can be used wherever an object of the base class is
expected.

Another common relationship among types is a “Has A” relationship. Types related by a “Has A” relationship imply membership.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息