📄 chapter07.html
字号:
existing bytes from <B>mem</B> to <B>newmem</B> (typically in an efficient
fashion). Finally, the old memory is deleted and the new memory and sizes are
assigned to the appropriate members.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The <B>Mem</B> class is designed to be
used as a tool within other classes to simplify their memory management (it
could also be used to hide a more sophisticated memory-management system
provided, for example, by the operating system). Appropriately, it is tested
here by creating a simple “string” class:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C07:MemTest.cpp</font>
<font color=#009900>// Testing the Mem class</font>
<font color=#009900>//{L} Mem</font>
#include <font color=#004488>"Mem.h"</font>
#include <cstring>
#include <iostream>
<font color=#0000ff>using</font> <font color=#0000ff>namespace</font> std;
<font color=#0000ff>class</font> MyString {
Mem* buf;
<font color=#0000ff>public</font>:
MyString();
MyString(<font color=#0000ff>char</font>* str);
~MyString();
<font color=#0000ff>void</font> concat(<font color=#0000ff>char</font>* str);
<font color=#0000ff>void</font> print(ostream& os);
};
MyString::MyString() { buf = 0; }
MyString::MyString(<font color=#0000ff>char</font>* str) {
buf = <font color=#0000ff>new</font> Mem(strlen(str) + 1);
strcpy((<font color=#0000ff>char</font>*)buf->pointer(), str);
}
<font color=#0000ff>void</font> MyString::concat(<font color=#0000ff>char</font>* str) {
<font color=#0000ff>if</font>(!buf) buf = <font color=#0000ff>new</font> Mem;
strcat((<font color=#0000ff>char</font>*)buf->pointer(
buf->msize() + strlen(str) + 1), str);
}
<font color=#0000ff>void</font> MyString::print(ostream& os) {
<font color=#0000ff>if</font>(!buf) <font color=#0000ff>return</font>;
os << buf->pointer() << endl;
}
MyString::~MyString() { <font color=#0000ff>delete</font> buf; }
<font color=#0000ff>int</font> main() {
MyString s(<font color=#004488>"My test string"</font>);
s.print(cout);
s.concat(<font color=#004488>" some additional stuff"</font>);
s.print(cout);
MyString s2;
s2.concat(<font color=#004488>"Using default constructor"</font>);
s2.print(cout);
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">All you can do with this class is to
create a <B>MyString</B>, concatenate text, and print to an
<A NAME="Index1437"></A><B>ostream</B>. The class only contains a pointer to a
<B>Mem</B>, but note the distinction between the default constructor, which sets
the pointer to zero, and the second constructor, which creates a <B>Mem</B> and
copies data into it. The advantage of the
<A NAME="Index1438"></A><A NAME="Index1439"></A>default constructor is that you
can create, for example, a large array of empty <B>MyString</B> objects very
cheaply, since the size of each object is only one pointer and the only overhead
of the default constructor is that of assigning to zero. The cost of a
<B>MyString</B> only begins to accrue when you concatenate data; at that point
the <B>Mem</B> object is created if it hasn’t been already. However, if
you use the default constructor and never concatenate any data, the destructor
call is still safe because <A NAME="Index1440"></A>calling <B>delete</B> for
zero is defined such that it does not try to release storage or otherwise cause
problems.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">If you look at these two constructors it
might at first seem like this is a prime candidate for default arguments.
However, if you drop the default constructor and write the remaining constructor
with a default argument:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>MyString(<font color=#0000ff>char</font>* str = <font color=#004488>""</font>);</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">everything will work correctly, but
you’ll lose the previous efficiency benefit since a <B>Mem</B> object will
always be created. To get the efficiency back, you must modify the
constructor:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>MyString::MyString(<font color=#0000ff>char</font>* str) {
<font color=#0000ff>if</font>(!*str) { <font color=#009900>// Pointing at an empty string</font>
buf = 0;
<font color=#0000ff>return</font>;
}
buf = <font color=#0000ff>new</font> Mem(strlen(str) + 1);
strcpy((<font color=#0000ff>char</font>*)buf->pointer(), str);
} </PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">This means, in effect, that the default
value becomes a flag that causes a separate piece of code to be executed than if
a non-default value is used. Although it seems innocent enough with a small
constructor like this one, in general this practice can cause problems. If you
have to <I>look</I> for the default rather than treating it as an ordinary
value, that should be a clue that you will end up with effectively two different
functions inside a single function body: one version for the normal case and one
for the default. You might as well split it up into two distinct function bodies
and let the compiler do the selection. This results in a slight (but usually
invisible) increase in efficiency, because the extra argument isn’t passed
and the extra code for the conditional isn’t executed. More importantly,
you are keeping the code for two separate functions <I>in</I> two separate
functions rather than combining them into one using default arguments, which
will result in easier maintainability, especially if the functions are
large.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">On the other hand, consider the
<B>Mem</B> class. If you look at the definitions of the two constructors and the
two <B>pointer( )</B> functions, you can see that using default arguments
in both cases will not cause the member function definitions to change at all.
Thus, the class could easily be:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C07:Mem2.h</font>
#ifndef MEM2_H
#define MEM2_H
<font color=#0000ff>typedef</font> <font color=#0000ff>unsigned</font> <font color=#0000ff>char</font> byte;
<font color=#0000ff>class</font> Mem {
byte* mem;
<font color=#0000ff>int</font> size;
<font color=#0000ff>void</font> ensureMinSize(<font color=#0000ff>int</font> minSize);
<font color=#0000ff>public</font>:
Mem(<font color=#0000ff>int</font> sz = 0);
~Mem();
<font color=#0000ff>int</font> msize();
byte* pointer(<font color=#0000ff>int</font> minSize = 0);
};
#endif <font color=#009900>// MEM2_H ///:~</font></PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Notice that a call to
<B>ensureMinSize(0)</B> will always be quite efficient.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Although in both of these cases I based
some of the decision-making process on the issue of
<A NAME="Index1441"></A>efficiency, you must be careful not to fall into the
trap of thinking only about efficiency (fascinating as it is). The most
important issue in class design is the interface of the class (its <B>public</B>
members, which are available to the client programmer). If these produce a class
that is easy to use and reuse, then you have a success; you can always tune for
efficiency if necessary but the effect of a class that is designed badly because
the programmer is over-focused on efficiency issues can be dire. Your primary
concern should be that the interface makes sense to those who use it and who
read the resulting code. Notice that in <B>MemTest.cpp</B> the usage of
<B>MyString</B> does not change regardless of whether a default constructor is
used or whether the efficiency is high or
low.</FONT><A NAME="_Toc312373871"></A><A NAME="_Toc472654874"></A><BR></P></DIV>
<A NAME="Heading245"></A><FONT FACE = "Verdana"><H2 ALIGN="LEFT">
Summary</H2></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">As a guideline, you shouldn’t use a
<A NAME="Index1442"></A><A NAME="Index1443"></A>default argument as a flag upon
which to conditionally execute code. You should instead break the function into
two or more overloaded functions if you can. A default argument should be a
value you would ordinarily put in that position. It’s a value that is more
likely to occur than all the rest, so client programmers can generally ignore it
or use it only if they want to change it from the default
value.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The default argument is included to make
function calls easier, especially when those functions have many arguments with
typical values. Not only is it much easier to write the calls, it’s easier
to read them, especially if the class creator can order the arguments so the
least-modified defaults appear latest in the list.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">An especially important use of default
arguments is when you start out with a function with a set of arguments, and
after it’s been used for a while you discover you need to add arguments.
By defaulting all the new arguments, you ensure that all client code using the
previous interface is not disturbed.
<A NAME="Index1444"></A><A NAME="Index1445"></A></FONT><A NAME="_Toc312373872"></A><A NAME="_Toc472654875"></A><BR></P></DIV>
<A NAME="Heading246"></A><FONT FACE = "Verdana"><H2 ALIGN="LEFT">
Exercises</H2></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia" SIZE=2>Solutions to selected exercises
can be found in the electronic document <I>The Thinking in C++ Annotated
Solution Guide</I>, available for a small fee from
www.BruceEckel.com.</FONT><BR></P></DIV>
<OL>
<LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Create a <B>Text</B> class
that contains a <B>string</B> object to hold the text of a file. Give it two
constructors: a default constructor and a constructor that takes a <B>string</B>
argument that is the name of the file to open. When the second constructor is
used, open the file and read the contents into the <B>string</B> member object.
Add a member function <B>contents( )</B> to return the <B>string</B> so
(for example) it can be printed. In <B>main( )</B>,<B> </B>open a file
using <B>Text</B> and print the
contents.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Create a
<B>Message</B> class with a constructor that takes a single <B>string</B> with a
default value. Create a private member <B>string</B>, and in the constructor
simply assign the argument <B>string</B> to your internal <B>string</B>. Create
two overloaded member functions called <B>print( )</B>: one that takes no
arguments and simply prints the message stored in the object, and one that takes
a <B>string</B> argument, which it prints in addition to the internal message.
Does it make sense to use this approach instead of the one used for the
constructor?</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Determine
how to generate assembly output with your compiler, and run experiments to
deduce the name-decoration
scheme.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Create a
class that contains four member functions, with 0, 1, 2, and 3 <B>int</B>
arguments, respectively. Create a <B>main( )</B> that makes an object of
your class and calls each of the member functions. Now modify the class so it
has instead a single member function with all the arguments defaulted. Does this
change your
<B>main( )</B>?</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Create
a function with two arguments and call it from <B>main( )</B>. Now make one
of the arguments a “placeholder” (no identifier) and see if your
call in <B>main( )</B>
changes.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Modify
<B>Stash3.h</B> and <B>Stash3.cpp</B> to use default arguments in the
constructor. Test the constructor by making two different versions of a
<B>Stash</B>
object.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Create a new
version of the <B>Stack</B> class (from Chapter 6) that contains the default
constructor as before, and a second constructor that takes as its arguments an
array of pointers to objects and the size of that array. This constructor should
move through the array and push each pointer onto the <B>Stack</B>. Test your
class with an array of
<B>string</B>.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Modify
<B>SuperVar</B> so that there are <B>#ifdef</B>s around all the <B>vartype</B>
code as described in the section on <B>enum</B>. Make <B>vartype</B> a regular
and <B>public </B>enumeration (with no instance) and modify <B>print( )</B>
so that it requires a <B>vartype</B> argument to tell it what to
do.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Implement
<B>Mem2.h</B> and make sure that the modified class still works with
<B>MemTest.cpp</B>.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Use
<B>class Mem</B> to implement <B>Stash</B>. Note that because the implementation
is <B>private</B> and thus hidden from the client programmer, the test code does
not need to be
modified.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">In
<B>class Mem</B>, add a <B>bool</B> <B>moved( )</B> member function that
takes the result of a call to <B>pointer( )</B> and tells you whether the
pointer has moved (due to reallocation). Write a <B>main( )</B> that tests
your <B>moved( )</B> member function. Does it make more sense to use
something like <B>moved( )</B> or to simply call <B>pointer( )</B>
every time you need to access the memory in
<B>Mem</B>?</FONT><A NAME="_Toc305628674"></A><A NAME="_Toc312373902"></A></OL><FONT FACE = "Verdana"><H1 ALIGN="LEFT">
</H1></FONT>
<DIV ALIGN="CENTER">
<FONT FACE="Verdana" size = "-1">
[ <a href="Chapter06.html">Previous Chapter</a> ]
[ <a href="Contents.html">Table of Contents</a> ]
[ <a href="DocIndex.html">Index</a> ]
[ <a href="Chapter08.html">Next Chapter</a> ]
</FONT>
<BR>
Last Update:09/27/2001</P></DIV>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -