📄 ch14.htm
字号:
<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. Allrights reserved.</p></CENTER></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -