⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 7serial.html

📁 Visual C++ has been one of most effective tool for the large industrial applications. This book is t
💻 HTML
📖 第 1 页 / 共 4 页
字号:
</td></tr></table><!-- End Code -->
Therefore, when you need to convert an int into a char, use static_cast:
<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>int i = 0x0d;
char c = static_cast&lt;char&gt; (i);</pre>
</td></tr></table><!-- End Code -->
<p>Or, if you have two classes, <var>Base</var> and <var>Derived: public Base</var>, you can implicitly convert pointer to <var>Derived</var> to a pointer to <var>Base</var> (<var>Derived</var> is-a <var>Base</var>). Therefore, you can use static_cast to go the other way:
<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>Base * bp = new Derived; // implicit conversion
Derived * = static_cast&lt;Base *&gt; (bp);</pre>
</td></tr></table><!-- End Code -->

<p>You should realize that casts are dangerous and should be used very judiciously. Try to avoid casting at all costs. Serialization and deserialization are special in this respect, since they require low level manipulation of types.

<p>Finally, notice the strange way we store Boolean values. A Boolean value really requires only one bit for its storage. But, since we don't want to split bytes (or even longs, for that matter), we'll use some redundancy here. We could, in principle store the value <var>true</var> as one and <var>false</var> as zero. However, it will cost us the same to write a zero as to write an arbitrary value. The difference is that zeros are much more common in files than, say, <i>0xbad1bad2</i>. So when I read back the value <i>0xbad1bad2</i> and I expect a Boolean, I feel reassured that I'm reading sensible data and not some random garbage. This is only one of the ways of using redundancy for consistency checking.

<p>The output serializing stream is the mirror image of <var>DeSerializer</var>.

<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>class Serializer
{
public:
    Serializer (std::string const &amp; nameFile)
        : _stream (nameFile.c_str (), ios_base::out | ios_base::binary)
    {
        if (!_stream.is_open ())
            throw "couldn't open file";
    }
    void PutLong (long l)
    {
        _stream.write (reinterpret_cast&lt;char *&gt; (&amp;l), sizeof (long));
        if (_stream.bad())
            throw "file write failed";
    }
    void PutDouble (double d)
    {
        _stream.write (reinterpret_cast&lt;char *&gt; (&amp;d), sizeof (double));
        if (_stream.bad())
            throw "file write failed";
    }
    void PutString (std::string const &amp; str)
    {
        int len = str.length ();
        PutLong (len);
        _stream.write (str.data (), len);
        if (_stream.bad())
            throw "file write failed";
    }
    void PutBool (bool b)
    {
        long l = b? TruePattern: FalsePattern;
        PutLong (l);
        if (_stream.bad ())
            throw "file write failed";
    }
private:
    std::ofstream _stream;
};</pre>
</td></tr></table><!-- End Code -->

<!-- Sidebar -->
<table width=100% border=0 cellpadding=5><tr>
<td width=10>
<td bgcolor="#cccccc" class=sidebar>
There is a shortcut notation combining assignment with a conditional. The following code:
<!-- Code --><table width=100% cellspacing=10><tr>	<td class=codetable>
<pre>long l = b? TruePattern: FalsePattern;</pre>
</td></tr></table><!-- End Code -->
is equivalent to:

<!-- Code --><table width=100% cellspacing=10><tr>	<td class=codetable>
<pre>long l;
if (b)
    l = TruePattern;
else
    l = FalsePattern;</pre>
</td></tr></table><!-- End Code -->
<p>The ternary (meaning, three-argument) operator <var>A? B: C</var> first evaluates A. If A is true, it evaluates and returns B, otherwise it evaluates and returns C. A piece of trivia: unlike in C, in C++ the ternary operator returns an l-value, so it can be used on the left-hand-side of the assignment. Not that I would recommend this style!
<p>There is an even more obscure operator in C++, the comma sequencing operator. The expression <var>A, B</var> first evaluates A, then evaluates and returns B. The evaluation of A is therefore a side-effect of the whole operation. Most often the comma operator is used to combine two expressions where one is expected, like in this double loop:
<!-- Code --><table width=100% cellspacing=10><tr>	<td class=codetable>
<pre>for (int i = 0, j = 0; i < maxI && j < maxJ; ++i, ++j)</pre>
</td></tr></table><!-- End Code -->
By the way, the first comma separates the declarations (complete with initialization) of two variables of the same type. It's the second comma, between ++i and ++j, that is the sequencing operator.
</table>
<!-- End Sidebar -->


<p>Notice how protective we are when reading from or writing to a file. That's because our program doesn't have full control of the disk. A write can fail because we run out of disk space. This can happen at any time, because we are not the only client of the file system--there are other applications and system services that keep allocating (and presumably freeing) disk space. Reading is worse, because we're not even sure what to expect in the file. Not only may a read fail because of a hardware problem (unreadable disk sector), but we must be prepared for all kinds of sabotage. Other applications could have gotten hold of our precious file and truncated, edited or written all over it. We can't even be sure that the file we are trying to parse has been created by our program. The user could have mistakenly or maliciously pass to our program the name of some executable, a spreadsheet or autoexec.bat. 
<p>We already have the first line of defense against such cases of mistaken identity or downright corruption--the version number. The first four bytes we read from the file must match our current version number or we refuse to load it. The error message we display in such a case is a bit misleading. A much better solution would be to spare a few additional bytes and stamp all our files with a magic number. Many people use their initials for the magic number in the hope that one day they'll be able to say to their children or grandchildren, "You see these bytes at the beginning of each file of this type? These are your mom's (dad's, gramma's, grampa's) initials." Provided the application or the system survives that long and is not widely considered an example of bad software engineering.

<h3>In-Memory (De-) Serialization</h3>

<p>Serialization of data structures is not necessarily related to their storage in files. Sometimes you just want to store some data structure in a chunk of memory, especially if you want to pass it to another application. Programs can talk to each other and pass data through shared memory or other channels (Windows clipboard comes to mind). You might also want to send data in packets across the network. These are all situations in which you can't simply pass pointers embedded in your data. You have to change the format of data.
<p>The serialization procedure is the same, whether the output goes to a file or to memory. In fact, if your data structure is serializable (it has the <var>Serialize</var> and <var>DeSerialize</var> methods), all you might need to do in order to serialize it to memory is to change the implementation of <var>Serializer</var> and <var>DeSerializer</var>. Even better, you might make these classes abstract--turn methods <var>PutLong</var>, <var>PutDouble</var>, <var>PutBool</var> and <var>PutString</var> to pure virtual--and provide two different implementations, one writing to a file and one writing to memory. You can do the same with the deserializer.
<p>There is one big difference between a file and a chunk of memory--the file grows as you write to it, a chunk of memory has fixed size. You have two choices--you can either grow your memory buffer as needed, or you can calculate the required amount of memory up front and pre-allocate the whole buffer. As it turns out, calculating the size of a serializable data structure is surprisingly easy. All you need is yet another implementation of the <var>Serializer</var> interface called the counting serializer. The counting serializer doesn't write anything, it just adds up the sizes of various data types it is asked to write.

<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>class CountingSerializer: public Serializer
{
public:
    CountingSerializer ()
        : _size (0) {}
    int GetSize () const { return _size; }
    void PutLong (long l)
    {
        _size += sizeof (long);
    }
    void PutDouble (double d)
    {
        _size += sizeof (double);
    }
    void PutString (std::string const &amp; str)
    {
        _size += sizeof (long); // count
        _size += str.length ();
    }
    void PutBool (bool b)
    {
        _size += sizeof (long);
    }
private:
    int _size;
};</pre>
</td></tr></table><!-- End Code -->
<p>For instance, if you wanted to calculate the size of the file or memory buffer required for the serialization of a calculator, you'd call its <var>Serialize</var> method with a counting serializer.

<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>CountingSerializer counter;
_calc.Serialize (counter);
int size = counter.GetSize ();</pre>
</td></tr></table><!-- End Code -->

<p>Remember that, in order for this to work, all methods of <var>Serializer</var> <i>must</i> be virtual.

<h3>Multiple Inheritance</h3>

<p>In order to make a class serializable, you have to add to it two methods, <var>Serialize</var> and <var>DeSerialize</var>, and implement them. It makes sense, then, to create a separate abstract class--a pure interface--to abstract this behavior.
<!-- Code -->
<table width="100%" cellspacing=10><tr>
    <td class=codeTable>
<pre>class Serializable
{
public:
    virtual void Serialize (Serializer &amp; out) const = 0;
    virtual void DeSerialize (DeSerializer &amp; in) = 0;
};</pre>
    </td></tr>
</table>
<!-- End Code -->
<p>All classes that are serializable, should inherit from the <var>Serializable</var> interface.

<!-- Code -->
<table width="100%" cellspacing=10><tr>
    <td class=codeTable>
<pre>class Calculator: public Serializable
class SymbolTable: public Serializable
class Store: public Serializable</pre>
    </td></tr>
</table>
<!-- End Code -->

<p>What's the advantage of doing that? After all, even when you inherit from <var>Serializable</var>, you still have to add the declaration of the two methods to you class and you have to provide their implementation. Suppose that a new programmer joins your group and he (or she) has to add a new class to the project. One day he sends you email asking, "How do I make this class serializable?" If this functionality is abstracted into a class, your answer could simply be, "Derive your class from Serializable." That's it! No further explanation is necessary.

<p>There is however a catch. What if your class is already derived from some other class? Now it will have to inherit from that class <i>and</i> from Serializable. This is exactly the case in which multiple inheritance can be put to work. In C++ a class may have more than one base class. The syntax for multiple inheritance is pretty straightforward:
<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>class MultiDerived: public Base1, public Base2</pre>
</td></tr></table><!-- End Code -->
<p>Suppose, for instance, that you were not satisfied with treating <var>std::string</var> as a simple type, known to the <var>Serializer</var>. Instead, you'd like to create a separate type, a serializable string. Here's how you could do it, using multiple inheritance:

<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>using std::string;

class SerialString: public string, public Serializable
{
public:
    SerialString (std::string const &amp; str): string (str) {}
    void Serialize (Serializer &amp; out) const;
    void DeSerialize (DeSerializer &amp; in);
};</pre>
</td></tr></table><!-- End Code -->

<p>Multiple inheritance is particularly useful when deriving from abstract classes. This kind of inheritance deals with interface rather than implementation. In fact, this is exactly the restriction on multiple inheritance that's built into Java. In Java you can inherit only from one full-blown class, but you can add to it multiple inheritance from any number of interfaces (the equivalent of C++ abstract classes). In most cases this is indeed a very reasonable restriction. 

<p>Next: <a href="8trans.html" tppabs="http://www.relisoft.com/book/tech/8trans.html">transactions</a>.

    </td>
    </tr>
</table>
<!-- End Main Table -->
</body>
</html>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -