📄 ch14.htm
字号:
<tt> int secs;</tt><tt> int minutes;</tt><tt> int hours;</tt><tt>public:</tt><tt> //...</tt><tt> DateTime::DateTime(ifstream& archive); //reconstituting constructor</tt><tt> ofstream& Write(ofstream& archive);</tt><tt> ifstream& Read(ifstream& archive);</tt><tt>};</tt><tt>ofstream& DateTime::Write(ofstream& archive)</tt><tt>{</tt><tt> Date::Write(archive); //must invoke base class Write() first</tt><tt> archive.write( reinterpret_cast<char*> (&), 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& DateTime::Read(ifstream& archive)</tt><tt>{</tt><tt> Date::Read(archive);</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><tt>DateTime::DateTime(ifstream& archive) //reconstituting constructor</tt><tt>{</tt><tt> Read(arcive);</tt><tt>}</tt></pre><h4> Third Party Classes</h4><p>Overriding the member functions <tt>Read()</tt> and <tt>Write()</tt> and serializing data members to and from a stream are error prone and can cause maintenance difficulties. Whenever data members are added or removed, or when their types are changed, the implementer has to modify these member functions accordingly -- but this is still managable. However, deriving from classes that do not define a reconstituting constructor and the member functions <tt>Read()</tt> and <tt>Write()</tt> is more difficult to handle because a derived class can only serialize its own members -- not members of its base classes. The same difficulties exist with embedded objects. How are such subobjects serialized? It might be possible to overcome these difficulties in some cases, albeit with considerable efforts. For example, a class that contains a <tt>vector</tt> can iterate through the vector's members and serialize them one by one. This is only half the story, though. A vector's state depends on other parameters, such as its capacity. Where can this information be stored if the <tt>vector</tt> object itself cannot be serialized? Serializing arrays is another conundrum. One solution is to write a header in the beginning of every serialized object that contains the number of elements. However, this won't work with reference counted objects. Most implementations of <tt>std::string</tt> are reference counted, which means that in the following code snippet, the five <tt>string</tt> objects share some of their data members:</p><pre><tt>#include <string></tt><tt>using namespace std;</tt><tt>void single_string()</tt><tt>{</tt><tt> string sarr[4];</tt><tt> string s = sarr[0];</tt><tt> for (int i = 1; i< 4; i++)</tt><tt> {</tt><tt> sarr[i] = s;</tt><tt> }</tt><tt>}</tt></pre><p>Reference counting is an implementation detail that is hidden from the users of the class; it is impossible to query the <tt>string</tt> object about how many strings it represents and to serialize this datum.</p><p>Handmade object persistence is becoming much more complicated than it seemed at first, isn't it? But that's not all yet. How might such a handmade persistence model represent templates? By simply storing specializations as ordinary objects, the model fails to represent the relationship that exists among the specializations. Worse yet, multiple inheritance and virtual inheritance are even more challenging. How can a handmade persistence model ensure that a virtual subobject is serialized only once, regardless of the number of its occurrences in the inheritance graph? </p><h4> A Proposal for a Standardized Persistence Model</h4><p>Most programmers probably give in at this point, and rightfully so. It is possible to come up with a solution even to the virtual base class problem, but as soon as this problem is solved, other special cases such as function objects, static data members, reference variables, and unions present more complexities. There is another drawback in the handmade persistence model: It is not standardized, and as such, programmers have to implement it on their own. The result is a lack of uniformity and varying levels of reliability and performance. Without standardized support for object persistence, a homemade persistence model is, at best, brittle and error prone. Obviously, without standardized object persistence it is impossible to ensure simple, portable, and efficient serialization and deserialization of objects. </p><h4> Library-Based Extensions</h4><p>What might such a standardized persistence model look like? There are two basic strategies. One is library-based, whereas the other relies on core language extensions (keywords and syntax). A library-based solution is advantageous in many respects. For example, it does not extend the core language, thus avoiding additional burden for programmers who do not intend to use persistent objects. In addition, a library can be replaced by a better implementation from another vendor without having to switch to a different compiler. This practice can be seen today with people who uninstall the original STL implementation -- provided by the compiler vendor -- and replace it with another one. Still, a library-based solution has to deal with the lack of language support for persistence, and it must face the same difficulties and complications that were demonstrated previously (the intricacies and vagaries of the most widely used object distribution frameworks, namely the Distributed Component Object Model (DCOM) and the Common Object Request Broker Architecture (CORBA), prove this point). STL might have never become what it is today without built-in support for templates and operator overloading. Furthermore, the language support for templates was extended in various ways to provide the necessary constructs for STL (see Chapter 2, "Standard Briefing: The Latest Addenda to ANSI/ISO C++,"<i> </i>and Chapter 9). Similarly, the support for persistence requires core language extensions.</p><h4> A New Constructor Type</h4><p>The special member functions are automatically synthesized by the implementation if the programmer does not declare them explicitly and if the implementation needs them (see Chapter 4). Similarly, a language extension can be made so that another type of constructor, a <i>reconstituting constructor</i>, is either implicitly synthesized by the implementation when needed, or so that it can be declared by the programmer. As is the case with other constructor types, the programmers need to be allowed to override the default reconstituting constructor by defining it explicitly. The syntactic form of such a constructor must be distinct from all other constructor forms. In particular, a reconstituting constructor is not to be identified solely by its signature. In other words, the following</p><pre><tt>class A</tt><tt>{</tt><tt>//...</tt><tt>public:</tt><tt> A(istream& repository ); //reconstituting ctor or an ordinary constructor</tt><tt>};</tt></pre><p>is not recommended. It might well be the case that the programmer's intention was to define an ordinary constructor that takes an <tt>istream</tt> object by reference and not a reconstituting constructor. Furthermore, such a convention might break existing code. A better approach is to add a syntactic clue that signifies a reconstituting constructor exclusively. For example, by preceding the symbol <tt>><</tt> to the constructor's name</p><pre><tt>class A</tt><tt>{</tt><tt>//...</tt><tt>public:</tt><tt> ><A(istream& repository ); //reconstituting constructor</tt><tt>};</tt></pre><p>the reconstituting constructor can take a single parameter of some stream type. This parameter is optional. When the reconstituting constructor is invoked without an argument, the implementation deserializes the object from a default input stream that can be specified in the compiler's setting (similar to the default location of the standard header files). To automate the serialization process, a serializing destructor is also necessary. How might such a destructor be declared? One solution is to add another type of destructor so that classes can have two destructor types. This is, however, troublesome because the object model of C++ is based on a single destructor per class. Adding another type of destructor is ruled out then. Perhaps there is no need to define a distinct destructor type. Instead, the existing destructor can do the serialization automatically: The compiler can insert into the destructor additional code that performs the necessary serialization operations. (As you know, compilers already insert code into user-defined destructors to invoke the destructors of base classes and embedded objects.) </p><p>Automating the serialization process has drawbacks, too. Not every class has to be serialized. The overhead of serializing an object should be imposed only when the user really needs it. Furthermore, the possibility of encountering runtime exceptions during serialization is rather high. A full hard disk, a broken network connection, and a corrupted repository are only a handful of the possible runtime exceptions that can occur during the process of writing the contents of an object to a permanent storage medium. However, throwing an exception from a destructor is highly undesirable (see Chapter 6), so perhaps automatic serialization during object destruction is too risky. Apparently, there is no escape from explicitly calling a member function to do the job. There are other obstacles here: How to handle the creation and serialization of an array of objects? How to synchronize changes in the definition of a class and the contents of an object that was serialized before the change took place? Every language that supports object persistence deals with these difficulties in its own way. C++ can borrow some of these ideas, too, or it can initiate innovative ideas.</p><p>This discussion gives you some feel of why language extensions are necessary, and what kind of obstacles they overcome. However hypothetical this discussion might seem, the evolution of C++ has been a democratic process. Many of the changes and extensions were initiated by users of the language rather than Standardization committee members. STL is probably the best example of this. If you have a comprehensive proposal for such an extension, you can present it to the Standardization committee.</p><h3> <a name="Heading12">Support for Concurrency</a></h3><p><i>Concurrency</i> is a generic term for multithreading and multiprocessing. Concurrent programming can effectively improve performance and responsiveness of an application, be it a word processor or a satellite homing system. C++ does not directly address the issues of multiprocessing, threads, and thread safety. It is important to note, however, that nothing in the Standard Library or the language itself <i>disallows </i>concurrency. Look at the example of exception handling: In a multithreaded environment, exception handling should be thread-safe, but a single-threaded environment can implement exception handling in a non-thread-safe manner; this is an implementation-dependent issue. Implementations are allowed to provide the necessary facilities for concurrency, and indeed many of them do so. Again, without direct support from the programming language, either by standardized libraries or by core extensions, the implementation of thread safety is more complicated and highly nonportable. There have been several proposals in the past for adding concurrency to C and C++. At present, however, none of these languages supports concurrency directly. </p><h4> Multithreading</h4><p><i>Multithreading</i>, as opposed to multiprocessing, refers to the use of several control threads in a single process. Multithreading is therefore simpler to design and implement, and it enables the application to use system resources more effectively.</p><p>Because all threads in a process share the process's data, it is essential to synchronize their operation properly so that one thread does not interfere with another. For that purpose, <i>synchronization objects</i> are used. Various types of synchronization objects, such as mutex, critical section, lock, and semaphore offer different levels of resource allocation and protection. Unfortunately, the details and the characterizations of synchronization objects vary from platform to platform. A standard library of synchronization objects has to be flexible enough to enable users to combine platform-specific synchronization objects with standard objects. This is similar to the use of <tt>std::string</tt> and nonstandard string objects in the same program. Alternatively, the standard thread library could provide the basic interfaces of the synchronization objects, and the implementation would be platform-dependent. There is a problem with introducing multithreading support into the Standard, however: single-threaded operating systems such as DOS. Although these platforms are not very popular these days, they are still in use, and implementing a thread library on these platforms is nearly impossible.</p><h4> Thread safety</h4><p>Perhaps the Standard can provide only the necessary features for thread safety and leave the other issues -- such as synchronization objects, event objects, instantiation, destruction of threads, and so on -- implementation-defined, as they are today. Thread safety ensures that an object can be used safely in a multithreaded environment. For example, the following thread-unsafe 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>public:</tt><tt> Date(); //current date</tt><tt> ~Date();</tt><tt> //accessors</tt><tt> int getDay() const { return day; }</tt><tt> int getMonth() const { return month; }</tt><tt> int getYear() const { return year; }</tt><tt> //mutators</tt><tt> void setDay(int d) { day = d; }</tt><tt> void setMonth(int m) { month = m; }</tt><tt> void setYear(int y) { year = y; }</tt><tt>};</tt></pre><p>can become thread-safe by applying the following changes to it: At the beginning of each member function, a lock has to be acquired; in every return point of each member function, the lock has to be released.</p><p>The modified member functions now look like this:</p><pre><tt>void Date::setDay(int d)</tt><tt>{</tt><tt> get_lock();</tt><tt> day = d;</tt><tt> release_lock();</tt>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -