📄 ch05.htm
字号:
members into a displayable string. In order to enhance performance, a design modification is made: Instead of the three integers, a single <tt>string</tt> now holds the date representation. Had class <tt>DateTime</tt> relied on the internal implementation of <tt>Date</tt>, it would have had to be modified as well. But because it can access <tt>Date</tt>'s data members only through access methods, all that is required is a small change in the <tt>Date::Day()</tt> member function. Please note that accessor methods are usually inlined anyway, so their use does not incur additional runtime overhead.</p><h3> <a name="Heading12">Declaring Virtual Base Class Destructors</a></h3><p>A base class needs to have its destructor declared <tt>virtual</tt>. In doing so, you ensure that the correct destructor is always called, even in the following case:</p><pre><tt>class Base</tt><tt>{</tt><tt>private:</tt><tt> char *p;</tt><tt>public:</tt><tt> Base() { p = new char [200]; }</tt><tt> ~ Base () {delete [] p; } //non virtual destructor, bad</tt><tt>};</tt><tt>class Derived : public Base</tt><tt>{</tt><tt>private:</tt><tt> char *q;</tt><tt>public:</tt><tt> Derived() { q = new char[300]; }</tt><tt> ~Derived() { delete [] q; }</tt><tt> //...</tt><tt>};</tt><tt>void destroy (Base & b) </tt><tt>{ </tt><tt> delete &b; </tt><tt>}</tt><tt>int main()</tt><tt>{</tt><tt> Base *pb = new Derived(); //200 + 300 bytes allocated</tt><tt> //... meddle with pb</tt><tt> destroy (*pb); //OOPS! only the destructor of Base is called</tt><tt> //were Base's destructor virtual, the correct destructor would be called</tt><tt> return 0;</tt><tt>}</tt></pre><h3> <a name="Heading13">Virtual Member Functions</a></h3><p>Virtual member functions enable subclasses to extend or override the behavior of a base class. Deciding which members in a class can be overridden by a derived class is not a trivial issue. A class that overrides a <tt>virtual</tt> member function is only committed to adhere to the prototype of the overridden member function -- not to its implementation. A common mistake is to declare all member functions as <tt>virtual</tt> "just in case". In this respect, C++ makes a clear-cut distinction between abstract classes that provide pure interfaces as opposed to base classes that provide implementation as well as an interface.</p><h4> Extending A Virtual Function in A Derived Class</h4><p>There are cases in which you want a derived class to extend a virtual function defined in its base class rather than override it altogether. It can be done quite easily in the following way:</p><pre><tt>class shape</tt><tt>{</tt><tt> //...</tt><tt>public:</tt><tt> virtual void draw();</tt><tt> virtual void resize(int x, int y) { clearscr(); /*...*/ }};</tt><tt>class rectangle: public shape</tt><tt>{</tt><tt> //...</tt><tt>public: </tt><tt> virtual void resize (int x, int y)</tt><tt> {</tt><tt> shape::resize(x, y); //explicit call to the base's virtual function</tt><tt> //add functionality</tt><tt> int size = x*y;</tt><tt> //...</tt><tt> }</tt><tt>};</tt></pre><p>The overriding function in a derived class should invoke an overridden function of its base class using its fully-qualified name.</p><h4> Changing Access Specification of A Virtual Function</h4><p>The access specification of a <tt>virtual</tt> member function that is defined in a base class can be changed in a derived class. For example</p><pre><tt>class Base</tt><tt>{</tt><tt>public:</tt><tt> virtual void Say() { cout<<"Base";}</tt><tt>};</tt><tt>class Derived : public Base</tt><tt>{</tt><tt>private: //access specifier changed; legal but not a good idea</tt><tt> void Say() {cout <<"Derived";} // overriding Base::Say()</tt><tt>};</tt></pre><p>Although this is legal, it does not work as expected when pointers or references are used; a pointer or reference to <tt>Base</tt> can also be assigned to any object that is publicly derived from <tt>Base</tt>:</p><pre><tt>Derived d;</tt><tt>Base *p = &d;</tt><tt>p->Say(); //OK, invokes Derived::Say()</tt></pre><p>Because the actual binding of a virtual member function is postponed to runtime, the compiler cannot detect that a nonpublic member function will be called; it assumes that <tt>p</tt> points to an object of type <tt>Base</tt>, in which <tt>Say()</tt> is a public member. As a rule, do not change the access specification of a virtual member function in a derived class.</p><h4> Virtual Member Functions Should Not Be Private</h4><p></p><p>As you saw previously, it is customary to extend virtual functions in a derived class by first invoking the base class's version of that function; then extend it with additional functionality. This can't be done when a virtual function is declared <tt>private</tt>. </p><h3> <a name="Heading14">Abstract Classes and Interfaces</a></h3><p>An abstract class is one that has at least one <i>pure virtual member function, </i>that is, a non-implemented placeholder that must be implemented by its derived class. Instances of an abstract class cannot be created because it is intended to serve as a design skeleton for concrete classes that are derived from it, and not as an independent object. See the following example:</p><pre><tt>class File //abstract class; serves as interface</tt><tt>{</tt><tt>public:</tt><tt> int virtual open() = 0; //pure virtual</tt><tt> int virtual close() = 0; //pure virtual </tt><tt>};</tt><tt>class diskFile: public File</tt><tt>{</tt><tt>private:</tt><tt> string filename;</tt><tt> //...</tt><tt>public:</tt><tt> int open() {/*...*/}</tt><tt> int close () {/*...*/}</tt><tt>};</tt></pre><h3> <a name="Heading15">Use Derivation Instead of Type-Fields</a></h3><p>Suppose that you have to implement an internationalization helper class that manages the necessary parameters of every natural language that is currently supported by a word processor. A naive implementation might rely on type-fields to indicate the specific language that is currently being used (for example, the interface language in which menus are displayed).</p><pre><tt>class Fonts {/*...*/};</tt><tt>class Internationalization</tt><tt>{</tt><tt>private:</tt><tt> Lang lg; //type field</tt><tt> FontResthisce fonts</tt><tt>public:</tt><tt> enum Lang {English, Hebrew, Danish}</tt><tt> Internationalization(Lang lang) : lg(lang) {};</tt><tt> Loadfonts(Lang lang);</tt><tt>};</tt></pre><p>Every modification in <tt>Internationalization</tt> affects all its users, even when they are not supposed to be affected. When adding support for a new language, the users of the already-supported languages have to recompile (or download, which is worse) the new version of the class. Moreover, as time goes by and support for new languages is added, the class becomes bigger and more difficult to maintain, and it tends to contain more bugs. A much better design approach is to use derivation instead of type-fields. For example</p><pre><tt>class Internationalization //now a base class</tt><tt>{</tt><tt>private:</tt><tt> FontResthisce fonts</tt><tt>public:</tt><tt> Internationalization ();</tt><tt> virtual int Loadfonts();</tt><tt> virtual void SetDirectionality();</tt><tt>};</tt><tt>class English : public Internationalization</tt><tt>{</tt><tt>public:</tt><tt> English();</tt><tt> Loadfonts() { fonts = TimesNewRoman; }</tt><tt> SetDirectionality(){}//do nothing; default: left to right</tt><tt>};</tt><tt>class Hebrew : public Internationalization</tt><tt>{</tt><tt>public:</tt><tt> Hebrew();</tt><tt> Loadfonts() { fonts = David; }</tt><tt> SetDirectionality() { directionality = right_to_left;}</tt><tt>};</tt></pre><p>Derivation simplifies class structure and localizes the changes that are associated with a specific language to its corresponding class without affecting others. </p><h3> <a name="Heading16">Overloading A Member Function Across Class Boundaries</a></h3><p>A class is a namespace. The scope for overloading a member function is confined to a class but not to its derived classes. Sometimes the need arises to overload the same function in its class as well as in a class that is derived from it. However, using an identical name in a derived class merely hides the base class's function, rather than overloading it. Consider the following:</p><pre><tt>class B</tt><tt>{</tt><tt>public:</tt><tt> void func();</tt><tt>};</tt><tt>class D : public B</tt><tt>{</tt><tt>public:</tt><tt> void func(int n); //now hiding B::f, not overloading it</tt><tt>};</tt><tt>D d;</tt><tt>d.func();//compilation error. B::f is invisible in d;</tt><tt>d.func(1); //OK, D::func takes an argument of type int</tt></pre><p>In order to overload -- rather than hide -- a function of a base class, the function name of the base class has to be injected explicitly into the namespace of the derived class by a <i>using declaration</i>. For example</p><pre><tt>class D : public B</tt><tt>{</tt><tt>using B::func; // inject the name of a base member into the scope of D</tt><tt>public:</tt><tt> void func(int n); // D now has two overloaded versions of func()</tt><tt>};</tt><tt>D d;</tt><tt>d.func ( ); // OK</tt><tt>d.func ( 10 ); // OK</tt></pre><h3> <a name="Heading17">Deciding Between Inheritance and Containment</a></h3><p>When designing a class hierarchy, you often face a decision between inheritance, or <i>is-a</i>, and containment, or <i>has-a</i>, relation. The choice is not always immediately apparent. Assume that you are designing a <tt>Radio</tt> class, and you already have the following classes implemented for you in some library: <tt>Dial</tt> and <tt>ElectricAppliance</tt>. It is obvious that <tt>Radio</tt> is derived from <tt>ElectricAppliance</tt>. However, it is not so obvious that <tt>Radio</tt> is also derived from <tt>Dial</tt>. In such cases, check whether there is always a 1:1 relationship between the two. Do all radios have one and only one dial? They don't. A radio can have no dials at all -- a transmitter/receiver adjusted to a fixed frequency, for example. Furthermore, it might have more than one dial -- FM and AM dials. Hence, your <tt>Radio</tt> class needs to be designed to have Dial(s) rather than being<i> </i>derived from <tt>Dial</tt>. Note that the relationship between <tt>Radio</tt> and <tt>ElectricAppliance</tt> is 1:1 and corroborates the decision to derive <tt>Radio</tt> from <tt>ElectricAppliance</tt>.</p><h3> <a name="Heading18">The Holds-a Relation</a></h3><p>Ownership defines the responsibility for the creation and the destruction of an object. An object is an owner of some other resource if and only if it has the responsibility for both constructing and destroying it. In this respect, an object that contains another object also owns it because its constructor is responsible for the invocation of the embedded object's constructor. Likewise, its destructor is responsible for invoking the embedded object's destructor. This is the well-known has-a relationship. A similar relationship is <i>holds-a</i>. It is distinguished from has-a by one factor: ownership. A class that indirectly contains -- by means of a reference or a pointer -- another object that is constructed and destroyed independently is said to hold that object. Here's an example:</p><pre><tt>class Phone {/*...*/};</tt><tt>class Dialer {/*...*/};</tt>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -