📄 ec4.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN" "http://www.w3.org/TR/REC-html40/frameset.dtd">
<HTML LANG="EN">
<HEAD>
<title>Effective C++, 2E | Chapter 4: Classes and Functions: Design and Declaration</TITLE>
<LINK REL=STYLESHEET HREF=../INTRO/ECMEC.CSS>
<SCRIPT LANGUAGE="Javascript" SRC="../JAVA/COOKIE.JS"></SCRIPT>
<SCRIPT LANGUAGE="Javascript">var imagemax = 2; setCurrentMax(2);</SCRIPT>
<SCRIPT LANGUAGE="Javascript" SRC="../JAVA/IMGDOC.JS"></SCRIPT>
<SCRIPT LANGUAGE="Javascript" SRC="../JAVA/NSIMGDOC.JS"></SCRIPT>
<SCRIPT LANGUAGE="Javascript" SRC="../JAVA/DINGBATS.JS"></SCRIPT>
<SCRIPT LANGUAGE="Javascript">
var dingbase = "EC4_DIR.HTM";
var dingtext = "EC++ Class/Func Design, P";
if (self == top) {
top.location.replace(dingbase + this.location.hash);
}
</SCRIPT>
</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000" ONLOAD="setResize()">
<!-- one image occurring in Chapter 4 -->
<!-- SectionName="EC++ Chapter Intro: Design of Classes/Functions" -->
<A NAME="p77"></A><A NAME="2326"></A><A NAME="2327"></A><A NAME="2328"></A>
<DIV ALIGN="CENTER"><FONT SIZE="-1">Back to <A HREF="./EC3_FR.HTM#2264" TARGET="_top">Item 17: Check for assignment to self in operator=.</A>
<BR>Continue to <A HREF="#17774">Item 18: Strive for class interfaces that are complete and minimal.</A></FONT></DIV>
<P><A NAME="dingp1"></A><FONT ID="egtitle">Classes and Functions: Design and Declaration</FONT><SCRIPT>create_link(1);</SCRIPT>
</P>
<P><A NAME="dingp2"></A>
Declaring a new class in a program creates a new type: class design is <I>type</I> design. You probably don't have much experience with type design, because most languages don't offer you the opportunity to get any practice. In C++, it is of fundamental importance, not just because you can do it if you want to, but because you <I>are</I> doing it every time you declare a class, whether you mean to or <NOBR>not.<SCRIPT>create_link(2);</SCRIPT>
</NOBR></P>
<A NAME="2329"></A>
<P><A NAME="dingp3"></A>
Designing good classes is challenging because designing good types is challenging. Good types have a natural syntax, an intuitive semantics, and one or more efficient implementations. In C++, a poorly thought out class definition can make it impossible to achieve any of these goals. Even the performance characteristics of a class's member functions are determined as much by the declarations of those member functions as they are by their <NOBR>definitions.<SCRIPT>create_link(3);</SCRIPT>
</NOBR></P>
<A NAME="2330"></A>
<P><A NAME="dingp4"></A>
How, then, do you go about designing effective classes? First, you must understand the issues you face. Virtually every class requires that you confront the following questions, the answers to which often lead to constraints on your <NOBR>design:<SCRIPT>create_link(4);</SCRIPT>
</NOBR></P>
<UL><A NAME="2331"></A>
<A NAME="dingp5"></A><LI><I>How should objects be created and destroyed?</I> How this is done strongly influences the design of your constructors and destructor, as well as your versions of <CODE>operator</CODE> <CODE>new</CODE>, <CODE>operator</CODE> <CODE><NOBR>new[]</NOBR></CODE>, <CODE>operator</CODE> <CODE>delete</CODE>, and <CODE>operator</CODE> <CODE><NOBR>delete[]</NOBR></CODE>, if you write them. (<A HREF="../MEC/MC2_FR.HTM#33985" TARGET="_top">Item M8</A> describes the differences among these terms.)<SCRIPT>create_link(5);</SCRIPT>
<A NAME="2332"></A>
<A NAME="dingp6"></A><LI><I>How does object initialization differ from object assignment?</I> The answer to this question determines the behavior of and the differences between your constructors and your assignment operators.<SCRIPT>create_link(6);</SCRIPT>
<A NAME="2333"></A>
<A NAME="dingp7"></A><LI><I>What does it mean to pass objects of the new type by value?</I> Remember, the copy constructor defines what it means to pass an object by value.<SCRIPT>create_link(7);</SCRIPT>
<A NAME="5765"></A>
<A NAME="dingp8"></A><LI><I><A NAME="p78"></A>What are the constraints on legal values for the new type?</I> These constraints determine the kind of error checking you'll have to do inside your member functions, especially your constructors and assignment operators. It may also affect the exceptions your functions throw and, if you use them, your functions' exception specifications (see <A HREF="../MEC/MC3_FR.HTM#6011" TARGET="_top">Item M14</A>).<SCRIPT>create_link(8);</SCRIPT>
<A NAME="16146"></A>
<A NAME="dingp9"></A><LI><I>Does the new type fit into an inheritance graph?</I> If you inherit from existing classes, you are constrained by the design of those classes, particularly by whether the functions you inherit are virtual or nonvirtual. If you wish to allow other classes to inherit from your class, that will affect whether the functions you declare are virtual.<SCRIPT>create_link(9);</SCRIPT>
<A NAME="16148"></A>
<A NAME="dingp10"></A><LI><I>What kind of type conversions are allowed?</I> If you wish to allow objects of type A to be <I>implicitly</I> converted into objects of type B, you will want to write either a type conversion function in class A or a non-<CODE>explicit</CODE> constructor in class B that can be called with a single argument. If you wish to allow <I>explicit</I> conversions only, you'll want to write functions to perform the conversions, but you'll want to avoid making them type conversion operators or non-<CODE>explicit</CODE> single-argument constructors. (<A HREF="../MEC/MC2_FR.HTM#5970" TARGET="_top">Item M5</A> discusses the advantages and disadvantages of user-defined conversion functions.)<SCRIPT>create_link(10);</SCRIPT>
<A NAME="16149"></A>
<A NAME="dingp11"></A><LI><I>What operators and functions make sense for the new type?</I> The answer to this question determines which functions you'll declare in your class interface.<SCRIPT>create_link(11);</SCRIPT>
<A NAME="16150"></A>
<A NAME="dingp12"></A><LI><I>What standard operators and functions should be explicitly disallowed?</I> Those are the ones you'll need to declare <CODE>private</CODE>.<SCRIPT>create_link(12);</SCRIPT>
<A NAME="16151"></A>
<A NAME="dingp13"></A><LI><I>Who should have access to the members of the new type?</I> This question helps you determine which members are public, which are protected, and which are private. It also helps you determine which classes and/or functions should be friends, as well as whether it makes sense to nest one class inside another.<SCRIPT>create_link(13);</SCRIPT>
<A NAME="16165"></A>
<A NAME="dingp14"></A><LI>How general is the new type? Perhaps you're not really defining a new type. Perhaps you're defining a whole <I>family</I> of types. If so, you don't want to define a new class, you want to define a new class <I>template</I>.<SCRIPT>create_link(14);</SCRIPT>
</UL>
<A NAME="16154"></A>
<P><A NAME="dingp15"></A>
These are difficult questions to answer, so defining effective classes in C++ is far from simple. Done properly, however, user-defined classes in C++ yield types that are all but indistinguishable from built-in types, and that makes all the effort <NOBR>worthwhile.<SCRIPT>create_link(15);</SCRIPT>
</NOBR></P>
<A NAME="17772"></A>
<P><A NAME="dingp16"></A>
A discussion of the details of each of the above issues would comprise a book in its own right, so the guidelines that follow are anything but <A NAME="p79"></A>comprehensive. However, they highlight some of the most important design considerations, warn about some of the most frequent errors, and provide solutions to some of the most common problems encountered by class designers. Much of the advice is as applicable to non-member functions as it is to member functions, so in this section I consider the design and declaration of global and namespace-resident functions, <NOBR>too.<SCRIPT>create_link(16);</SCRIPT>
</NOBR></P>
<!-- SectionName="E18: Complete and minimal class interfaces" -->
<A NAME="17774"></A><A NAME="5794"></A>
<DIV ALIGN="CENTER"><FONT SIZE="-1">Back to <A HREF="#2326">Classes and Functions: Design and Declaration</A>
<BR>Continue to <A HREF="#5887">Item 19: Differentiate among member functions, non-member functions, and friend functions.</A></FONT></DIV>
<P><A NAME="dingp17"></A><FONT ID="eititle">Item 18: Strive for class interfaces that are complete and minimal.</FONT><SCRIPT>create_link(17);</SCRIPT>
</P>
<P><A NAME="dingp18"></A>
The client interface for a class is the interface that is accessible to the programmers who use the class. Typically, only functions exist in this interface, because having data members in the client interface has a number of drawbacks (see <A HREF="#5976">Item 20</A>).<SCRIPT>create_link(18);</SCRIPT>
</P>
<A NAME="5798"></A>
<P><A NAME="dingp19"></A>
Trying to figure out what functions should be in a class interface can drive you crazy. You're pulled in two completely different directions. On the one hand, you'd like to build a class that is easy to understand, straightforward to use, and easy to implement. That usually implies a fairly small number of member functions, each of which performs a distinct task. On other hand, you'd like your class to be powerful and convenient to use, which often means adding functions to provide support for commonly performed tasks. How do you decide which functions go into the class and which ones <NOBR>don't?<SCRIPT>create_link(19);</SCRIPT>
</NOBR></P>
<A NAME="5799"></A>
<P><A NAME="dingp20"></A>
Try this: aim for a class interface that is <I>complete</I> and <I>minimal</I>.<SCRIPT>create_link(20);</SCRIPT>
</P>
<A NAME="5801"></A>
<P><A NAME="dingp21"></A>
A <I>complete</I> interface is one that allows clients to do anything they might reasonably want to do. That is, for any reasonable task that clients might want to accomplish, there is a reasonable way to accomplish it, although it may not be as convenient as clients might like. A <I>minimal</I> interface, on the other hand, is one with as few functions in it as possible, one in which no two member functions have overlapping functionality. If you offer a complete, minimal interface, clients can do whatever they want to do, but the class interface is no more complicated than absolutely <NOBR>necessary.<SCRIPT>create_link(21);</SCRIPT>
</NOBR></P>
<A NAME="5803"></A>
<P><A NAME="dingp22"></A>
The desirability of a complete interface seems obvious enough, but why a minimal interface? Why not just give clients everything they ask for, adding functionality until everyone is <NOBR>happy?<SCRIPT>create_link(22);</SCRIPT>
</NOBR></P>
<A NAME="5804"></A>
<P><A NAME="dingp23"></A>
Aside from the moral issue — is it really <I>right</I> to mollycoddle your clients? — there are definite technical disadvantages to a class interface that is crowded with functions. First, the more functions in an interface, the harder it is for potential clients to understand. The harder it <A NAME="p80"></A>is for them to understand, the more reluctant they will be to learn how to use it. A class with 10 functions looks tractable to most people, but a class with 100 functions is enough to make many programmers run and hide. By expanding the functionality of your class to make it as attractive as possible, you may actually end up discouraging people from learning how to use <NOBR>it.<SCRIPT>create_link(23);</SCRIPT>
</NOBR></P>
<A NAME="5805"></A>
<P><A NAME="dingp24"></A>
A large interface can also lead to confusion. Suppose you create a class that supports cognition for an artificial intelligence application. One of your member functions is called <CODE>think</CODE>, but you later discover that some people want the function to be called <CODE>ponder</CODE>, and others prefer the name <CODE>ruminate</CODE>. In an effort to be accommodating, you offer all three functions, even though they do the same thing. Consider then the plight of a potential client of your class who is trying to figure things out. The client is faced with three different functions, all of which are supposed to do the same thing. Can that really be true? Isn't there some subtle difference between the three, possibly in efficiency or generality or reliability? If not, why are there three different functions? Rather than appreciating your flexibility, such a potential client is likely to wonder what on earth you were thinking (or pondering, or ruminating <NOBR>over).<SCRIPT>create_link(24);</SCRIPT>
</NOBR></P>
<A NAME="5807"></A>
<P><A NAME="dingp25"></A>
A second disadvantage to a large class interface is that of maintenance (see <A HREF="../MEC/MC6_FR.HTM#5373" TARGET="_top">Item M32</A>). It's simply more difficult to maintain and enhance a class with many functions than it is a class with few. It is more difficult to avoid duplicated code (with the attendant duplicated bugs), and it is more difficult to maintain consistency across the interface. It's also more difficult to <NOBR>document.<SCRIPT>create_link(25);</SCRIPT>
</NOBR></P>
<A NAME="5808"></A>
<P><A NAME="dingp26"></A>
Finally, long class definitions result in long header files. Because header files typically have to be read every time a program is compiled (see <A HREF="./EC5_FR.HTM#6793" TARGET="_top">Item 34</A>), class definitions that are longer than necessary can incur a substantial penalty in total compile-time over the life of a <NOBR>project.<SCRIPT>create_link(26);</SCRIPT>
</NOBR></P>
<A NAME="5812"></A>
<P><A NAME="dingp27"></A>
The long and short of it is that the gratuitous addition of functions to an interface is not without costs, so you need to think carefully about whether the convenience of a new function (a new function can <I>only</I> be added for convenience if the interface is already complete) justifies the additional costs in complexity, comprehensibility, maintainability, and compilation <NOBR>speed.<SCRIPT>create_link(27);</SCRIPT>
</NOBR></P>
<A NAME="5814"></A>
<P><A NAME="dingp28"></A>
Yet there's no sense in being unduly miserly. It is often justifiable to offer more than a minimal set of functions. If a commonly performed task can be implemented much more efficiently as a member function, that may well justify its addition to the interface. (Then again, it may <A NAME="p81"></A>not. See <A HREF="../MEC/MC4_FR.HTM#40995" TARGET="_top">Item M16</A>.) If the addition of a member function makes the class substantially easier to use, that may be enough to warrant its inclusion in the class. And if adding a member function is likely to prevent client errors, that, too, is a powerful argument for its being part of the <NOBR>interface.<SCRIPT>create_link(28);</SCRIPT>
</NOBR></P>
<A NAME="5818"></A>
<P><A NAME="dingp29"></A>
Consider a concrete example: a template for classes that implement arrays with client-defined upper and lower bounds and that offer optional bounds-checking. The beginning of such an array template is shown <NOBR>below:<SCRIPT>create_link(29);</SCRIPT>
</NOBR></P>
<A NAME="16239"></A>
<UL><PRE>template<class T>
class Array {
public:
enum BoundsCheckingStatus {NO_CHECK_BOUNDS = 0,
CHECK_BOUNDS = 1};
</PRE>
</UL><A NAME="16241"></A>
<UL><PRE> Array( int lowBound, int highBound,
BoundsCheckingStatus check = NO_CHECK_BOUNDS);
</PRE>
</UL><A NAME="16230"></A>
<UL><PRE> Array(const Array& rhs);
</PRE>
</UL><A NAME="16231"></A>
<UL><PRE> ~Array();
</PRE>
</UL><A NAME="5829"></A>
<UL><PRE> Array& operator=(const Array& rhs);
</PRE>
</UL><A NAME="16233"></A>
<UL><PRE>private:
int lBound, hBound; // low bound, high bound
</PRE>
</UL><A NAME="5830"></A>
<UL><PRE>
vector<T> data; // contents of array; see
// <A HREF="./EC7_FR.HTM#8392" TARGET="_top">Item 49</A> for vector info
</PRE>
</UL><A NAME="5831"></A>
<UL><PRE> BoundsCheckingStatus checkingBounds;
};
</PRE>
</UL><A NAME="5837"></A>
<P><A NAME="dingp30"></A>
The member functions declared so far are the ones that require basically no thinking (or pondering or ruminating). You have a constructor to allow clients to specify each array's bounds, a copy constructor, an assignment operator, and a destructor. In this case, you've declared the destructor nonvirtual, which implies that this class is not to be used as a base class (see <A HREF="./EC3_FR.HTM#223029" TARGET="_top">Item 14</A>).<SCRIPT>create_link(30);</SCRIPT>
</P>
<A NAME="5847"></A>
<P><A NAME="dingp31"></A>
The declaration of the assignment operator is actually less clear-cut than it might at first appear. After all, built-in arrays in C++ don't allow assignment, so you might want to disallow it for your <CODE>Array</CODE> objects, too (see <A HREF="#6406">Item 27</A>). On the other hand, the array-like <CODE>vector</CODE> template (in the standard library — see <A HREF="./EC7_FR.HTM#8392" TARGET="_top">Item 49</A>) permits assignments between <CODE>vector</CODE> objects. In this example, you'll follow <CODE>vector</CODE>'s lead, and that decision, as you'll see below, will affect other portions of the classes's <NOBR>interface.<SCRIPT>create_link(31);</SCRIPT>
</NOBR></P>
<A NAME="5851"></A>
<P><A NAME="dingp32"></A>
<A NAME="p82"></A>Old-time C hacks would cringe to see this interface. Where is the support for declaring an array of a particular size? It would be easy enough to add another <NOBR>constructor,<SCRIPT>create_link(32);</SCRIPT>
</NOBR></P>
<A NAME="5852"></A>
<UL><PRE>Array(int size,
BoundsCheckingStatus check = NO_CHECK_BOUNDS);
</PRE>
</UL><A NAME="5853"></A>
<P><A NAME="dingp33"></A>
but this is not part of a minimal interface, because the constructor taking an upper and lower bound can be used to accomplish the same thing. Nonetheless, it might be a wise political move to humor the old geezers, possibly under the rubric of consistency with the base <NOBR>language.<SCRIPT>create_link(33);</SCRIPT>
</NOBR></P>
<A NAME="5854"></A>
<P><A NAME="dingp34"></A>
What other functions do you need? Certainly it is part of a complete interface to index into an <NOBR>array:<SCRIPT>create_link(34);</SCRIPT>
</NOBR></P>
<A NAME="5855"></A>
<UL><PRE>// return element for read/write
T& operator[](int index);
</PRE>
</UL><A NAME="5857"></A>
<UL><PRE>// return element for read-only
const T& operator[](int index) const;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -