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