📄 chapter05.html
字号:
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">In general, it’s not a good idea to
depend on anything that’s implementation-specific when you’re
writing a program. When you must have implementation-specific dependencies,
encapsulate them inside a structure so that any porting changes are focused in
one
place.</FONT><A NAME="_Toc312373844"></A><A NAME="_Toc472654843"></A><BR></P></DIV>
<A NAME="Heading216"></A><FONT FACE = "Verdana"><H2 ALIGN="LEFT">
The class</H2></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Access control is often referred to as
<I>implementation hiding<A NAME="Index1237"></A><A NAME="Index1238"></A></I>.
Including functions within structures (often referred to as
encapsulation</FONT><A NAME="fnB36" HREF="#fn36">[36]</A><A NAME="Index1239"></A><FONT FACE="Georgia">)
produces a data type with characteristics and behaviors, but access control puts
boundaries within that data type, for two important reasons. The first is to
establish what the client programmers can and can’t use. You can build
your internal mechanisms into the structure without worrying that client
programmers will think that these mechanisms are part of the interface they
should be using.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">This feeds directly into the second
reason, which is to separate the interface from the implementation.
<A NAME="Index1240"></A><A NAME="Index1241"></A><A NAME="Index1242"></A> If the
structure is used in a set of programs, but the client programmers can’t
do anything but send messages to the <B>public</B> interface, then you can
change anything that’s <B>private</B> without requiring modifications to
their code.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Encapsulation and access control, taken
together, invent something more than a C <B>struct</B>. We’re now in the
world of object-oriented programming, where a structure is describing a class of
objects as you would describe a class of fishes or a class of birds: Any object
belonging to this class will share these characteristics and behaviors.
That’s what the structure declaration has become, a description of the way
all objects of this type will look and act.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">In the original OOP
<A NAME="Index1243"></A>language, Simula-67<A NAME="Index1244"></A>, the keyword
<A NAME="Index1245"></A><B>class</B> <A NAME="Index1246"></A>was used to
describe a new data type. This apparently inspired Stroustrup to choose the same
keyword for C++, to emphasize that this was the focal point of the whole
language: the creation of new data types that are more than just C
<B>struct</B>s with functions. This certainly seems like adequate justification
for a new keyword.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">However, the use of <B>class</B> in C++
comes close to being an unnecessary keyword. It’s identical to the
<B>struct</B> keyword in absolutely every way except one: <B>class</B> defaults
to <B>private</B>, whereas <B>struct</B> defaults to <B>public</B>. Here are two
structures that produce the same result:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C05:Class.cpp</font>
<font color=#009900>// Similarity of struct and class</font>
<font color=#0000ff>struct</font> A {
<font color=#0000ff>private</font>:
<font color=#0000ff>int</font> i, j, k;
<font color=#0000ff>public</font>:
<font color=#0000ff>int</font> f();
<font color=#0000ff>void</font> g();
};
<font color=#0000ff>int</font> A::f() {
<font color=#0000ff>return</font> i + j + k;
}
<font color=#0000ff>void</font> A::g() {
i = j = k = 0;
}
<font color=#009900>// Identical results are produced with:</font>
<font color=#0000ff>class</font> B {
<font color=#0000ff>int</font> i, j, k;
<font color=#0000ff>public</font>:
<font color=#0000ff>int</font> f();
<font color=#0000ff>void</font> g();
};
<font color=#0000ff>int</font> B::f() {
<font color=#0000ff>return</font> i + j + k;
}
<font color=#0000ff>void</font> B::g() {
i = j = k = 0;
}
<font color=#0000ff>int</font> main() {
A a;
B b;
a.f(); a.g();
b.f(); b.g();
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The <B>class</B> is the fundamental OOP
concept in C++. It is one of the keywords that will <I>not </I>be set in bold in
this book – it becomes annoying with a word repeated as often as
“class.” The shift to classes is so important that I suspect
Stroustrup’s preference would have been to throw <B>struct</B> out
altogether, but the need for backwards compatibility with C wouldn’t allow
that.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Many people prefer a style of creating
classes that is more <B>struct</B>-like than class-like, because you override
the “default-to-<B>private</B>” behavior of the class by starting
out with <B>public</B> elements:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#0000ff>class</font> X {
<font color=#0000ff>public</font>:
<font color=#0000ff>void</font> interface_function();
<font color=#0000ff>private</font>:
<font color=#0000ff>void</font> private_function();
<font color=#0000ff>int</font> internal_representation;
}; </PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The logic behind this is that it makes
more sense for the reader to see the members of interest first, then they can
ignore anything that says <B>private</B>. Indeed, the only reasons all the other
members must be declared in the class at all are so the compiler knows how big
the objects are and can allocate them properly, and so it can guarantee
consistency.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The examples in this book, however, will
put the <B>private</B> members first, like this:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#0000ff>class</font> X {
<font color=#0000ff>void</font> private_function();
<font color=#0000ff>int</font> internal_representation;
<font color=#0000ff>public</font>:
<font color=#0000ff>void</font> interface_function();
}; </PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Some people even go to the trouble of
decorating their own private names:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#0000ff>class</font> Y {
<font color=#0000ff>public</font>:
<font color=#0000ff>void</font> f();
<font color=#0000ff>private</font>:
<font color=#0000ff>int</font> mX; <font color=#009900>// "Self-decorated" name</font>
}; </PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Because <B>mX</B> is already hidden in
the scope of <B>Y</B>, the <B>m</B> (for “member”) is unnecessary.
However, in projects with many global variables (something you should strive to
avoid, but which is sometimes inevitable in existing projects), it is helpful to
be able to distinguish inside a member function definition which data is global
and which is a
member.</FONT><A NAME="_Toc312373845"></A><A NAME="_Toc472654844"></A><BR></P></DIV>
<A NAME="Heading217"></A><FONT FACE = "Verdana"><H3 ALIGN="LEFT">
Modifying Stash to use access control</H3></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">It makes sense to take the examples from
Chapter 4 and modify them to use classes and access control. Notice how the
client programmer portion of the interface is now clearly distinguished, so
there’s no possibility of client programmers accidentally manipulating a
part of the class that they shouldn’t.
<A NAME="Index1247"></A></FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C05:Stash.h</font>
<font color=#009900>// Converted to use access control</font>
#ifndef STASH_H
#define STASH_H
<font color=#0000ff>class</font> Stash {
<font color=#0000ff>int</font> size; <font color=#009900>// Size of each space</font>
<font color=#0000ff>int</font> quantity; <font color=#009900>// Number of storage spaces</font>
<font color=#0000ff>int</font> next; <font color=#009900>// Next empty space</font>
<font color=#009900>// Dynamically allocated array of bytes:</font>
<font color=#0000ff>unsigned</font> <font color=#0000ff>char</font>* storage;
<font color=#0000ff>void</font> inflate(<font color=#0000ff>int</font> increase);
<font color=#0000ff>public</font>:
<font color=#0000ff>void</font> initialize(<font color=#0000ff>int</font> size);
<font color=#0000ff>void</font> cleanup();
<font color=#0000ff>int</font> add(<font color=#0000ff>void</font>* element);
<font color=#0000ff>void</font>* fetch(<font color=#0000ff>int</font> index);
<font color=#0000ff>int</font> count();
};
#endif <font color=#009900>// STASH_H ///:~</font></PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The <B>inflate( )</B> function has
been made <B>private</B> because it is used only by the <B>add( )</B>
function and is thus part of the underlying implementation, not the interface.
This means that, sometime later, you can change the underlying implementation to
use a different system for memory management.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Other than the name of the include file,
the header above is the only thing that’s been changed for this example.
The implementation file and test file are the
same.</FONT><A NAME="_Toc312373846"></A><A NAME="_Toc472654845"></A><BR></P></DIV>
<A NAME="Heading218"></A><FONT FACE = "Verdana"><H3 ALIGN="LEFT">
Modifying Stack to use access control</H3></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">As a second example, here’s the
<B>Stack</B> turned into a class. Now the nested data structure is
<B>private</B>, which is nice because it ensures that the client programmer will
neither have to look at it nor be able to depend on the internal representation
of the <B>Stack</B>: <A NAME="Index1248"></A></FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C05:Stack2.h</font>
<font color=#009900>// Nested structs via linked list</font>
#ifndef STACK2_H
#define STACK2_H
<font color=#0000ff>class</font> Stack {
<font color=#0000ff>struct</font> Link {
<font color=#0000ff>void</font>* data;
Link* next;
<font color=#0000ff>void</font> initialize(<font color=#0000ff>void</font>* dat, Link* nxt);
}* head;
<font color=#0000ff>public</font>:
<font color=#0000ff>void</font> initialize();
<font color=#0000ff>void</font> push(<font color=#0000ff>void</font>* dat);
<font color=#0000ff>void</font>* peek();
<font color=#0000ff>void</font>* pop();
<font color=#0000ff>void</font> cleanup();
};
#endif <font color=#009900>// STACK2_H ///:~</font></PRE></FONT></BLOCKQUOTE>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">As before, the implementation
doesn’t change and so it is not repeated here. The test, too, is
identical. The only thing that’s been changed is the robustness of the
class interface. The real value of access control is to prevent you from
crossing boundaries during development. In fact, <A NAME="Index1249"></A>the
compiler is the only thing that knows about the protection level of class
members. There is no access control information mangled into the member name
that carries through to the linker. All the protection checking is done by the
compiler; it has vanished by
runtime.<A NAME="Index1250"></A><A NAME="Index1251"></A><A NAME="Index1252"></A></FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Notice that the interface presented to
the client programmer is now truly that of a push-down
stack<A NAME="Index1253"></A><A NAME="Index1254"></A>. It happens to be
implemented as a linked list<A NAME="Index1255"></A><A NAME="Index1256"></A>,
but you can change that without affecting what the client programmer interacts
with, or (more importantly) a single line of client
code.</FONT><A NAME="_Toc312373847"></A><BR></P></DIV>
<DIV ALIGN="LEFT"><P><A NAME="Index1257"></A><A NAME="Index1258"></A><A NAME="_Toc472654846"></A><BR></P></DIV>
<A NAME="Heading219"></A><FONT FACE = "Verdana"><H2 ALIGN="LEFT">
Handle classes</H2></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Access control in C++ allows you to
separate interface from implementation, but the implementation hiding
<A NAME="Index1259"></A><A NAME="Index1260"></A>is only partial. The compiler
must still see the declarations for all parts of an object in order to create
and manipulate it properly. You could imagine a programming language that
requires only the public interface of an object and allows the private
implementation to be hidden, but C++ performs type checking statically (at
compile time) as much as possible. This means that you’ll learn as early
as possible if there’s an error. It also means that your program is more
efficient. However, including the private implementation has two effects: the
implementation is visible even if you can’t easily access it, and it can
cause needless
recompilation<A NAME="Index1261"></A><A NAME="Index1262"></A><A NAME="Index1263"></A>.</FONT><A NAME="_Toc312373848"></A><A NAME="_Toc472654847"></A><BR></P></DIV>
<A NAME="Heading220"></A><FONT FACE = "Verdana"><H3 ALIGN="LEFT">
Hiding the implementation</H3></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Some projects cannot afford to have their
implementation visible to the client programmer. It may show strategic
information in a library header file that the company doesn’t want
available to competitors. You may be working on a system where
security<A NAME="Index1264"></A> is an issue – an encryption algorithm,
for example – and you don’t want to expose any clues in a header
file that might help people to crack the code. Or you may be putting your
library in a “hostile” environment, where the
programmers<A NAME="Index1265"></A> will directly access the private components
anyway, using pointers <A NAME="Index1266"></A>and
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -