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

📄 chapter11.html

📁 Thinking in c++ 2nd edition,c++编程思想(第2版)
💻 HTML
📖 第 1 页 / 共 5 页
字号:
saving and restoring all the registers that are used in the ISR, but if the ISR
needs to use any memory further down on the stack, this must be a safe thing to
do. (You can think of an ISR as an ordinary function with no arguments and
<B>void</B> return value that saves and restores the CPU state. An ISR function
call is triggered by some hardware event instead of an explicit call from within
a program.)</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Now imagine what would happen if an
ordinary function tried to return values on the stack. You can&#8217;t touch any
part of the stack that&#8217;s above the return address, so the function would
have to push the values below the return address. But when the assembly-language
RETURN is executed, the stack pointer must be pointing to the return address (or
right below it, depending on your machine), so right before the RETURN, the
function must move the stack pointer up, thus clearing off all its local
variables. If you&#8217;re trying to return values on the stack below the return
address, you become vulnerable at that moment because an interrupt could come
along. The ISR would move the stack pointer down to hold its return address and
its local variables and overwrite your return value.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">To solve this problem, the caller
<I>could</I> be responsible for allocating the extra storage on the stack for
the return values before calling the function. However, C was not designed this
way, and C++ must be compatible. As you&#8217;ll see shortly, the C++ compiler
uses a more efficient scheme.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Your next idea might be to return the
value in some global data area, but this doesn&#8217;t work either. Reentrancy
means that any function can be an interrupt routine for any other function,
<I>including the same function you&#8217;re currently inside</I>. Thus, if you
put the return value in a global area, you might return into the same function,
which would overwrite that return value. The same logic applies to
recursion<A NAME="Index1906"></A>.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The only safe place to return values is
in the registers, so you&#8217;re back to the problem of what to do when the
registers aren&#8217;t large enough to hold the return value. The answer is to
push the address of the return value&#8217;s destination on the stack as one of
the function arguments, and let the function copy the return information
directly into the destination. This not only solves all the problems, it&#8217;s
more efficient. It&#8217;s also the reason that, in
<B>PassingBigStructures.cpp</B>, the compiler pushes the address of <B>B2</B>
before the call to <B>bigfun(&#160;)</B> in <B>main(&#160;)</B>. If you look at
the assembly output for <B>bigfun(&#160;)</B>, you can see it expects this
hidden argument and performs the copy to the destination <I>inside</I> the
function.</FONT><BR></P></DIV>
<A NAME="Heading336"></A><FONT FACE = "Verdana"><H4 ALIGN="LEFT">
Bitcopy versus
initialization<BR><A NAME="Index1907"></A><A NAME="Index1908"></A></H4></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">So far, so good. There&#8217;s a workable
process for passing and returning large simple structures. But notice that all
you have is a way to copy the bits from one place to another, which certainly
works fine for the primitive way that C looks at variables. But in C++ objects
can be much more sophisticated than a patch of bits; they have meaning. This
meaning may not respond well to having its bits copied.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Consider a simple example: a class that
knows how many objects of its type exist at any one time. From Chapter 10, you
know the way to do this is by including a <B>static</B> data
member:</FONT><BR></P></DIV>

<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C11:HowMany.cpp</font>
<font color=#009900>// A class that counts its objects</font>
#include &lt;fstream&gt;
#include &lt;string&gt;
<font color=#0000ff>using</font> <font color=#0000ff>namespace</font> std;
ofstream out(<font color=#004488>"HowMany.out"</font>);

<font color=#0000ff>class</font> HowMany {
  <font color=#0000ff>static</font> <font color=#0000ff>int</font> objectCount;
<font color=#0000ff>public</font>:
  HowMany() { objectCount++; }
  <font color=#0000ff>static</font> <font color=#0000ff>void</font> print(<font color=#0000ff>const</font> string&amp; msg = <font color=#004488>""</font>) {
    <font color=#0000ff>if</font>(msg.size() != 0) out &lt;&lt; msg &lt;&lt; <font color=#004488>": "</font>;
    out &lt;&lt; <font color=#004488>"objectCount = "</font>
         &lt;&lt; objectCount &lt;&lt; endl;
  }
  ~HowMany() {
    objectCount--;
    print(<font color=#004488>"~HowMany()"</font>);
  }
};

<font color=#0000ff>int</font> HowMany::objectCount = 0;

<font color=#009900>// Pass and return BY VALUE:</font>
HowMany f(HowMany x) {
  x.print(<font color=#004488>"x argument inside f()"</font>);
  <font color=#0000ff>return</font> x;
}

<font color=#0000ff>int</font> main() {
  HowMany h;
  HowMany::print(<font color=#004488>"after construction of h"</font>);
  HowMany h2 = f(h);
  HowMany::print(<font color=#004488>"after call to f()"</font>);
} <font color=#009900>///:~</font></PRE></FONT></BLOCKQUOTE>

<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The class <B>HowMany</B> contains a
<B>static</B> <B>int objectCount</B> and a <B>static</B> member function
<B>print(&#160;)</B> to report the value of that <B>objectCount</B>, along with
an optional message argument. The constructor increments the count each time an
object is created, and the destructor decrements it.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The output, however, is not what you
would expect:</FONT><BR></P></DIV>

<BLOCKQUOTE><FONT SIZE = "+1"><PRE>after construction of h: objectCount = 1
x argument inside f(): objectCount = 1
~HowMany(): objectCount = 0
after call to f(): objectCount = 0
~HowMany(): objectCount = -1
~HowMany(): objectCount = -2</PRE></FONT></BLOCKQUOTE>

<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">After <B>h</B> is created, the object
count is one, which is fine. But after the call to <B>f(&#160;) </B>you would
expect to have an object count of two, because <B>h2</B> is now in scope as
well. Instead, the count is zero, which indicates something has gone horribly
wrong. This is confirmed by the fact that the two destructors at the end make
the object count go negative, something that should never
happen.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Look at the point inside
<B>f(&#160;)</B>, which occurs after the argument is passed by value. This means
the original object <B>h</B> exists outside the function frame, and
there&#8217;s an additional object <I>inside</I> the function frame, which is
the copy that has been passed by value. However, the argument has been passed
using C&#8217;s primitive notion of bitcopying, whereas the C++ <B>HowMany</B>
class requires true initialization to maintain its integrity, so the default
bitcopy fails to produce the desired effect.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">When the local object goes out of scope
at the end of the call to <B>f(&#160;)</B>, the destructor is called, which
decrements <B>objectCount</B>, so outside the function, <B>objectCount</B> is
zero. The creation of <B>h2</B> is also performed using a bitcopy, so the
constructor isn&#8217;t called there either, and when <B>h</B> and <B>h2</B> go
out of scope, their destructors cause the negative values of
<B>objectCount</B>.</FONT><A NAME="_Toc312373964"></A><A NAME="_Toc472654939"></A><BR></P></DIV>
<A NAME="Heading337"></A><FONT FACE = "Verdana"><H3 ALIGN="LEFT">
Copy-construction</H3></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The problem occurs because the compiler
makes an assumption about how to create <I>a new object from an existing
object<A NAME="Index1909"></A><A NAME="Index1910"></A><A NAME="Index1911"></A></I>.
When you <A NAME="Index1912"></A><A NAME="Index1913"></A>pass an object by
value, you create a new object, the passed object inside the function frame,
from an existing object, the original object outside the function frame. This is
also often true when returning an object from a function. In the expression
</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>HowMany h2 = f(h);</PRE></FONT></BLOCKQUOTE>

<DIV ALIGN="LEFT"><P><FONT FACE="Georgia"><B>h2</B>, a previously unconstructed
object, is created from the return value of <B>f(&#160;)</B>, so again a new
object is created from an existing one.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The compiler&#8217;s assumption is that
you want to perform this creation using a bitcopy, and in many cases this may
work fine, but in <B>HowMany</B> it doesn&#8217;t fly because the meaning of
initialization goes beyond simply copying. Another common example occurs if the
class contains pointers &#8211; what do they point to, and should you copy them
or should they be connected to some new piece of memory?</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Fortunately, you can intervene in this
process and prevent the compiler from doing a bitcopy. You do this by defining
your own function to be used whenever the compiler needs to make a new object
from an existing object. Logically enough, you&#8217;re making a new object, so
this function is a constructor, and also logically enough, the single argument
to this constructor has to do with the object you&#8217;re constructing from.
But that object can&#8217;t be passed into the constructor by value because
you&#8217;re trying to <I>define</I> the function that handles passing by value,
and syntactically it doesn&#8217;t make sense to pass a pointer because, after
all, you&#8217;re creating the new object from an existing object. Here,
references come to the rescue, so you take the reference of the source object.
This function is called the
<I>copy-constructor<A NAME="Index1914"></A><A NAME="Index1915"></A></I> and is
often referred to as <B>X(X&amp;)</B>, which is its appearance for a class
called <B>X</B>.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">If you create a copy-constructor, the
compiler will not perform a bitcopy when creating a new object from an existing
one. It will always call your copy-constructor. So, if you don&#8217;t create a
copy-constructor, the compiler will do something sensible, but you have the
choice of taking over complete control of the process.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Now it&#8217;s possible to fix the
problem in <B>HowMany.cpp</B>:</FONT><BR></P></DIV>

<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#009900>//: C11:HowMany2.cpp</font>
<font color=#009900>// The copy-constructor</font>
#include &lt;fstream&gt;
#include &lt;string&gt;
<font color=#0000ff>using</font> <font color=#0000ff>namespace</font> std;
ofstream out(<font color=#004488>"HowMany2.out"</font>);

<font color=#0000ff>class</font> HowMany2 {
  string name; <font color=#009900>// Object identifier</font>
  <font color=#0000ff>static</font> <font color=#0000ff>int</font> objectCount;
<font color=#0000ff>public</font>:
  HowMany2(<font color=#0000ff>const</font> string&amp; id = <font color=#004488>""</font>) : name(id) {
    ++objectCount;
    print(<font color=#004488>"HowMany2()"</font>);
  }
  ~HowMany2() {
    --objectCount;
    print(<font color=#004488>"~HowMany2()"</font>);
  }
  <font color=#009900>// The copy-constructor:</font>
  HowMany2(<font color=#0000ff>const</font> HowMany2&amp; h) : name(h.name) {
    name += <font color=#004488>" copy"</font>;
    ++objectCount;
    print(<font color=#004488>"HowMany2(const HowMany2&amp;)"</font>);
  }
  <font color=#0000ff>void</font> print(<font color=#0000ff>const</font> string&amp; msg = <font color=#004488>""</font>) <font color=#0000ff>const</font> {
    <font color=#0000ff>if</font>(msg.size() != 0) 
      out &lt;&lt; msg &lt;&lt; endl;
    out &lt;&lt; '\t' &lt;&lt; name &lt;&lt; <font color=#004488>": "</font>
        &lt;&lt; <font color=#004488>"objectCount = "</font>
        &lt;&lt; objectCount &lt;&lt; endl;
  }
};

<font color=#0000ff>int</font> HowMany2::objectCount = 0;

<font color=#009900>// Pass and return BY VALUE:</font>
HowMany2 f(HowMany2 x) {
  x.print(<font color=#004488>"x argument inside f()"</font>);
  out &lt;&lt; <font color=#004488>"Returning from f()"</font> &lt;&lt; endl;
  <font color=#0000ff>return</font> x;
}

<font color=#0000ff>int</font> main() {
  HowMany2 h(<font color=#004488>"h"</font>);
  out &lt;&lt; <font color=#004488>"Entering f()"</font> &lt;&lt; endl;
  HowMany2 h2 = f(h);

⌨️ 快捷键说明

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