📄 index.html
字号:
forced on the programmer. Pure procedural C++ code (legacy C code that is ported
to a C++ compiler, for example) does not pay for these features. In other words,
users -- almost without exception -- have a choice between higher-level features,
which impose a performance penalty, and lower-level features, which are free
from these performance penalties but are more susceptible to design modifications
and are harder to maintain. The "pay as you go" principle enables programmers
to use C++ in diverse application domains and apply different programming paradigms
according to their needs and priorities.</p>
<h2> <a name="Heading9">Possible Future Additions to C++</a></h2>
<p>It's hard to predict which new features will be added to C++ in the future,
mostly because it's hard to predict what programming in general will be like
five or ten years from now. However, automatic garbage collection, concurrency,
and object persistence are already implemented in many other object-oriented
programming languages; in the future, they might be added to C++ as well. Rule-based
programming and better support for dynamically linked libraries are other such
possible extensions that C++ might or might not have in the future. The following
sections discuss these features, and their incurred complications, in greater
detail.</p>
<h3> <a name="Heading10">Automatic Garbage Collection</a></h3>
<p>"If the programmer's convenience is that important, why doesn't C++ have a
garbage collector?" is an often-heard question (garbage collection is also discussed
in Chapter 11, "Memory Management"). Clearly, an automated garbage collector
can make the life of a programmer easier. However, unlike objects, virtual member
functions, and dynamic casts, the programmer does not have the freedom of choice
with garbage collection. If garbage collection is an automatic process that
is hidden from the programmer, it violates the "pay as you go" principle. The
cost of automatic garbage collection is forced on users, even if they prefer
to manage dynamic memory manually.</p>
<p>Is it possible to add automated garbage collection as a switch (very much like
the capability to turn off RTTI support in some compilers)? This is an interesting
question. Surely, in programs that do not use dynamic memory allocation, the
programmer might want to turn the garbage collector off. The real crux is with
programs that allocate memory dynamically. Consider the following example:</p>
<pre>
<tt>void f()</tt>
<tt>{</tt>
<tt> int * p = new int;</tt>
<tt> //...use p</tt>
<tt>}</tt>
</pre>
<p>When the garbage collector is switched on, the implementation will mark the
pointer <tt>p</tt> as unreferenced when <tt>f()</tt> exits. Consequently, in
the next invocation of the garbage collector, the memory pointed to by <tt>p</tt>
will be released. Adding a garbage collector for such simple cases is overkill,
though. The programmer can use an <tt>auto_ptr</tt> (<tt>auto_ptr</tt> is discussed
in Chapter 6, "Exception handling," and in Chapter 11) to achieve the same effect.
For example</p>
<pre>
<tt>void f()</tt>
<tt>{</tt>
<tt> auto_ptr<int> p (new int);</tt>
<tt> //...use p</tt>
<tt>} //auto_ptr's destructor releases p</tt>
</pre>
<p>Garbage collection is more useful when dynamic memory has to be released at
a different scope from where it was allocated. For example, virtual constructors
(which are discussed in Chapter 4, "Special Member Functions: Default Constructor,
Copy Constructor, Destructor, and Assignment Operator") enable the user to instantiate
a new object of the right type, without having to know the exact type of the
source object (the example is repeated here for convenience):</p>
<pre>
<tt>class Browser</tt>
<tt>{</tt>
<tt>public:</tt>
<tt> Browser();</tt>
<tt> Browser( const Browser&);</tt>
<tt> virtual Browser* construct() </tt>
<tt> { return new Browser; } //virtual default constructor</tt>
<tt> virtual Browser* clone() </tt>
<tt> { return new Browser(*this); } //virtual copy constructor</tt>
<tt> virtual ~Browser();</tt>
<tt>//...</tt>
<tt>};</tt>
<tt>class HTMLEditor: public Browser</tt>
<tt>{</tt>
<tt>public:</tt>
<tt> HTMLEditor ();</tt>
<tt> HTMLEditor (const HTMLEditor &);</tt>
<tt> HTMLEditor * construct() </tt>
<tt> { return new HTMLEditor; }//virtual default constructor</tt>
<tt> HTMLEditor * clone() </tt>
<tt> { return new HTMLEditor (*this); } //virtual copy constructor</tt>
<tt> virtual ~HTMLEditor();</tt>
<tt> //...</tt>
<tt>};</tt>
</pre>
<p>In a garbage collected environment, it is possible to use a virtual constructor
in the following way:</p>
<pre>
<tt>void instantiate (Browser& br)</tt>
<tt>{</tt>
<tt> br.construct()->view();</tt>
<tt>}</tt>
</pre>
<p>Here again, the system automatically registers the unnamed pointer that is
returned from <tt>br.construct()</tt> and marks it as unreferenced so that the
garbage collector can later destroy its associated object and recycle its storage.
In a non-garbage collected environment, <tt>instantiate()</tt> causes a memory
leak because the allocated object is never deleted (it might cause undefined
behavior as well because the allocated object is never destroyed). To enable
this programming practice, a garbage collector is mandatory rather than optional.
You might suggest that <tt>instantiate()</tt> is to be written as follows:</p>
<pre>
<tt>void instantiate (Browser& br)</tt>
<tt>{</tt>
<tt> Browser *pbr = br.construct();</tt>
<tt> pbr->view();</tt>
<tt> delete pbr;</tt>
<tt>}</tt>
</pre>
<p>This way, <tt>instantiate()</tt> can be used in a non-garbage collected environment
as well as in a garbage collected one: When the garbage collector is active,
the <tt>delete</tt> statement is ignored (perhaps by some macro magic) and the
dynamically allocated object is automatically released some time after <tt>instantiate()</tt>
has exited. The <tt>delete</tt> statement is executed only when the garbage
collector is inactive. However, there is another subtle problem here. </p>
<h4> The Problem with Late Destructor Invocation</h4>
<p>In a non-garbage collected environment, <tt>pbr</tt> is deleted right before
<tt>instantiate()</tt> exits, which means that the destructor of the dynamically
allocated object is also invoked at that point. Conversely, in a garbage collected
environment, the destructor will be activated at an unspecified time after <tt>instantiate()</tt>
exits. The programmer cannot predict when this will happen. It might take a
few seconds, but it can also take hours or even days before the garbage collector
is invoked the next time. Now suppose that the destructor of <tt>Browser</tt>
releases a locked resourcesuch as a database connection, a lock, or a modem.
The program's behavior in a garbage collected environment is unpredictable --
the locked resource can cause a deadlock because other objects might be waiting
for it, too. In order to avert such a potential deadlock, destructors can perform
only operations that do not affect other objects, and locked resources have
to be released explicitly by calling another member function. For example</p>
<pre>
<tt>void instantiate (Browser& br)</tt>
<tt>{</tt>
<tt> Browser *pbr = br.construct();</tt>
<tt> pbr->view();</tt>
<tt> pbr->release(); //release all locked resources</tt>
<tt> delete pbr;</tt>
<tt>}</tt>
</pre>
<p>This is, in fact, the predominant technique in garbage collected languages.
Then again, to ensure interoperability between a garbage collected environment
and a non-garbage collected one, programmers will have to write a dedicated
member function that releases locked resources that the class acquires -- even
if that class is used in a non-garbage collected environment. This is an unacceptable
burden and a violation of the "pay as you go" principle. The conclusion that
can be drawn from this discussion is that garbage collection cannot be optional.
It is nearly impossible to write efficient and reliable programs that work in
both environments. Either automatic garbage collection needs to be an integral
part of the language, or it is totally out (as is the case in C++ at present).</p>
<h4> Time-Critical Applications</h4>
<p>Garbage collection cannot be optional, as you have observed. Why not make it
an integral part of the language? Real-time systems are based on deterministic
time calculations. For example, a function that has to execute within a time
slot of 500 microseconds should never exceed its allotted time slice. However,
the garbage collection process is non-deterministic -- it is impossible to predict
when it will be invoked, and how long it will take. Therefore, languages that
offer automatic garbage collection are usually disqualified for use in time-critical
applications. Note that real-time programming is not confined to missile launching
and low-level hardware manipulation; most modern operating systems include time-critical
components that control the allocation of system resources among processes and
threads. Many communication systems are also deterministic by nature. Adding
an automated garbage collector to C++ would disqualify it from being used in
such application domains. Because a toggled garbage collector is also impractical,
C++, by design, is not a garbage collected language at present. Notwithstanding
the difficulties involved in garbage collection, there are some serious discussions
of adding garbage collection to C++. It is too early to determine if and when
this will happen.</p>
<h3> <a name="Heading11">Object Persistence</a></h3>
<p>Persistent objects can be stored in nonvolatile storage and used later in other
runs of the same program or in other programs. Storing the contents of an object
in persistent storage is called <i>serialization</i>. The process of reconstituting
a serialized object from a persistent repository is called <i>deserialization</i>,
or <i>reconstitution</i>. Other object-oriented languages support object persistence
directly by means of a library or built-in keywords and operators. C++ does
not support object persistence directly. Designing an efficient, general purpose,
platform-independent model of object persistence is quite a challenge. This
section exemplifies handmade solutions that make up for the lack of language
support for persistence. The difficulties and complications that are associated
with a handmade object persistence model demonstrate the importance of language
support.</p>
<h4> Serialization and Deserialization of Concrete Objects</h4>
<p>Consider the following class:</p>
<pre>
<tt>class Date</tt>
<tt>{</tt>
<tt>private:</tt>
<tt> int day;</tt>
<tt> int month;</tt>
<tt> int year;</tt>
<tt> //constructor and destructor</tt>
<tt>public:</tt>
<tt> Date(); //current date</tt>
<tt> ~Date();</tt>
<tt> //...</tt>
<tt>};</tt>
</pre>
<p>Storing a <tt>Date</tt> object is a rather straightforward operation: Every
data member is written to a persistent stream (usually this is a local disk
file, but it can also be a file on a remote computer). The data members can
be read from the stream at a later stage. For that purpose, two additional member
functions are required, one for storing the object and the other for reading
the stored object: </p>
<pre>
<tt>#include<fstream></tt>
<tt>using namespace std;</tt>
<tt>class Date</tt>
<tt>{</tt>
<tt>//...</tt>
<tt> virtual ofstream& Write(ofstream& archive);</tt>
<tt> virtual ifstream& Read(ifstream& archive);</tt>
<tt>};</tt>
<tt>ofstream& Date::Write(ofstream& archive)</tt>
<tt>{</tt>
<tt> archive.write( reinterpret_cast<char*> (&day), sizeof(day));</tt>
<tt> archive.write( reinterpret_cast<char*> (&month), sizeof(month));</tt>
<tt> archive.write( reinterpret_cast<char*> (&month), sizeof(year));</tt>
<tt> return archive;</tt>
<tt>}</tt>
<tt>ifstream& Date::Read(ifstream& archive)</tt>
<tt>{</tt>
<tt> archive.read( reinterpret_cast<char*> (&day), sizeof(day));</tt>
<tt> archive.read( reinterpret_cast<char*> (&month), sizeof(month));</tt>
<tt> archive.read( reinterpret_cast<char*> (&month), sizeof(year));</tt>
<tt> return archive;</tt>
<tt>}</tt>
</pre>
<p>In addition to the member functions <tt>Read()</tt> and <tt>Write()</tt>, it
is necessary to define a <i>reconstituting constructor</i>, which reads a serialized
object from a stream:</p>
<pre>
<tt>Date::Date(ifstream& archive) //reconstituting constructor</tt>
<tt>{</tt>
<tt> Read(arcive);</tt>
<tt>}</tt>
</pre>
<h4> Class Hierarchies</h4>
<p>For concrete classes such as <tt>Date</tt>, whose members are fundamental types,
making up for the lack of standardized persistence facilities is rather straightforward.
The serialization and deserialization operations merely store and read data
members, respectively. Note that the class's member functions are not serialized.
This is not a major issue of concern because the serialized object should be
a close approximation of the binary representation of the object in memory.</p>
<p>Handling derived classes and classes that contain member objects is more complicated:
The member functions <tt>Read()</tt> and <tt>Write()</tt> need to be redefined
in every class in the hierarchy. Likewise, a reconstituting constructor is required
for every class, as in the following example: </p>
<pre>
<tt>class DateTime: public Date</tt>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -