📄 ch05.htm
字号:
<tt>class Modem</tt><tt>{</tt><tt>private: </tt><tt> Phone* pline;</tt><tt> Dialer& dialer;</tt><tt>public:</tt><tt> Modem (Phone *pp, Dialer& d) : pline(pp), dialer {}</tt><tt>//Phone and Dialer objects are constructed and destroyed</tt><tt>//independently of Modem</tt><tt>};</tt><tt>void f()</tt><tt>{</tt><tt> Phone phone;</tt><tt> Dialer dialer;</tt><tt> Modem modem(&phone, dialer);</tt><tt> //...use modem</tt><tt>}</tt></pre><p><tt>Modem</tt> uses <tt>Phone</tt> and <tt>Dialer</tt>. However, it is not responsible for constructing or destroying them.</p><p> </p><h3> <a name="Heading19">Empty Classes</a></h3><p>A class that contains no data members and no member functions is an <i>empty class</i>. For example</p><pre><tt>class PlaceHolder {};</tt></pre><p>An empty class can serve as a placeholder for a yet-to-be defined class. Imagine an interface class that serves as a base for other classes; instead of waiting for its full implementation to be completed, it can be used this way in the interim. Additionally, an empty class can also be used as a means of forcing derivation relationship among classes that are not originally descended from one base class. (This is a bottom-up design). Finally, it can be used as a dummy argument to distinguish between overloaded versions of a function. In fact, one of the standard versions of operator <tt>new</tt> (see also Chapter 11, "Memory Management") uses this technique:</p><pre><tt>#include <new></tt><tt>using namespace std;</tt><tt>int main()</tt><tt>{</tt><tt> try</tt><tt> {</tt><tt> int *p = new int[100]; //exception-throwing new</tt><tt> }</tt><tt> catch(bad_alloc & new_failure) {/*..*/}</tt><tt> int *p = new (nothrow) int [100]; // exception-free version of</tt><tt> if (p) </tt><tt> {/*..*/}</tt><tt> return 0;</tt><tt>}</tt></pre><p>The <tt>nothrow</tt> argument is of type <tt>nothrow_t</tt>, which is an empty class by itself.</p><h3> <a name="Heading20">Using structs as A Shorthand for Public Classes</a></h3><p>Traditionally, <tt>structs</tt> serve as data aggregates. However, in C++ a <tt>struct</tt> can have constructors, a destructor, and member functions -- just like a class. The only difference between the two is the default access type: By default, a class has <tt>private</tt> access type to its members and derived objects, whereas a <tt>struct</tt> has <tt>public</tt> access. Consequently, <tt>structs</tt> are sometimes used as shorthand for classes, whose members are all <tt>public</tt>. Abstract classes are a good example of classes that have all public members.</p><pre><tt>#include <cstdio></tt><tt>using namespace std;</tt><tt>struct File //interface class. all members are implicitly public</tt><tt>{</tt><tt> virtual int Read() = 0;</tt><tt> File(FILE *);</tt><tt> virtual ~File() = 0;</tt><tt>};</tt><tt>class TextFile: File //implicit public inheritance; File is a struct</tt><tt>{</tt><tt>private:</tt><tt> string path;</tt><tt>public:</tt><tt> int Flush();</tt><tt> int Read();</tt><tt>};</tt><tt>class UnicodeFile : TextFile //implicit private inheritance</tt><tt>{</tt><tt>public:</tt><tt> wchar_t convert(char c);</tt><tt>};</tt></pre><h3> <a name="Heading21">Friendship</a></h3><p>A class can grant access to its members on a selective basis bydeclaring external classes and functions as friends. A friend has full access to all the grantor's members, including private and protected ones. Friendship is sometimes unjustly criticized for exposing implementation details. However, this is radically different from declaring data members as public because friendship enables the class to declare explicitly which clients can access its members; in contrast, a <tt>public</tt> declaration provides indiscriminate access to a member. Here's an example:</p><pre><tt>bool operator ==( const Date & d1, const Date& d2);</tt><tt>{</tt><tt> return (d1.day == d2.day) &&</tt><tt> (d1.month == d2.month) &&</tt><tt> (d1.year == d2.year);</tt><tt>}</tt><tt>class Date</tt><tt>{</tt><tt> private:</tt><tt> int day, month, year;</tt><tt> public:</tt><tt> friend bool operator ==( const Date & d1, const Date& d2);</tt><tt>};</tt></pre><p>Remember that friendship is not inherited, so nonpublic members of any class that is derived from <tt>Date</tt> are not accessible to operator <tt>==</tt>.</p><h3> <a name="Heading22">Nonpublic Inheritance</a></h3><p>When a derived class inherits from a nonpublic base, the is-a relationship between a derived object and its nonpublic base does not exist. For example:</p><pre><tt>class Mem_Manager {/*..*/};</tt><tt>class List: private Mem_Manager {/*..*/};</tt><tt>void OS_Register( Mem_Manager& mm);</tt><tt>int main()</tt><tt>{</tt><tt> List li;</tt><tt> OS_Register( li ); //compile time error; conversion from</tt><tt> //List & to Mem_Manager& is inaccessible</tt><tt> return 0;</tt><tt>}</tt></pre><p>Class <tt>List</tt> has a private base, <tt>Mem_Manager</tt>, which is responsible for its necessary memory bookkeeping. However, <tt>List</tt> is not a memory manager by itself. Therefore, private inheritance is used to block its misuse. Private inheritance is similar to containment. As a matter of fact, the same effect might have been achieved by making <tt>Mem_Manager</tt> a member of class <tt>List</tt>. <tt>Protected</tt> inheritance is used in class hierarchies for similar purposes.</p><h3> <a name="Heading23">Common Root Class</a></h3><p>In many frameworks and software projects, all classes are forced to be descendants of one common root class, which is usually named <tt>Object</tt>. This design policy prevails in other OO languages such as Smalltalk and Java, whose classes are derived from class <tt>Object</tt> implicitly. However, imitating this in C++ incurs many compromises and potential bugs. It creates artificial kinship among classes that have absolutely nothing in common. Bjarne Stroustrup addresses the issue: "Now what is the common relationship between a smile, the driver of my CD-ROM reader, a recording of Richard Strauss' Don Juan, a line of text, my medical records, and a real-time clock? Placing them all in a single hierarchy when their only shared property is that they are programming artifacts (they are all "objects") is of little fundamental value and can cause confusion." (<i>The C++ Programming Language, 3rd ed.</i>, page 732).</p><p>If you are looking for genericity, that is, if you need an algorithm/container/function that works for every data type, you might find that templates serve you better. Moreover, a common root design policy also forces you to refrain from multiple inheritance entirely because any class that is derived simultaneously from two or more base classes faces the <i>dreadful derivation diamond</i> problem: It embeds more than one base subobject. Finally, the common root class usually serves as a means of implementing exception handling and RTTI, both of which are integral parts of C++ anyway.</p><h3> <a name="Heading24">Forward Declarations</a></h3><p>Consider the following common situation in which classes refer to one another:</p><pre><tt>//file: bank.h</tt><tt>class Report</tt><tt>{</tt><tt>public:</tt><tt> void Output(const Account& account); // compile time error;</tt><tt> // Account is not declared yet</tt><tt>};</tt><tt>class Account</tt><tt>{</tt><tt>public:</tt><tt> void Show() {Report::Output(*this);}</tt><tt>};</tt></pre><p>An attempt to compile this header file causes compilation errors because the compiler does not recognize the identifier <tt>Account</tt> as a class name when class <tt>Report</tt> is compiled. Even if you relocate the declaration of class <tt>Account</tt> and place it before class <tt>Report</tt>, you encounter the same problem: <tt>Report</tt> is referred to from <tt>Account</tt>. For that purpose, a <i>forward declaration</i> is required. A forward declaration instructs the compiler to hold off reporting such errors until the entire source file has been scanned. For example </p><pre><tt>//file: bank.h</tt><tt>class Acount; //forward declaration</tt><tt>class Report</tt><tt>{</tt><tt>public:</tt><tt> void Output(const Account& account); //fine</tt><tt>};</tt><tt>class Account</tt><tt>{</tt><tt>private:</tt><tt> Report rep;</tt><tt>public:</tt><tt> void Show() {Report::Output(*this);}</tt><tt>};</tt></pre><p>The <tt>forward</tt> declaration in the beginning of the source file enables class <tt>Report</tt> to refer to class <tt>Account</tt> even though its definition has not yet been seen. Note that only references and pointers can refer to a <tt>forward</tt>-declared class.</p><h3> <a name="Heading25">Local Classes</a></h3><p>A class can be declared inside a function or a block. In such cases, it is not visible from anywhere else, and instances thereof can only be created within the scope in which it is declared. This can be useful if you need to hide an ancillary object that is not to be accessible or used anywhere else. For example</p><pre><tt>void f(const char *text)</tt><tt>{</tt><tt> class Display //local helper class; visible only in f()</tt><tt> {</tt><tt> const char *ps;</tt><tt> public:</tt><tt> Display(const char *t) : ps(t) {}</tt><tt> ~Display() { cout<<ps; }</tt><tt> };</tt><tt>Display ucd(text); //local object of type Display</tt><tt>}</tt></pre><p>A local class has no linkage.</p><h3> <a name="Heading26">Multiple Inheritance</a></h3><p>Multiple inheritance was introduced to C++ in 1989. It isn't an exaggeration to say that it has been the most controversial feature ever added to C++. The opponents of multiple inheritance maintain that it adds an unnecessary complexity to the language, that every design model that uses multiple inheritance can be modeled with single inheritance, and that it complicates compiler writing. Of the three arguments, only the third one is true. Multiple inheritance is optional. Designers who feel that they can make do without it are never forced to use it. The added level of complexity that is ascribed to multiple inheritance is not a compelling argument either because the same criticism is applicable to other language features such as templates, operator overloading, exception handling, and so on.</p><p>Multiple inheritance enables the designer to create objects that are closer to their real-world reality. A fax modem card is essentially a modem and a fax combined in one. Similarly, a <tt>fax_modem</tt> class that is publicly derived from both <tt>fax</tt> and <tt>modem</tt> represents the concept of a fax/modem better than a single inheritance model does. But the most compelling argument in favor of multiple inheritance is that some designs cannot be realized without it. For example, implementing the <tt>Observer</tt> pattern in Java is nearly impossible because Java lacks multiple inheritance ("Java vs. C++ -- A Critical Comparison," <i>C++ Report</i>, January 1997). <tt>Observer</tt> is not the only pattern that relies on multiple inheritance -- <tt>Adapter</tt> and <tt>Bridge</tt> also do (ibid.).</p><h4> Using Multiple Inheritance to Conjoin Features</h4><p>Derived classes can combine the functionality of several base classes simultaneously, by means of multiple inheritance. Trying to achieve the same effect using single inheritance can be very difficult, to say the least. For example</p><pre><tt>class Persistent //abstract base class used by</tt><tt>{</tt><tt> //all persistence-supporting objects</tt><tt>public:</tt><tt> virtual void WriteObject(void *pobj, size_t sz) = 0;</tt><tt> virtual void* ReadO
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -