📄 chapter14.html
字号:
pointers. Before, <B>Stack </B>would accept <B>void</B> pointers, so the user
had no type checking to make sure the proper pointers were inserted. In
addition, <B>peek( )</B> and <B>pop( )</B> now return <B>String</B>
pointers instead of <B>void</B> pointers, so no cast is necessary to use the
pointer.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Amazingly enough, this extra
type-checking safety is free in <B>push( )</B>, <B>peek( )</B>, and
<B>pop( )</B>! The compiler is being given extra type information that it
uses at compile-time, but the functions are inlined and no extra code is
generated.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Name hiding comes into play here because,
in particular, the <B>push( )</B> function has a different signature: the
argument list is different. If you had two versions of <B>push( )</B> in
the same class, that would be overloading, but in this case overloading is
<I>not</I> what we want because that would still allow you to pass any kind of
pointer into <B>push( )</B> as a <B>void*</B>. Fortunately, C++ hides the
<B>push(void*)</B> version in the base class in favor of the new version
that’s defined in the derived class, and therefore it only allows us to
<B>push( )</B> <B>string</B> pointers onto the <B>StringStack</B>.
</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Because we can now guarantee that we know
exactly what kind of objects are in the container, the destructor works
correctly and the <A NAME="Index2326"></A>ownership problem is solved – or
at least, one approach to the ownership problem. Here, if you
<B>push( )</B> a <B>string</B> pointer onto the <B>StringStack</B>, then
(according to the semantics of the <B>StringStack</B>)<B> </B>you’re also
passing ownership of that pointer to the <B>StringStack</B>. If you
<B>pop( )</B> the pointer, you not only get the pointer, but you also get
ownership of that pointer. Any pointers that are left on the <B>StringStack</B>
when its destructor is called are then deleted by that destructor. And since
these are always <B>string</B> pointers and the <B>delete</B> statement is
working on <B>string</B> pointers instead of <B>void</B> pointers, the proper
destruction happens and everything works correctly.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">There is a drawback: this class works
<I>only</I> for <B>string</B> pointers. If you want a <B>Stack </B>that works
with some other kind of object, you must write a new version of the class so
that it works only with your new kind of object. This rapidly becomes tedious,
and is finally solved using templates, as you will see in Chapter
16.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">We can make an additional observation
about this example: it changes the interface of the <B>Stack</B> in the process
of inheritance. If the interface is different, then a <B>StringStack</B> really
isn’t a <B>Stack</B>, and you will never be able to correctly use a
<B>StringStack</B> as a <B>Stack</B>. This makes the use of inheritance
questionable here; if you’re not creating a <B>StringStack</B> that
<A NAME="Index2327"></A><I>is-a</I> type of <B>Stack</B>, then why are you
inheriting? A more appropriate version of <B>StringStack</B> will be shown later
in this
chapter.</FONT><A NAME="_Toc312374022"></A><A NAME="_Toc472654997"></A><BR></P></DIV>
<A NAME="Heading414"></A><FONT FACE = "Verdana"><H2 ALIGN="LEFT">
Functions that don’t automatically inherit</H2></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Not all functions are automatically
inherited from the base class into the derived class. Constructors and
destructors deal with the creation and destruction of an object, and they can
know what to do with the aspects of the object only for their particular class,
so all the constructors <A NAME="Index2328"></A>and destructors
<A NAME="Index2329"></A>in the hierarchy below them must be called. Thus,
constructors and destructors don’t inherit and must be created specially
for each derived class.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">In addition, the <B>operator=</B>
<A NAME="Index2330"></A>doesn’t inherit because it performs a
constructor-like activity. That is, just because you know how to assign all the
members of an object on the left-hand side of the <B>=</B> from an object on the
right-hand side doesn’t mean that assignment will still have the same
meaning after inheritance.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">In lieu of inheritance, these functions
are <A NAME="Index2331"></A>synthesized by the compiler if you don’t
create them yourself. (With constructors, you can’t create <I>any
</I>constructors in order for the compiler to synthesize the default constructor
and the copy-constructor.) This was briefly described in Chapter 6. The
synthesized constructors use
<A NAME="Index2332"></A><A NAME="Index2333"></A><A NAME="Index2334"></A>memberwise
initialization and the synthesized <B>operator=</B> uses
<A NAME="Index2335"></A><A NAME="Index2336"></A><A NAME="Index2337"></A>memberwise
assignment. Here’s an example of the functions that are synthesized by the
compiler:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C14:SynthesizedFunctions.cpp</font>
<font color=#009900>// Functions that are synthesized by the compiler</font>
#include <iostream>
<font color=#0000ff>using</font> <font color=#0000ff>namespace</font> std;
<font color=#0000ff>class</font> GameBoard {
<font color=#0000ff>public</font>:
GameBoard() { cout << <font color=#004488>"GameBoard()\n"</font>; }
GameBoard(<font color=#0000ff>const</font> GameBoard&) {
cout << <font color=#004488>"GameBoard(const GameBoard&)\n"</font>;
}
GameBoard& <font color=#0000ff>operator</font>=(<font color=#0000ff>const</font> GameBoard&) {
cout << <font color=#004488>"GameBoard::operator=()\n"</font>;
<font color=#0000ff>return</font> *<font color=#0000ff>this</font>;
}
~GameBoard() { cout << <font color=#004488>"~GameBoard()\n"</font>; }
};
<font color=#0000ff>class</font> Game {
GameBoard gb; <font color=#009900>// Composition</font>
<font color=#0000ff>public</font>:
<font color=#009900>// Default GameBoard constructor called:</font>
Game() { cout << <font color=#004488>"Game()\n"</font>; }
<font color=#009900>// You must explicitly call the GameBoard</font>
<font color=#009900>// copy-constructor or the default constructor</font>
<font color=#009900>// is automatically called instead:</font>
Game(<font color=#0000ff>const</font> Game& g) : gb(g.gb) {
cout << <font color=#004488>"Game(const Game&)\n"</font>;
}
Game(<font color=#0000ff>int</font>) { cout << <font color=#004488>"Game(int)\n"</font>; }
Game& <font color=#0000ff>operator</font>=(<font color=#0000ff>const</font> Game& g) {
<font color=#009900>// You must explicitly call the GameBoard</font>
<font color=#009900>// assignment operator or no assignment at </font>
<font color=#009900>// all happens for gb!</font>
gb = g.gb;
cout << <font color=#004488>"Game::operator=()\n"</font>;
<font color=#0000ff>return</font> *<font color=#0000ff>this</font>;
}
<font color=#0000ff>class</font> Other {}; <font color=#009900>// Nested class</font>
<font color=#009900>// Automatic type conversion:</font>
<font color=#0000ff>operator</font> Other() <font color=#0000ff>const</font> {
cout << <font color=#004488>"Game::operator Other()\n"</font>;
<font color=#0000ff>return</font> Other();
}
~Game() { cout << <font color=#004488>"~Game()\n"</font>; }
};
<font color=#0000ff>class</font> Chess : <font color=#0000ff>public</font> Game {};
<font color=#0000ff>void</font> f(Game::Other) {}
<font color=#0000ff>class</font> Checkers : <font color=#0000ff>public</font> Game {
<font color=#0000ff>public</font>:
<font color=#009900>// Default base-class constructor called:</font>
Checkers() { cout << <font color=#004488>"Checkers()\n"</font>; }
<font color=#009900>// You must explicitly call the base-class</font>
<font color=#009900>// copy constructor or the default constructor</font>
<font color=#009900>// will be automatically called instead:</font>
Checkers(<font color=#0000ff>const</font> Checkers& c) : Game(c) {
cout << <font color=#004488>"Checkers(const Checkers& c)\n"</font>;
}
Checkers& <font color=#0000ff>operator</font>=(<font color=#0000ff>const</font> Checkers& c) {
<font color=#009900>// You must explicitly call the base-class</font>
<font color=#009900>// version of operator=() or no base-class</font>
<font color=#009900>// assignment will happen:</font>
Game::<font color=#0000ff>operator</font>=(c);
cout << <font color=#004488>"Checkers::operator=()\n"</font>;
<font color=#0000ff>return</font> *<font color=#0000ff>this</font>;
}
};
<font color=#0000ff>int</font> main() {
Chess d1; <font color=#009900>// Default constructor</font>
Chess d2(d1); <font color=#009900>// Copy-constructor</font>
<font color=#009900>//! Chess d3(1); // Error: no int constructor</font>
d1 = d2; <font color=#009900>// Operator= synthesized</font>
f(d1); <font color=#009900>// Type-conversion IS inherited</font>
Game::Other go;
<font color=#009900>//! d1 = go; // Operator= not synthesized </font>
<font color=#009900>// for differing types</font>
Checkers c1, c2(c1);
c1 = c2;
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The constructors and the <B>operator=</B>
for <B>GameBoard </B>and <B>Game </B>announce themselves so you can see when
they’re used by the compiler. In addition, the <B>operator
Other( )</B> performs automatic type conversion from a <B>Game</B> object
to an object of the nested class <B>Other</B>. The class <B>Chess</B> simply
inherits from <B>Game</B> and creates no functions (to see how the compiler
responds). The function <B>f( )</B> takes an <B>Other</B> object to test
the automatic type conversion function.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">In <B>main( )</B>, the synthesized
default constructor and copy-constructor for the derived class <B>Chess </B>are
called. The <B>Game</B> versions of these constructors are called as part of the
constructor-call hierarchy. Even though it looks like inheritance, new
constructors are actually synthesized by the compiler. As you might expect, no
constructors with arguments are automatically created because that’s too
much for the compiler to intuit.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The <B>operator=</B> is also synthesized
as a new function in <B>Chess</B> using memberwise assignment (thus, the
base-class version is called) because that function was not explicitly written
in the new class. And of course the destructor was automatically synthesized by
the compiler.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Because of all these rules about
rewriting functions that handle object creation, it may seem a little strange at
first that the automatic type conversion operator <I>is</I> inherited. But
it’s not too unreasonable – if there are enough pieces in
<B>Game</B> to make an <B>Other</B> object, those pieces are still there in
anything derived from <B>Game</B> and the type conversion operator is still
valid (even though you may in fact want to redefine it).</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia"><B>operator=</B> is synthesized
<I>only</I> for assigning objects of the same type. If you want to assign one
type to another you must always write that <B>operator=</B>
yourself.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">If you look more closely at <B>Game</B>,
you’ll see that the copy-constructor and assignment operators have
explicit calls to the member object copy-constructor and assignment operator.
You will normally want to do this because otherwise, in the case of the
copy-constructor, the default member object constructor will be used instead,
and in the case of the assignment operator, <I>no</I> assignment at all will be
done for the member objects!</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Lastly, look at <B>Checkers</B>, which
explicitly writes out the default constructor, copy-constructor, and assignment
operators. In the case of the default constructor, the default base-class
constructor is automatically called, and that’s typically what you want.
But, and this is an important point, as soon as you decide to write your own
copy-constructor and assignment operator, the compiler assumes that you know
what you’re doing and <I>does not</I> automatically call the base-class
versions, as it does in the synthesized functions. If you want the base class
versions called (and you typically do) then you must explicitly call them
yourself. In the <B>Checkers</B> copy-constructor, this call appears in the
constructor initializer list:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>Checkers(<font color=#0000ff>const</font> Checkers& c) : Game(c) {</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">In the <B>Checkers</B> assignment
operator, the base class call is the first line in the function
body:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>Game::<font color=#0000ff>operator</font>=(c);</PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">These calls should be part of the
canonical form that you use whenever you inherit a
class.</FONT><A NAME="_Toc305593255"></A><A NAME="_Toc305628727"></A><A NAME="_Toc312374023"></A><A NAME="_Toc472654998"></A><BR></P></DIV>
<A NAME="Heading415"></A><FONT FACE = "Verdana"><H3 ALIGN="LEFT">
Inheritance and static member
functions<BR><A NAME="Index2338"></A><A NAME="Index2339"></A><A NAME="Index2340"></A><A NAME="Index2341"></A></H3></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia"><B>static</B> member functions act the
same as non-<B>static</B> member functions:</FONT><BR></P></DIV>
<OL>
<LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">They inherit into the
derived class.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">If
you redefine a static member, all the other overloaded functions in the base
class are hidden.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">If
you change the signature of a function in the base class, all the base class
versions with that function name are hidden (this is really a variation of the
previous point).</FONT></OL><DIV ALIGN="LEFT"><P><FONT FACE="Georgia">However,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -