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

📄 mc6.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 5 页
字号:
<A NAME="p266"></A><P><A NAME="dingp79"></A>
True, most pure virtual functions are never implemented, but pure virtual destructors are a special case. They <I>must</I> be implemented, because they are called whenever a derived class destructor is invoked. Furthermore, they often perform useful tasks, such as releasing resources (see <A HREF="./MC3_FR.HTM#5292" TARGET="_top">Item 9</A>) or logging messages. Implementing pure virtual functions may be uncommon in general, but for pure virtual destructors, it's not just common, it's <NOBR>mandatory.)<SCRIPT>create_link(79);</SCRIPT>
</NOBR></P><A NAME="32151"></A>
<P><A NAME="dingp80"></A>
You may have noticed that this discussion of assignment through base class pointers is based on the assumption that concrete base classes like <CODE>Animal</CODE> contain data members. If there are no data members, you might point out, there is no problem, and it would be safe to have a concrete class inherit from a second, dataless, concrete <NOBR>class.<SCRIPT>create_link(80);</SCRIPT>
</NOBR></P><A NAME="33163"></A>
<P><A NAME="dingp81"></A>
One of two situations applies to your data-free would-be concrete base class: either it might have data members in the future or it might not. If it might have data members in the future, all you're doing is postponing the problem until the data members are added, in which case you're merely trading short-term convenience for long-term grief (see also <A HREF="#5373">Item 32</A>). Alternatively, if the base class should truly never have any data members, that sounds very much like it should be an abstract class in the first place. What use is a concrete base class without <NOBR>data?<SCRIPT>create_link(81);</SCRIPT>
</NOBR></P><A NAME="33267"></A>
<P><A NAME="dingp82"></A>
Replacement of a concrete base class like <CODE>Animal</CODE> with an abstract base class like <CODE>AbstractAnimal</CODE> yields benefits far beyond simply making the behavior of <CODE>operator=</CODE> easier to understand. It also reduces the chances that you'll try to treat arrays polymorphically, the unpleasant consequences of which are examined in <A HREF="./MC1_FR.HTM#84818" TARGET="_top">Item 3</A>. The most significant benefit of the technique, however, occurs at the design level, because replacing concrete base classes with abstract base classes forces you to explicitly recognize the existence of useful abstractions. That is, it makes you create new abstract classes for useful concepts, even if you aren't aware of the fact that the useful concepts <NOBR>exist.<SCRIPT>create_link(82);</SCRIPT>
</NOBR></P>

<A NAME="32222"></A>

<P><A NAME="dingp83"></A>If you have two concrete classes C1 and C2 and you'd like C2 to publicly inherit from C1, you should transform that two-class hierarchy into a three-class hierarchy by creating a new abstract class A and having both C1 and C2 publicly inherit from <NOBR>it:<SCRIPT>create_link(83);</SCRIPT>
</NOBR></P>

<SPAN ID="Image3of1" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_266A1.GIF" BORDER=0></SPAN>
<SPAN ID="Image3of2" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_266A2.GIF" BORDER=0></SPAN>
<SPAN ID="Image3of3" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_266A3.GIF" BORDER=0></SPAN>
<SPAN ID="Image3of4" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_266A4.GIF" BORDER=0></SPAN>
<SPAN ID="Image3of5" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_266A5.GIF" BORDER=0></SPAN>

<SPAN ID="Image3of6" STYLE="position: relative; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_266A5.GIF" BORDER=0></SPAN>

<A NAME="32250"></A>
<A NAME="p267"></A><P><A NAME="dingp84"></A>
The primary value of this transformation is that it forces you to identify the abstract class A. Clearly, C1 and C2 have something in common; that's why they're related by public inheritance (see <A HREF="../EC/EC6_FR.HTM#6914" TARGET="_top">Item E35</A>). With this transformation, you must identify what that something is. Furthermore, you must formalize the something as a class in C++, at which point it becomes more than just a vague something, it achieves the status of a formal <I>abstraction</I>, one with well-defined member functions and well-defined <NOBR>semantics.<SCRIPT>create_link(84);</SCRIPT>
</NOBR></P><A NAME="32252"></A>
<P><A NAME="dingp85"></A>
All of which leads to some worrisome thinking. After all, every class represents <I>some</I> kind of abstraction, so shouldn't we create two classes for every concept in our hierarchy, one being abstract (to embody the abstract part of the abstraction) and one being concrete (to embody the object-generation part of the abstraction)? No. If you do, you'll end up with a hierarchy with too many classes. Such a hierarchy is difficult to understand, hard to maintain, and expensive to compile. That is not the goal of object-oriented <NOBR>design.<SCRIPT>create_link(85);</SCRIPT>
</NOBR></P><A NAME="32253"></A>
<P><A NAME="dingp86"></A>
The goal is to identify <I>useful</I> abstractions and to force them &#151; and only them &#151; into existence as abstract classes. But how do you identify useful abstractions? Who knows what abstractions might prove useful in the future? Who can predict who's going to want to inherit from <NOBR>what?<SCRIPT>create_link(86);</SCRIPT>
</NOBR></P><A NAME="32254"></A>
<P><A NAME="dingp87"></A>
Well, I don't know how to predict the future uses of an inheritance hierarchy, but I do know one thing: the need for an abstraction in one context may be coincidental, but the need for an abstraction in more than one context is usually meaningful. Useful abstractions, then, are those that are needed in more than one context. That is, they correspond to classes that are useful in their own right (i.e., it is useful to have objects of that type) and that are also useful for purposes of one or more derived <NOBR>classes.<SCRIPT>create_link(87);</SCRIPT>
</NOBR></P><A NAME="32255"></A>
<P><A NAME="dingp88"></A>
This is precisely why the transformation from concrete base class to abstract base class is useful: it forces the introduction of a new abstract class only when an existing concrete class is about to be used as a base class, i.e., when the class is about to be (re)used in a new context. Such abstractions are <I>useful</I>, because they have, through demonstrated need, shown themselves to be <NOBR>so.<SCRIPT>create_link(88);</SCRIPT>
</NOBR></P><A NAME="33376"></A>
<P><A NAME="dingp89"></A>
The first time a concept is needed, we can't justify the creation of both an abstract class (for the concept) and a concrete class (for the objects corresponding to that concept), but the second time that concept is needed, we <I>can</I> justify the creation of both the abstract and the concrete classes. The transformation I've described simply mechanizes this process, and in so doing it forces designers and programmers to represent explicitly those abstractions that are useful, even if the de<A NAME="p268"></A>signers and programmers are not consciously aware of the useful concepts. It also happens to make it a lot easier to bring sanity to the behavior of assignment <NOBR>operators.<SCRIPT>create_link(89);</SCRIPT>
</NOBR></P><A NAME="32256"></A>
<P><A NAME="dingp90"></A>
Let's consider a brief example. Suppose you're working on an application that deals with moving information between computers on a network by breaking it into packets and transmitting them according to some protocol. All we'll consider here is the class or classes for representing packets. We'll assume such classes make sense for this <NOBR>application.<SCRIPT>create_link(90);</SCRIPT>
</NOBR></P><A NAME="32257"></A>
<P><A NAME="dingp91"></A>
Suppose you deal with only a single kind of transfer protocol and only a single kind of packet. Perhaps you've heard that other protocols and packet types exist, but you've never supported them, nor do you have any plans to support them in the future. Should you make an abstract class for packets (for the concept that a packet represents) as well as a concrete class for the packets you'll actually be using? If you do, you could hope to add new packet types later without changing the base class for packets. That would save you from having to recompile packet-using applications if you add new packet types. But that design requires two classes, and right now you need only one (for the particular type of packets you use). Is it worth complicating your design now to allow for future extension that may never take <NOBR>place?<SCRIPT>create_link(91);</SCRIPT>
</NOBR></P><A NAME="32258"></A>
<P><A NAME="dingp92"></A>
There is no unequivocally correct choice to be made here, but experience has shown it is nearly impossible to design good classes for concepts we do not understand well. If you create an abstract class for packets, how likely are you to get it right, especially since your experience is limited to only a single packet type? Remember that you gain the benefit of an abstract class for packets only if you can design that class so that future classes can inherit from it without its being changed in any way. (If it needs to be changed, you have to recompile all packet clients, and you've gained <NOBR>nothing.)<SCRIPT>create_link(92);</SCRIPT>
</NOBR></P><A NAME="33789"></A>
<P><A NAME="dingp93"></A>
It is unlikely you could design a satisfactory abstract packet class unless you were well versed in many different kinds of packets and in the varied contexts in which they are used. Given your limited experience in this case, my advice would be not to define an abstract class for packets, adding one later only if you find a need to inherit from the concrete packet <NOBR>class.<SCRIPT>create_link(93);</SCRIPT>
</NOBR></P><A NAME="32260"></A>
<P><A NAME="dingp94"></A>
The transformation I've described here is <I>a</I> way to identify the need for abstract classes, not <I>the</I> way. There are many other ways to identify good candidates for abstract classes; books on object-oriented analysis are filled with them. It's not the case that the only time you should introduce abstract classes is when you find yourself wanting to have a concrete class inherit from another concrete class. However, the desire <A NAME="p269"></A>to relate two concrete classes by public inheritance is usually indicative of a need for a new abstract <NOBR>class.<SCRIPT>create_link(94);</SCRIPT>
</NOBR></P><A NAME="33438"></A>
<P><A NAME="dingp95"></A>
As is often the case in such matters, brash reality sometimes intrudes on the peaceful ruminations of theory. Third-party C++ class libraries are proliferating with gusto, and what are you to do if you find yourself wanting to create a concrete class that inherits from a concrete class in a library to which you have only read <NOBR>access?<SCRIPT>create_link(95);</SCRIPT>
</NOBR></P><A NAME="33838"></A>
<P><A NAME="dingp96"></A>
You can't modify the library to insert a new abstract class, so your choices are both limited and <NOBR>unappealing:<SCRIPT>create_link(96);</SCRIPT>
</NOBR></P>
<UL><A NAME="33839"></A>
<A NAME="dingp97"></A><LI>Derive your concrete class from the existing concrete class, and put up with the assignment-related problems we examined at the beginning of this Item. You'll also have to watch out for the array-related pitfalls described in <A HREF="./MC1_FR.HTM#84818" TARGET="_top">Item 3</A>.<SCRIPT>create_link(97);</SCRIPT>

<A NAME="33214"></A>
<A NAME="dingp98"></A><LI>Try to find an abstract class higher in the library hierarchy that does most of what you need, then inherit from that class. Of course, there may not be a suitable class, and even if there is, you may have to duplicate a lot of effort that has already been put into the implementation of the concrete class whose functionality you'd like to extend.<SCRIPT>create_link(98);</SCRIPT>

<A NAME="33215"></A>
<A NAME="dingp99"></A><LI>Implement your new class in terms of the library class you'd like to inherit from (see Items <A HREF="../EC/EC6_FR.HTM#7424" TARGET="_top">E40</A> and <A HREF="../EC/EC6_FR.HTM#21052" TARGET="_top">E42</A>). For example, you could have an object of the library class as a data member, then reimplement the library class's interface in your new class:<SCRIPT>create_link(99);</SCRIPT>

<A NAME="57904"></A>
<UL><PRE>
class Window {                      // this is the library class
public:
virtual void resize(int newWidth, int newHeight);
virtual void repaint() const;
<A NAME="8194"></A>
int width() const;
int height() const;
};
<A NAME="57913"></A>
class SpecialWindow {               // this is the class you
public:                             // wanted to have inherit
  ...                               // from Window
<A NAME="51391"></A>
  // pass-through implementations of nonvirtual functions
  int width() const { return w.width(); }
  int height() const { return w.height(); }
<A NAME="51412"></A>
  // new implementations of "inherited" virtual functions
  virtual void resize(int newWidth, int newHeight);
  virtual void repaint() const;
<A NAME="57897"></A>
private:
    Window w;
};
</PRE>
</UL><A NAME="51362"></A>
<A NAME="p270"></A><A NAME="dingp100"></A>This strategy requires that you be prepared to update your class each time the library vendor updates the class on which you're dependent. It also requires that you be willing to forgo the ability to redefine virtual functions declared in the library class, because you can't redefine virtual functions unless you inherit them.<SCRIPT>create_link(100);</SCRIPT>

<A NAME="33847"></A>
<A NAME="dingp101"></A><LI>Make do with what you've got. Use the concrete class that's in the library and modify your software so that the class suffices. Write non-member functions to provide the functionality you'd like to add to the class, but can't. The resulting software may not be as clear, as efficient, as maintainable, or as extensible as you'd like, but at least it will get the job done.<SCRIPT>create_link(101);</SCRIPT>

</UL>
<A NAME="33216"></A>
<P><A NAME="dingp102"></A>
None of these choices is particularly attractive, so you have to apply some engineering judgment and choose the poison you find least unappealing. It's not much fun, but life's like that sometimes. To make things easier for yourself (and the rest of us) in the future, complain to the vendors of libraries whose designs you find wanting. With luck (and a lot of comments from clients), those designs will improve as time goes <NOBR>on.<SCRIPT>create_link(102);</SCRIPT>
</NOBR></P><A NAME="33910"></A>
<P><A NAME="dingp103"></A>
Still, the general rule remains: non-leaf classes should be abstract. You may need to bend the rule when working with outside libraries, but in code over which you have control, adherence to it will yield dividends in the form of increased reliability, robustness, comprehensibility, and extensibility throughout your <NOBR>software.<SCRIPT>create_link(103);</SCRIPT>
</NOBR></P>
<!-- SectionName="M34: Combining C and C++ in the same program" -->
<A NAME="33950"></A>
<DIV ALIGN="CENTER"><FONT SIZE="-1">Back to <A HREF="#10947">Item 33: Make non-leaf classes abstract</A> &nbsp;&nbsp;<BR>&nbsp;&nbsp;Continue to <A HREF="#5473">Item 35: Familiarize yourself with the language standard</A></FONT></DIV>
<P><A NAME="dingp104"></A><font ID="mititle">Item 34: &nbsp;Understand how to combine C++ and C in the same program.</font><SCRIPT>create_link(104);</SCRIPT>
</P>

<A NAME="72194"></A><A NAME="76665"></A>

<P><A NAME="dingp105"></A>
In many ways, the things you have to worry about when making a program out of some components in C++ and some in C are the same as those you have to worry about when cobbling together a C program out of object files produced by more than one C compiler. There is no way to combine such files unless the different compilers agree on implementation-dependent features like the size of <CODE>int</CODE>s and <CODE>double</CODE>s, the mechanism by which parameters are passed from caller to callee, and whether the caller or the callee orchestrates the passing. These pragmatic aspects of mixed-compiler software development are quite properly ignored by <NOBR><FONT COLOR="#FF0000" SIZE="-2"><B>&deg;</B></FONT><A HREF="http://www.awl.com/cseng/cgi-bin/cdquery.pl?name=cstandard" onMouseOver="self.status='C++ Language Standard Home Page'; return true" onMouseOut="self.status = self.defaultStatus" target="_top">language</NOBR> standardization efforts</A>, so the only reliable way to know that object files from compiler A and compiler B can be safely combined in a program is to obtain assurances from the vendors of A and B that their products produce compatible output. This is as true for programs made up of C++ and C as it is for all-C++ or all-C programs, so before you try to mix C++ and C in the same program, make sure your C++ and C compilers generate compatible object <NOBR>files.<SCRIPT>create_link(105);</SCRIPT>
</NOBR></P><A NAME="36594"></A>
<A NAME="p271"></A>
<P><A NAME="dingp106"></A>
Having done that, there are four other things you need to consider: name mangling, initialization of statics, dynamic memory allocation, and data structure <NOBR>compatibility.<SCRIPT>create_link(106);</SCRIPT>
</NOBR></P>

<P><A NAME="dingp107"></A><font ID="mhtitle">Name Mangling</font><SCRIPT>create_link(107);</SCRIPT>
</P>

<A NAME="36595"></A>
<P><A NAME="dingp108"></A>
Name mangling, as you may know, is the process through which your C++ compilers give each function in your program a unique name. In C, this process is unnecessary, because you can't overload function names, but nearly all C++ programs have at least a few functions with the same name. (Consider, for example, the iostream library, which declares several versions of <CODE>operator&lt;&lt;</CODE> and <CODE>operator&gt;&gt;</CODE>.) Overloading is incompatible with most linkers, because linkers generally take a dim view of multiple functions with the same name. Name mangling is a concession to the realities of linkers; in particular, to the fact that linkers usually insist on all function names being <NOBR>unique.<SCRIPT>create_link(108);</SCRIPT>
</NOBR></P><A NAME="36644"></A>
<P><A NAME="dingp109"></A>
As long as you stay within the confines of C++, name mangling is not likely to concern you. If you have a function name <CODE>drawLine</CODE> that a compiler mangles into <CODE>xyzzy</CODE>, you'll always use the name <CODE>drawLine</CODE>, and you'll have little reason to care that the underlying object files happen to refer to <CODE>xyzzy</CODE>.<SCRIPT>create_link(109);</SCRIPT>
</P><A NAME="36681"></A>
<P><A NAME="dingp110"></A>
It's a different story if <CODE>drawLine</CODE> is in a C library. In that case, your C++ source file probably includes a header file that contains a declaration like <NOBR>this,<

⌨️ 快捷键说明

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