📄 index.html
字号:
<tt> get_lock();</tt>
<tt> day = d;</tt>
<tt> release_lock();</tt>
<tt>}</tt>
<tt>void Date::setMonth(int m)</tt>
<tt>{</tt>
<tt> get_lock();</tt>
<tt> month = m;</tt>
<tt> release_lock();</tt>
<tt>}</tt>
<tt>//etc.</tt>
</pre>
<p>This is tedious, and yet very simple to automate. The recurrent pattern is
very reminiscent of the "resource acquisition is initialization" idiom (discussed
in Chapter 5, "Object-Oriented Programming and Design"). You can define a class
whose constructor acquires a lock, and whose destructor releases it. For example</p>
<pre>
<tt>class LockDate</tt>
<tt>{</tt>
<tt>private:</tt>
<tt> Date& date;</tt>
<tt>public:</tt>
<tt> LockDate(const Date& d) : date { lock(&d); }</tt>
<tt> ~LockDate() { release(&d); }</tt>
<tt>};</tt>
</pre>
<p>A real-world lock class would probably be templatized. It would also provide
timeouts and handle exceptions; however, the definition of <tt>LockDate</tt>
suffices for this discussion. The member functions of <tt>Date</tt> can now
be defined as follows:</p>
<pre>
<tt>int Date::getDay() const</tt>
<tt>{</tt>
<tt> LockDate ld(this);</tt>
<tt> return day;</tt>
<tt>}</tt>
<tt> /...and so on</tt>
<tt>void Date::getDay(int d)</tt>
<tt>{</tt>
<tt> LockDate ld(this);</tt>
<tt> day = d;</tt>
<tt> }</tt>
<tt>//etc.</tt>
</pre>
<p>This looks better than the original thread-safe version, but it's still tedious.
Standard C++, however, goes only that far. A fully automated thread safety requires
core language extensions.</p>
<p>It might not seem obvious from the example why language support for thread
safety is necessary. After all, instantiating a local object in every member
function is not unacceptably complicated or inefficient. The troubles begin
with inheritance. Invoking a non-thread-safe inherited member function might
have undefined results in this case. To ensure thread safety in inherited member
functions as well, the implementer of <tt>Date</tt> has to override every inherited
member function. In each override, a lock has to be acquired. Then, the parent
member function is invoked, and finally, the lock is released. With a little
help from the programming language, these operations can be made much easier.</p>
<p><b>Before Method and After Method</b></p>
<p>The CLOS programming language defines the concepts <i>before method</i> and
<i>after method</i>. A before method is a sequence of operations that precedes
the action of a method. An after method is a sequence of operations that succeeds
the action of a method. Thus, each method (member function) in CLOS can be thought
of as an object with a corresponding constructor and destructor. CLOS provides
default before method and after method for each user-defined method. By default,
the before method and after method do nothing. However, the user can override
them to perform initialization and cleanup operations. Adopting this concept
in C++ with slight modifications might simplify the implementation of thread-safe
classes. One direction is to provide identical before method and after method
for every member function of a class. That is, the before method and after method
are defined only once, but they are automatically invoked by every member function
of the class (except for the constructor and destructor). One of the benefits
of this approach is that new member functions that are added to the class automatically
become thread-safe, as do inherited member functions.</p>
<h3> <a name="Heading13">Extensible Member Functions</a></h3>
<p>Several programming languages enable the user to compose inherited member functions
in a derived class almost automatically. In C++, a member function of a derived
class overrides rather than extends the corresponding member of the base class.
It is possible to extend the inherited function by calling it explicitly before
performing any other operations in the overriding member function (see Chapter
5). The following example (repeated here for convenience) shows how it is done:</p>
<pre>
<tt>class rectangle: public shape</tt>
<tt>{</tt>
<tt> //...</tt>
<tt> virtual void resize (int x, int y) //extends base's resize()</tt>
<tt> {</tt>
<tt><b> shape::resize(x, y);</b> //explicit call to the base's virtual function</tt>
<tt> //add functionality</tt>
<tt> int size = x*y;</tt>
<tt> }</tt>
<tt>};</tt>
</pre>
<p>There are two problems with this approach. First, if the base class name changes,
the implementer of the derived class has to find every occurrence of the old
qualified name and change it accordingly. </p>
<p>Another problem is that some member functions are meant to be extended rather
than overridden. The best examples are constructors and destructors (which,
luckily, the compiler takes care of), but there are other such examples. The
serialization and deserialization operations that were discussed previously
also need to be extended rather than overridden in a derived class.</p>
<p>It is very tempting to solve the first problem by adding the keyword <tt>super</tt>
to the language. Smalltalk and other object-oriented languages already have
it. Why not let C++ programmers enjoy it as well? <tt>super</tt> refers to the
direct base class. It can be used in the following manner:</p>
<pre>
<tt>class rectangle: public shape</tt>
<tt>{</tt>
<tt> //...</tt>
<tt> void resize (int x, int y) //extends base's resize()</tt>
<tt> {</tt>
<tt> super.resize(x, y); //the name of the base class is not necessary anymore</tt>
<tt> //add functionality</tt>
<tt> int size = x*y;</tt>
<tt> }</tt>
<tt>};</tt>
<tt>class specialRect: public rectangle</tt>
<tt>{</tt>
<tt>void resize (int x, int y) //extends base's resize()</tt>
<tt> {</tt>
<tt> super.resize(x, y); //calls recatngle::resize()</tt>
<tt> //add more functionality</tt>
<tt> }</tt>
<tt>};</tt>
</pre>
<p>However, <tt>super</tt> is ambiguous in objects that have multiple base classes.
An alternative solution is to add a different keyword to the language, <tt>extensible</tt>,
that instructs the compiler to insert a call of the base member function in
an overriding member function automatically. For example</p>
<pre>
<tt>class shape</tt>
<tt>{</tt>
<tt>public:</tt>
<tt> extensible void resize();</tt>
<tt>}</tt>
<tt>class rectangle: public shape</tt>
<tt>{</tt>
<tt> public:</tt>
<tt> void resize (int x, int y) //extends base's resize()</tt>
<tt> { //shape::resize() is implicitly invoked at this point</tt>
<tt>//add functionality</tt>
<tt> int size = x*y;</tt>
<tt> }</tt>
<tt>};</tt>
<tt>class specialRect: public rectangle</tt>
<tt>{</tt>
<tt>void resize (int x, int y) //extends base's resize()</tt>
<tt> {<b> </b>//implicitly calls recatngle::resize()</tt>
<tt> //...add more functionality</tt>
<tt> }</tt>
<tt>};</tt>
</pre>
<p><tt>extensible</tt> is a specialized form of <tt>virtual</tt>, so the latter
is unnecessary. Surely, <tt>extensible</tt> solves the first problem: If the
base class name changes, the implementer of the derived class does not have
to change the definition of the member functions. The second problem is also
solved here: After a member function is declared <tt>extensible</tt>, the compiler
automatically sees that the corresponding member function of a derived class
first invokes the member function of the base class.</p>
<h3> <a name="Heading14">Dynamically Linked Libraries</a></h3>
<p>A typical C++ application consists of a statically linked executable that contains
all the code and data of the program. Although static linking is efficient in
terms of speed, it's inflexible: Every change in the code requires a complete
rebuild of the executable. When a dynamically linked library is used, the executable
does not need to be rebuilt; the next time the application is run, it automatically
picks up the new library version. The advantage of dynamically linked libraries
is a transparent upgrade of new releases of the dynamically linked library.
However, this transparent "drop in" model breaks under the object model of C++
if the data layout of an object changes in the new release of the library; this
is because the size of an object and the offset of its data members are fixed
at compile time. There have been suggestions to extend the object model of C++
so that it can support dynamic shared libraries better. However, the costs are
slower execution speed and size.</p>
<h3> <a name="Heading15">Rule-Based Programming</a></h3>
<p>Many commercial databases support <i>triggers</i>. A trigger is a user-defined
rule that instructs the system to perform specific actions automatically whenever
a certain data value changes. For example, imagine a database that contains
two tables, Person and Bank Account. Every row in Bank Account is associated
with a record in Person. Deleting a Person record automatically triggers the
deletion of all its associated Bank Account records. Rules are the equivalent
of triggers in software systems. William Tepfenhart and other researchers at
AT&T Bell Laboratories have extended C++ to support rules (<i>UML and C++:
A Practical Guide to Object-Oriented Development</i>, p. 137). The extended
language is called R++ (the <i>R</i> stands for "rules"). In addition to member
functions and data members, R++ defines a third kind of class member: a rule.
A rule consists of a condition and an associated action that is automatically
executed when the condition evaluates to <tt>true</tt>. In C++, the programmer
has to test the condition manually in order to decide whether the associated
action is to be executed, usually by a <tt>switch</tt> statement or an <tt>if</tt>
statement. In R++, this testing is automated -- the system monitors the data
members listed in the rule's condition, and whenever the condition is satisfied,
the rule "fires" (that is, the associated action is executed). Rule-based programming
is widely used in artificial intelligence, debugging systems, and event-driven
systems. Adding this feature to C++ could considerably simplify the design and
implementation of such systems.</p>
<h2> <a name="Heading16">Conclusions</a></h2>
<p>Language extensions are needed to facilitate the implementation of operations
that otherwise might be more difficult or even impossible. However, there is
always a tradeoff involved. To use an analogy, adding an air conditioner to
a car decreases its fuel efficiency and degrades its performance (<i>Principles
of Programming Languages: Design, Evaluation and Implementation</i>, p. 327).
Whether it is a beneficial tradeoff depends on various factors, such as the
climate in the region where the car is used, the cost of fuel, the engine's
power, and the personal preferences of its users. Note that the air conditioner
can always be turned off to gain more power and increase the fuel efficiency.
Ideally, new language features will not impose a performance penalty of any
kind when they are not used. When the programmer deliberately uses them, they
should impose as little overhead as possible or no overhead at all. There is,
however, a notable difference between an air conditioner and language extensions:
Extensions interact with one another. For example, the imaginary keyword <tt>super</tt>
has an undesirable interaction with another language feature, namely multiple
inheritance. A more realistic example is template's template arguments. The
space between the left two angular brackets is mandatory:</p>
<pre>
<tt>Vector <Vector<char*> > msg_que(10);</tt>
</pre>
<p>Otherwise, the <tt>>></tt> sequence is parsed as the right shift operator.
In other situations, the interaction is much more complex: Koenig lookup, for
instance, can have surprising results under some circumstances (as you read
in Chapter 8, "Namespaces").</p>
<p>This chapter has presented three major proposals for language extensions: garbage
collection, persistence, and concurrency. Suggestions for less radical extensions
are extensible members and rules. None of these is to be taken lightly. The
complexity involved in standardizing each of these is intensified even further
when they interact with each other. For example, a persistence model becomes
even more complicated in a thread-safe environment.</p>
<p>Considering the challenges that the designers of C++ have faced during the
past two decades, you can remain optimistic. If you are familiar with the prestandardized
implementations of container classes, RTTI, and exception handling of several
well known frameworks, you are probably aware of how the standardized container
classes, RTTI, and exception handling are much better in every way. This will
also be the case if any of the features that are discussed here become part
of the C++ Standard.</p>
<CENTER>
<P>
<HR>
<A HREF="/publishers/que/series/professional/0789720221/index.htm"><img src="/publishers/que/series/professional/0789720221/button/contents.gif" WIDTH="128"
HEIGHT="28" ALIGN="BOTTOM" ALT="Contents" BORDER="0"></A> <BR>
<BR>
<BR>
<p></P>
<P>© <A HREF="/publishers/que/series/professional/0789720221/copy.htm">Copyright 1999</A>, Macmillan Computer Publishing. All
rights reserved.</p>
</CENTER>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -