📄 [10] constructors, c++ faq lite.htm
字号:
<P><SMALL>[ <A
href="http://www.sunistudio.com/cppfaq/ctors.html#top">Top</A> | <A
href="http://www.sunistudio.com/cppfaq/ctors.html#bottom">Bottom</A> | <A
href="http://www.sunistudio.com/cppfaq/inline-functions.html">Previous section</A>
| <A
href="http://www.sunistudio.com/cppfaq/dtors.html">Next section</A>
]</SMALL>
<HR>
<P><A name=[10.9]></A>
<DIV class=FaqTitle>
<H3>[10.9] 为何不能在构造函数的初始化列表中初始化静态成员数据?<IMG alt=UPDATED!
src="[10] Constructors, C++ FAQ Lite.files/updated.gif"></H3></DIV><SMALL><EM>[Recently
added a "," in the initialization list thanks to <A
href="mailto:tada@mail.wplus.net">Yaroslav Mironov</A> (on 4/01). <A
href="http://www.sunistudio.com/cppfaq/ctors.html#[10.15]">Click here to go to
the next FAQ in the "chain" of recent
changes<!--rawtext:[10.15]:rawtext--></A>.]</EM></SMALL>
<P>因为必须显式定义类的静态数据成员。
<P><TT>Fred.h</TT>:
<P>
<DIV
class=CodeBlock><TT> class Fred {<BR> public:<BR> Fred();<BR> </TT><EM>// ...</EM><TT><BR> private:<BR> int i_;<BR> static int j_;<BR> };
</TT></DIV>
<P><TT>Fred.cpp</TT> (或 <TT>Fred.C</TT> 或其他):
<P>
<DIV
class=CodeBlock><TT> Fred::Fred()<BR> : i_(10) </TT><EM>// 正确:能够(而且应该)这样初始化成员数据</EM><TT><BR> , j_(42) </TT><EM>// 错误:不能象这样初始化静态成员数据</EM><TT><BR> {<BR> </TT><EM>// ...</EM><TT><BR> }<BR> <BR> </TT><EM>// 必须这样定义静态数据成员:</EM><TT><BR> int Fred::j_ = 42;
</TT></DIV>
<P><SMALL>[ <A
href="http://www.sunistudio.com/cppfaq/ctors.html#top">Top</A> | <A
href="http://www.sunistudio.com/cppfaq/ctors.html#bottom">Bottom</A> | <A
href="http://www.sunistudio.com/cppfaq/inline-functions.html">Previous section</A>
| <A
href="http://www.sunistudio.com/cppfaq/dtors.html">Next section</A>
]</SMALL>
<HR>
<P><A name=[10.10]></A>
<DIV class=FaqTitle>
<H3>[10.10] 为何有静态数据成员的类得到了链接错误?</H3></DIV>
<P>因为<A
href="http://www.sunistudio.com/cppfaq/ctors.html#[10.9]">静态数据成员必须被显式定义在一个编辑单元中</A>。如果不这样做,你就可能得到<TT>"undefined external"</TT>链接错误。例如:
<P>
<DIV
class=CodeBlock><TT> </TT><EM>// Fred.h</EM><TT><BR> <BR> class Fred {<BR> public:<BR> </TT><EM>// ...</EM><TT><BR> private:<BR> static int j_; </TT><EM>// 声明静态数据成员:<TT>Fred::j_</TT></EM><TT><BR> </TT><EM>// ...</EM><TT><BR> };
</TT></DIV>
<P>链接器会向你抱怨(<TT>"Fred::j_ is not defined"</TT>),除非你在一个源文件中定义(而不仅仅是声明)<TT>Fred::j_</TT>:
<P>
<DIV
class=CodeBlock><TT> </TT><EM>// Fred.cpp</EM><TT><BR> <BR> #include "Fred.h"<BR> <BR> int Fred::j_ = some_expression_evaluating_to_an_int;<BR> <BR> </TT><EM>// Alternatively, if you wish to use the implicit 0 value for <TT>static</TT> <TT>int</TT>s:</EM><TT><BR> </TT><EM>// <TT>int Fred::j_;</TT></EM><TT>
</TT></DIV>
<P>通常定义<TT>Fred</TT>类的静态数据成员的地方是<TT>Fred.cpp</TT>文件(或者<TT>Fred.C</TT>或者你使用的其他扩展名)。
<P><SMALL>[ <A
href="http://www.sunistudio.com/cppfaq/ctors.html#top">Top</A> | <A
href="http://www.sunistudio.com/cppfaq/ctors.html#bottom">Bottom</A> | <A
href="http://www.sunistudio.com/cppfaq/inline-functions.html">Previous section</A>
| <A
href="http://www.sunistudio.com/cppfaq/dtors.html">Next section</A>
]</SMALL>
<HR>
<P><A name=[10.11]></A>
<DIV class=FaqTitle>
<H3>[10.11] 什么是“<TT>static</TT> initialization order fiasco”?</H3></DIV>
<P>你的项目的微妙杀手。
<P><EM><TT>static</TT> initialization order
fiasco</EM>是对C++的一个非常微妙的并且常见的误解。不幸的是,错误发生在<TT>main()</TT>开始之前,很难检测到。
<P>简而言之,假设你有存在于不同的源文件<TT>x.cpp</TT> 和<TT>y.cpp</TT>的两个静态对象<TT>x</TT> 和
<TT>y</TT>。再假定<TT>y</TT>对象的构造函数会调用<TT>x</TT>对象的某些方法。
<P>就是这些。就这么简单。
<P>结局是你完蛋不完蛋的机会是50%-50%。如果碰巧<TT>x.cpp</TT>的编辑单元先被初始化,这很好。但如果<TT>y.cpp</TT>的编辑单元先被初始化,然后<TT>y</TT>的构造函数比<TT>x</TT>的构造函数先运行。也就是说,<TT>y</TT>的构造函数会调用<TT>x</TT>对象的方法,而<TT>x</TT>对象还没有被构造。
<P>我听说他们受雇于了麦当劳。享受他们的切碎肉的新工作去了。
<P>如果你觉得在卧室的一角玩俄罗斯方块是令人兴奋的,你可以到此为止。相反,如果你想通过用一种系统的方法防止灾难,来提高自己存活的机会,你可能想阅读<A
href="http://www.sunistudio.com/cppfaq/ctors.html#[10.12]">下一个 FAQ<!--rawtext:[10.12]:rawtext--></A>。
<P>注意:static initialization order fiasco不作用于内建的/固有的类型,象<TT>int</TT> 或
<TT>char*</TT>。例如,如果创建一个<TT>static</TT>
<TT>float</TT>对象,不会有静态初始化次序的问题。静态初始化次序真正会崩溃的时机只有在你的<TT>static</TT>或全局对象有构造函数时。
<P><SMALL>[ <A
href="http://www.sunistudio.com/cppfaq/ctors.html#top">Top</A> | <A
href="http://www.sunistudio.com/cppfaq/ctors.html#bottom">Bottom</A> | <A
href="http://www.sunistudio.com/cppfaq/inline-functions.html">Previous section</A>
| <A
href="http://www.sunistudio.com/cppfaq/dtors.html">Next section</A>
]</SMALL>
<HR>
<P><A name=[10.12]></A>
<DIV class=FaqTitle>
<H3>[10.12] 如何防止“<TT>static</TT> initialization order fiasco”?</H3></DIV>
<P>使用“首次使用时构造(construct on first use)”用法,意思就是简单地将静态对象包裹于函数内部。
<P>例如,假设你有两个类,<TT>Fred</TT> 和
<TT>Barney</TT>。有一个称为<TT>x</TT>的全局<TT>Fred</TT>对象,和一个称为<TT>y</TT>的全局<TT>Barney</TT>对象。<TT>Barney</TT>的构造函数调用了<TT>x</TT>对象的<TT>goBowling()</TT>方法。
<TT>x.cpp</TT>文件定义了<TT>x</TT>对象:
<P>
<DIV
class=CodeBlock><TT> </TT><EM>// File x.cpp</EM><TT><BR> #include "Fred.hpp"<BR> Fred x;
</TT></DIV>
<P><TT>y.cpp</TT>文件定义了<TT>y</TT>对象:
<P>
<DIV
class=CodeBlock><TT> </TT><EM>// File y.cpp</EM><TT><BR> #include "Barney.hpp"<BR> Barney y;
</TT></DIV>
<P><TT>Barney</TT>构造函数的全部看起来可能是象这样的:
<P>
<DIV
class=CodeBlock><TT> </TT><EM>// File Barney.cpp</EM><TT><BR> #include "Barney.hpp"<BR> <BR> Barney::Barney()<BR> {<BR> </TT><EM>// ...</EM><TT><BR> x.goBowling();<BR> </TT><EM>// ...</EM><TT><BR> }
</TT></DIV>
<P>正如<A
href="http://www.sunistudio.com/cppfaq/ctors.html#[10.11]">以上</A>所描述的,由于它们位于不同的源文件,那么
<TT>y </TT>在 <TT>x </TT>之前构造而发生灾难的机率是50%。
<P>这个问题有许多解决方案,但一个非常简便的方案就是用一个返回<TT>Fred</TT>对象引用的全局函数<TT>x()</TT>,来取代全局的<TT>Fred</TT>对象
<TT>x</TT>。
<P>
<DIV
class=CodeBlock><TT> </TT><EM>// File x.cpp</EM><TT><BR> <BR> #include "Fred.hpp"<BR> <BR> Fred& x()<BR> {<BR> static Fred* ans = new Fred();<BR> return *ans;<BR> }
</TT></DIV>
<P>由于静态局部对象只在控制流第一次越过它们的声明时构造,因此以上的<TT>new Fred()</TT>语句只会执行一次:<TT>x()</TT>被第一次调用时。每个后续的调用将返回同一个<TT>Fred</TT>对象(<TT>ans</TT>指向的那个)。然后你所要做的就是将
<TT>x </TT>改成 <TT>x()</TT>:
<P>
<DIV
class=CodeBlock><TT> </TT><EM>// File Barney.cpp</EM><TT><BR> #include "Barney.hpp"<BR> <BR> Barney::Barney()<BR> {<BR> </TT><EM>// ...</EM><TT><BR> x().goBowling();<BR> </TT><EM>// ...</EM><TT><BR> }
</TT></DIV>
<P>由于该全局的<TT>Fred</TT>对象在首次使用时被构造,因此被称为<I>首次使用时构造用法(</I><EM>Construct On First
Use Idiom)</EM>
<P>这种方法的不利方面是<TT>Fred</TT>对象不会被析构。<EM>C++ FAQ Book</EM>有另一种技巧消除这个影响(但面临了“static
<EM>de</EM>-initialization order fiasco”的代价)。
<P>注意:对于内建/固有类型,象<TT>int</TT> 或
<TT>char*</TT>,不必这样做。例如,如果创建一个静态的或全局的<TT>float</TT>对象,不需要将它包裹于函数之中。静态初始化次序真正会崩溃的时机只有在你的<TT>static</TT>或全局对象有构造函数时。
<P><SMALL>[ <A
href="http://www.sunistudio.com/cppfaq/ctors.html#top">Top</A> | <A
href="http://www.sunistudio.com/cppfaq/ctors.html#bottom">Bottom</A> | <A
href="http://www.sunistudio.com/cppfaq/inline-functions.html">Previous section</A>
| <A
href="http://www.sunistudio.com/cppfaq/dtors.html">Next section</A>
]</SMALL>
<HR>
<P><A name=[10.13]></A>
<DIV class=FaqTitle>
<H3>[10.13] 对于静态数据成员,如何防止“<TT>static</TT> initialization order
fiasco”?</H3></DIV>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -