⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 ei34.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 2 页
字号:
  class Date;                    // class declaration
</PRE>
</UL><A NAME="18978"></A>
<UL><PRE>
  Date returnADate();            // fine &#151; no definition
  void takeADate(Date d);        // of Date is needed
</PRE>
</UL><A NAME="18983"></A>
<A NAME="dingp19"></A>Of course, pass-by-value is generally a bad idea (see <A HREF="./EI22_FR.HTM#6133" TARGET="_top">Item 22</A>), but if you find yourself forced to use it for some reason, there's still no justification for introducing unnecessary compilation <NOBR>dependencies.<SCRIPT>create_link(19);</SCRIPT>
</NOBR></P>
<A NAME="19010"></A>
<P><A NAME="dingp20"></A>
If you're surprised that the declarations for <CODE>returnADate</CODE> and <CODE>takeADate</CODE> compile without a definition for <CODE>Date</CODE>, join the club; so was I. It's not as curious as it seems, however, because if anybody <EM>calls</EM> those functions, <CODE>Date</CODE>'s definition must be visible. Oh, I know what you're thinking: why bother to declare functions that nobody calls? Simple. It's not that <EM>nobody</EM> calls them, it's that <EM>not everybody</EM> calls them. For example, if you have a library containing hundreds of function declarations (possibly spread over several namespaces &#151; see <A HREF="./EI28_FR.HTM#6429" TARGET="_top">Item 28</A>), it's unlikely that every client calls every function. By moving the onus of providing class definitions (via <A NAME="p148"></A><CODE>#include</CODE> directives) from your header file of function <EM>declarations</EM> to clients' files containing function <EM>calls</EM>, you eliminate artificial client dependencies on type definitions they don't really <NOBR>need.<SCRIPT>create_link(20);</SCRIPT>
</NOBR></P><A NAME="18944"></A>
<B><A NAME="dingp21"></A><LI>Don't <CODE>#include</CODE> header files in your header files unless your headers won't compile without them.</B> Instead, manually declare the classes you need, and let clients of your header files <CODE>#include</CODE> the additional headers necessary to make <EM>their</EM> code compile. A few clients may grumble that this is inconvenient, but rest assured that you are saving them much more pain than you're inflicting. In fact, this technique is so well-regarded, it's enshrined in the standard C++ library (see <A HREF="./EI49_FR.HTM#8392" TARGET="_top">Item 49</A>); the header <CODE>&lt;iosfwd&gt;</CODE> contains declarations (and only declarations) for the types in the iostream library.<SCRIPT>create_link(21);</SCRIPT>

</UL></P>
<A NAME="6835"></A>
<P><A NAME="dingp22"></A>
Classes like <CODE>Person</CODE> that contain only a pointer to an unspecified implementation are often called <EM>Handle</EM> classes or <EM>Envelope</EM> classes. (In the former case, the classes they point to are called <EM>Body</EM> classes; in latter case, the pointed-to classes are known as <I>Letter</I> classes.) Occasionally, you may hear people refer to such classes as <EM>Cheshire Cat</EM> classes, an allusion to the cat in <EM>Alice in Wonderland</EM> that could, when it chose, leave behind only its smile after the rest of it had <NOBR>vanished.<SCRIPT>create_link(22);</SCRIPT>
</NOBR></P>
<A NAME="77079"></A>
<P><A NAME="dingp23"></A>
Lest you wonder how Handle classes actually do anything, the answer is simple: they forward all their function calls to the corresponding Body classes, and those classes do the real work. For example, here's how two of <CODE>Person</CODE>'s member functions would be <NOBR>implemented:<SCRIPT>create_link(23);</SCRIPT>
</NOBR></P>
<A NAME="77082"></A>
<UL><PRE>
#include "Person.h"          // because we're implementing
                             // the Person class, we must
                             // #include its class definition
<A NAME="77091"></A>
#include "PersonImpl.h"      // we must also #include
                             // PersonImpl's class definition,
                             // otherwise we couldn't call
                             // its member functions. Note
                             // that PersonImpl has <i>exactly</i>
                             // the same member functions as
                             // Person &#151; their interfaces
                             // are identical
<A NAME="77092"></A>
Person::Person(const string&amp; name, const Date&amp; birthday,
               const Address&amp; addr, const Country&amp; country)
{
  impl = new PersonImpl(name, birthday, addr, country);
}
<A NAME="77095"></A>
<A NAME="p149"></A>string Person::name() const
{
  return impl-&gt;name();
}
</PRE>
</UL></P><A NAME="77080"></A>
<P><A NAME="dingp24"></A>
Note how the <CODE>Person</CODE> constructor calls the <CODE>PersonImpl</CODE> constructor (implicitly, by using <CODE>new</CODE> &#151; see Items <A HREF="./EI5_FR.HTM#1869" TARGET="_top">5</A> and <A HREF="../MEC/MI8_FR.HTM#33985" TARGET="_top">M8</A>) and how <CODE>Person::name</CODE> calls <CODE>PersonImpl::name</CODE>. This is important. Making <CODE>Person</CODE> a handle class doesn't change what <CODE>Person</CODE> does, it just changes where it does <NOBR>it.<SCRIPT>create_link(24);</SCRIPT>
</NOBR></P>
<A NAME="6839"></A>
<P><A NAME="dingp25"></A>
An alternative to the Handle class approach is to make <CODE>Person</CODE> a special kind of abstract base class called a <I>Protocol class</I>. By definition, a Protocol class has no implementation; its <I>raison d'&ecirc;tre</I> is to specify an interface for derived classes (see <A HREF="./EI36_FR.HTM#7007" TARGET="_top">Item 36</A>). As a result, it typically has no data members, no constructors, a virtual destructor (see <A HREF="./EI14_FR.HTM#223029" TARGET="_top">Item 14</A>), and a set of pure virtual functions that specify the interface. A Protocol class for <CODE>Person</CODE> might look like <NOBR>this:<SCRIPT>create_link(25);</SCRIPT>
</NOBR></P></P>
<A NAME="6852"></A>
<UL><PRE>class Person {
public:
  virtual ~Person();
</PRE>
</UL><A NAME="6853"></A>
<UL><PRE>  virtual string name() const = 0;
  virtual string birthDate() const = 0;
  virtual string address() const = 0;
  virtual string nationality() const = 0;
};
</PRE>
</UL><A NAME="6854"></A>
<P><A NAME="dingp26"></A>
Clients of this <CODE>Person</CODE> class must program in terms of <CODE>Person</CODE> pointers and references, because it's not possible to instantiate classes containing pure virtual functions. (It is, however, possible to instantiate classes <EM>derived</EM> from <CODE>Person</CODE> &#151; see below.) Like clients of Handle classes, clients of Protocol classes need not recompile unless the Protocol class's interface is <NOBR>modified.<SCRIPT>create_link(26);</SCRIPT>
</NOBR></P>
<A NAME="6857"></A>
<P><A NAME="dingp27"></A>
Of course, clients of a Protocol class must have <I>some</I> way of creating new objects. They typically do it by calling a function that plays the role of the constructor for the hidden (derived) classes that are actually instantiated. Such functions go by several names (among them <EM>factory functions</EM> and <EM>virtual constructors</EM>), but they all behave the same way: they return pointers to dynamically allocated objects that support the Protocol class's interface (see also <A HREF="../MEC/MI25_FR.HTM#5341" TARGET="_top">Item M25</A>). Such a function might be declared like this,<SCRIPT>create_link(27);</SCRIPT>

<A NAME="19091"></A>
<UL><PRE>// makePerson is a "virtual constructor" (aka, a "factory
// function") for objects supporting the Person interface
Person*
  makePerson(const string&amp; name,         // return a ptr to
             const Date&amp; birthday,       // a new Person
             const Address&amp; addr,        // initialized with
             const Country&amp; country);    // the given params
</PRE>
</UL><A NAME="19096"></A>
<A NAME="dingp28"></A><A NAME="p150"></A>and used by clients like this:<SCRIPT>create_link(28);</SCRIPT>

<A NAME="19097"></A>
<UL><PRE>string name;
Date dateOfBirth;
Address address;
Country nation;
<A NAME="19192"></A>
...
<A NAME="19188"></A>
// create an object supporting the Person interface
Person *pp = makePerson(name, dateOfBirth, address, nation);
<A NAME="19101"></A>
...
<A NAME="19194"></A>
cout  &lt;&lt; pp-&gt;name()              // use the object via the
      &lt;&lt; " was born on "         // Person interface
      &lt;&lt; pp-&gt;birthDate()
      &lt;&lt; " and now lives at "
      &lt;&lt; pp-&gt;address();
<A NAME="19103"></A>
...
<A NAME="19104"></A>
delete pp;                       // delete the object when
                                 // it's no longer needed
</PRE>
</UL></P><A NAME="19086"></A>
<P><A NAME="dingp29"></A>
Because functions like <CODE>makePerson</CODE> are closely associated with the Protocol class whose interface is supported by the objects they create, it's good style to declare them <CODE>static</CODE> inside the Protocol <NOBR>class:<SCRIPT>create_link(29);</SCRIPT>
</NOBR></P>
<A NAME="19106"></A>
<UL><PRE>class Person {
public:
  ...  						// as above
</PRE>
</UL><A NAME="19107"></A>
<UL><PRE>
// makePerson is now a member of the class
static Person * makePerson(const string&amp; name,
                           const Date&amp; birthday,
                           const Address&amp; addr,
                           const Country&amp; country);
};
</PRE>
</UL><A NAME="19108"></A>
<P><A NAME="dingp30"></A>
This avoids cluttering the global namespace (or any other namespace) with lots of functions of this nature (see also <A HREF="./EI28_FR.HTM#6429" TARGET="_top">Item 28</A>).<SCRIPT>create_link(30);</SCRIPT>
</P>
<A NAME="19131"></A>
<P><A NAME="dingp31"></A>
At some point, of course, concrete classes supporting the Protocol class's interface must be defined and real constructors must be called. That all happens behind the scenes inside the implementation files for the virtual constructors. For example, the Protocol class <CODE>Person</CODE> might have a concrete derived class <CODE>RealPerson</CODE> that provides implementations for the virtual functions it <NOBR>inherits:<SCRIPT>create_link(31);</SCRIPT>
</NOBR></P>
<A NAME="19134"></A>
<UL><PRE>class RealPerson: public Person {
public:
  RealPerson(const string&amp; name, const Date&amp; birthday,
             const Address&amp; addr, const Country&amp; country)
  :  name_(name), birthday_(birthday),
     address_(addr), country_(country)
  {}
</PRE>
</UL><A NAME="6863"></A>
<UL><PRE><A NAME="p151"></A>  virtual ~RealPerson() {}
</PRE>
</UL><A NAME="22586"></A>
<UL><PRE>
  string name() const;          // implementations of
  string birthDate() const;     // these functions are not
  string address() const;       // shown, but they are
  string nationality() const;   // easy to imagine
</PRE>
</UL><A NAME="22620"></A>
<UL><PRE>private:
  string name_;
  Date birthday_;
  Address address_;
  Country country_;
};
</PRE>
</UL><A NAME="19178"></A>
<P><A NAME="dingp32"></A>
Given <CODE>RealPerson</CODE>, it is truly trivial to write <CODE>Person::makePerson</CODE>:<SCRIPT>create_link(32);</SCRIPT>
</P>
<A NAME="19179"></A>
<UL><PRE>Person * Person::makePerson(const string&amp; name,
                            const Date&amp; birthday,
                            const Address&amp; addr,
                            const Country&amp; country)
{
  return new RealPerson(name, birthday, addr, country);
}
</PRE>
</UL><A NAME="6868"></A>
<P><A NAME="dingp33"></A>
<CODE>RealPerson</CODE> demonstrates one of the two most common mechanisms for implementing a Protocol class: it inherits its interface specification from the Protocol class (<CODE>Person</CODE>), then it implements the functions in the interface. A second way to implement a Protocol class involves multiple inheritance, a topic explored in <A HREF="./EI43_FR.HTM#7778" TARGET="_top">Item 43</A>.<SCRIPT>create_link(33);</SCRIPT>
</P>
<A NAME="223129"></A>
<P><A NAME="dingp34"></A>
Okay, so Handle classes and Protocol classes decouple interfaces from implementations, thereby reducing compilation dependencies between files. Cynic that you are, I know you're waiting for the fine print. "What does all this hocus-pocus cost me?" you mutter. The answer is the usual one in Computer Science: it costs you some speed at runtime, plus some additional memory per <NOBR>object.<SCRIPT>create_link(34);</SCRIPT>
</NOBR></P>
<A NAME="6880"></A>
<P><A NAME="dingp35"></A>
In the case of Handle classes, member functions have to go through the implementation pointer to get to the object's data. That adds one level of indirection per access. And you must add the size of this implementation pointer to the amount of memory required to store each object. Finally, the implementation pointer has to be initialized (in the Handle class's constructors) to point to a dynamically allocated implementation object, so you incur the overhead inherent in dynamic memory allocation (and subsequent deallocation) &#151; see <A HREF="./EI10_FR.HTM#1986" TARGET="_top">Item 10</A>.<SCRIPT>create_link(35);</SCRIPT>
</P>
<A NAME="6881"></A>
<P><A NAME="dingp36"></A>
For Protocol classes, every function call is virtual, so you pay the cost of an indirect jump each time you make a function call (see Items <A HREF="./EI14_FR.HTM#223029" TARGET="_top">14</A> and <A HREF="../MEC/MI24_FR.HTM#41284" TARGET="_top">M24</A>). Also, objects derived from the Protocol class must contain a virtual pointer (again, see Items <A HREF="./EI14_FR.HTM#223029" TARGET="_top">14</A> and <A HREF="../MEC/MI24_FR.HTM#41284" TARGET="_top">M24</A>). This pointer may in<A NAME="p152"></A>crease the amount of memory needed to store an object, depending on whether the Protocol class is the exclusive source of virtual functions for the <NOBR>object.<SCRIPT>create_link(36);</SCRIPT>
</NOBR></P>
<A NAME="6890"></A>
<P><A NAME="dingp37"></A>
Finally, neither Handle classes nor Protocol classes can get much use out of inline functions. All practical uses of inlines require access to implementation details, and that's the very thing that Handle classes and Protocol classes are designed to avoid in the first <NOBR>place.<SCRIPT>create_link(37);</SCRIPT>
</NOBR></P>
<A NAME="6891"></A>
<P><A NAME="dingp38"></A>
It would be a serious mistake, however, to dismiss Handle classes and Protocol classes simply because they have a cost associated with them. So do virtual functions, and you wouldn't want to forgo those, would you? (If so, you're reading the wrong book.) Instead, consider using these techniques in an evolutionary manner. Use Handle classes and Protocol classes during development to minimize the impact on clients when implementations change. Replace Handle classes and Protocol classes with concrete classes for production use when it can be shown that the difference in speed and/or size is significant enough to justify the increased coupling between classes. Someday, we may hope, tools will be available to perform this kind of transformation <NOBR>automatically.<SCRIPT>create_link(38);</SCRIPT>
</NOBR></P>
<A NAME="6892"></A>
<P><A NAME="dingp39"></A>
A skillful blending of Handle classes, Protocol classes, and concrete classes will allow you to develop software systems that execute efficiently and are easy to evolve, but there is a serious disadvantage: you may have to cut down on the long breaks you've been taking while your programs <NOBR>recompile.<SCRIPT>create_link(39);</SCRIPT>
</NOBR></P>

<DIV ALIGN="CENTER"><FONT SIZE="-1">Back to <A HREF="./EI33_FR.HTM" TARGET="_top">Item 33: Use inlining judiciously.</A> &nbsp;&nbsp;<BR>&nbsp;&nbsp;Continue to <A HREF="./EINHERFR.HTM" TARGET="_top">Inheritance and Object-Oriented Design</A></FONT></DIV>

</BODY>
</HTML>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -