📄 [10] constructors, c++ faq lite.htm
字号:
<P>使用与<A
href="http://www.sunistudio.com/cppfaq/ctors.html#[10.12]">描述过的相同的技巧</A>,但这次使用静态成员函数而不是全局函数而已。
<P>假设类 <TT>X </TT>有一个<TT>static</TT> <TT>Fred</TT>对象:
<P>
<DIV
class=CodeBlock><TT> </TT><EM>// File X.hpp</EM><TT><BR> <BR> class X {<BR> public:<BR> </TT><EM>// ...</EM><TT><BR> <BR> private:<BR> static Fred x_;<BR> };
</TT></DIV>
<P>自然的,该静态成员被分开初始化:
<P>
<DIV
class=CodeBlock><TT> </TT><EM>// File X.cpp</EM><TT><BR> <BR> #include "X.hpp"<BR> <BR> Fred X::x_;
</TT></DIV>
<P>自然的,<TT>Fred</TT>对象会在 <TT>X </TT>的一个或多个方法中被使用:
<P>
<DIV
class=CodeBlock><TT> void X::someMethod()<BR> {<BR> x_.goBowling();<BR> }
</TT></DIV>
<P>但现在“灾难情景”就是如果某人在某处不知何故在<TT>Fred</TT>对象被构造前调用这个方法。例如,如果某人在静态初始化期间创建一个静态的 <TT>X
</TT>对象并调用它的<TT>someMethod()</TT>方法,然后你就受制于编译器是在<TT>someMethod()</TT>被调用之前或之后构造
<TT>X::x_</TT>。(ANSI/ISO C++委员会正在设法解决这个问题,但诸多的编译器对处理这些更改一般还没有完成;关注此处将来的更新。)
<P>无论何种结果,将<TT>X::x_</TT> 静态数据成员改为静态成员函数总是最简便和安全的:
<P>
<DIV
class=CodeBlock><TT> </TT><EM>// File X.hpp</EM><TT><BR> <BR> class X {<BR> public:<BR> </TT><EM>// ...</EM><TT><BR> <BR> private:<BR> static Fred& x();<BR> };
</TT></DIV>
<P>自然的,该静态成员被分开初始化:
<P>
<DIV
class=CodeBlock><TT> </TT><EM>// File X.cpp</EM><TT><BR> <BR> #include "X.hpp"<BR> <BR> Fred& X::x()<BR> {<BR> static Fred* ans = new Fred();<BR> return *ans;<BR> }
</TT></DIV>
<P>然后,简单地将 <TT>x_ </TT>改为 <TT>x()</TT>:
<P>
<DIV
class=CodeBlock><TT> void X::someMethod()<BR> {<BR> x().goBowling();<BR> }
</TT></DIV>
<P>如果你对性能敏感并且关心每次调用<TT>X::someMethod()</TT>的额外的函数调用的开销,你可以设置一个<TT>static</TT>
<TT>Fred&</TT>来取代。正如你所记得的,静态局部对象仅被初始化一次(控制流程首次越过它们的声明处时),因此,将只调用<TT>X::x()</TT>一次:<TT>X::someMethod()</TT>首次被调用时:
<P>
<DIV
class=CodeBlock><TT> void X::someMethod()<BR> {<BR> static Fred& x = X::x();<BR> x.goBowling();<BR> }
</TT></DIV>
<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.14]></A>
<DIV class=FaqTitle>
<H3>[10.14] 如何处理构造函数的失败?</H3></DIV>
<P>抛出一个异常。详见 <A
href="http://www.sunistudio.com/cppfaq/exceptions.html#[17.2]">[17.2]</A>。
<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.15]></A>
<DIV class=FaqTitle>
<H3>[10.15] 什么是“命名参数用法(Named Parameter Idiom)”?<IMG alt=NEW!
src="[10] Constructors, C++ FAQ Lite.files/new.gif"></H3></DIV><SMALL><EM>[Recently
created (on 4/01). <A
href="http://www.sunistudio.com/cppfaq/dtors.html#[11.13]">Click here to go to
the next FAQ in the "chain" of recent
changes<!--rawtext:[11.13]:rawtext--></A>.]</EM></SMALL>
<P>发掘<A
href="http://www.sunistudio.com/cppfaq/references.html#[8.4]">方法链</A>的非常有用的方法。
<P>命名参数用法(Named Parameter
Idiom)解决的最基本问题是C++仅支持位置相关的参数。例如,函数调用者不能说“这个值给形参<TT>xyz</TT>,另一个值给形参<TT>pqr</TT>”。在C++(和C
和Java)中只能说“这是第一个参数,这是第二个参数等”。Ada语言提出并实现的命名参数,对于带有大量的可缺省参数的函数尤其有用。
<P>多年来,人们构造了很多方案来弥补C 和
C++缺乏的命名参数。其中包括将参数值隐藏于一个字符串参数,然后在运行时解析这个字符串。例如,这就是<TT>fopen()</TT>的第二个参数的做法。另一种方案是将所有的布尔参数联合成一个位映射,然后调用者将这堆转换成位的常量共同产生一个实际的参数。例如,这就是<TT>open()</TT>的第二个参数的做法。这些方法可以工作,但下面的技术产生的调用者的代码更明显,更容易写,更容易读,而且一般来说更雅致。
<P>这个想法,称为命名参数用法(Named Parameter
Idiom),它是将函数的参数变为以新的方式创建的类的方法,这些方法通过引用返回<TT>*this</TT>。然后你只要将主要的函数改名为那个类中的无参数的“随意”方法。
<P>举一个例子来解释上面那段。
<P>这个例子实现“打开一个文件”的概念。该概念逻辑上需要一个文件名的参数,和一些允许选择的参数,文件是否被只读或可读写或只写的方式打开;如果文件不存在,是否创建它;是从末尾写(添加"append")还是从起始处写(覆盖"overwrite");如果文件被创建,指定块大小;
I/O是否有缓冲区,缓冲区大小;文件是被共享还是独占访问;以及其他可能的选项。如果我们用常规的位置相关的参数的函数实现这个概念,那么调用者的代码会非常难读:有8个可选的参数,并且调用者很可能犯错误。因此我们使用命名参数用法来取代。
<P>在实现它之前,假如你想接受函数的所有默认参数,看一下调用者的代码是什么样子:
<P>
<DIV class=CodeBlock><TT> File f = OpenFile("foo.txt");
</TT></DIV>
<P>那是简单的情况。现在看一下如果你想改变一大堆的参数:
<P>
<DIV
class=CodeBlock><TT> File f = OpenFile("foo.txt").<BR> readonly().<BR> createIfNotExist().<BR> appendWhenWriting().<BR> blockSize(1024).<BR> unbuffered().<BR> exclusiveAccess();
</TT></DIV>
<P>注意这些“参数”,被公平的以随机的顺序(位置无关的)调用并且都有名字。因此,程序员不必记住参数的顺序,而且这些名字是(正如所希望的)意义明显的。
<P>以下是如何实现:首先创建一个新的类(<TT>OpenFile</TT>),该类包含了所有的参数值作为 <TT>private:
</TT>数据成员。然后所有的方法(<TT>readonly()</TT>, <TT>blockSize(unsigned)</TT>,
等)返回<TT>*this</TT>(也就是返回一个<TT>OpenFile</TT>对象的引用,以允许方法被<A
href="http://www.sunistudio.com/cppfaq/references.html#[8.4]">链状</A>调用)。最后完成一个带有必要参数(在这里,就是文件名)的常规的,参数位置相关的<TT>OpenFile</TT>的构造函数。
<P>
<DIV
class=CodeBlock><TT> class File;<BR> <BR> class OpenFile {<BR> public:<BR> OpenFile(const string& filename);<BR> </TT><EM>// 为每个数据成员设置默认值</EM><TT><BR> OpenFile& readonly(); </TT><EM>// 将 <TT>readonly_</TT> 变为 <TT>true</TT></EM><TT><BR> OpenFile& createIfNotExist();<BR> OpenFile& blockSize(unsigned nbytes);<BR> </TT><EM>// ...</EM><TT><BR> private:<BR> friend File;<BR> bool readonly_; </TT><EM>// 默认为 <TT>false</TT> [举例]</EM><TT><BR> </TT><EM>// ...</EM><TT><BR> unsigned blockSize_; </TT><EM>// 默认为
4096 [举例]</EM><TT><BR> </TT><EM>// ...</EM><TT><BR> };
</TT></DIV>
<P>要做的另外一件事就是使得 <TT>File</TT>的构造函数带一个<TT>OpenFile</TT>对象:
<P>
<DIV
class=CodeBlock><TT> class File {<BR> public:<BR> File(const OpenFile& params);<BR> </TT><EM>// vacuums the actual params out of the OpenFile object</EM><TT><BR> <BR> </TT><EM>// ...</EM><TT><BR> };
</TT></DIV>
<P>注意<TT>OpenFile</TT> 将 <TT>File </TT>声明为<A
href="http://www.sunistudio.com/cppfaq/friends.html">友元</A>,这样<A
href="http://www.sunistudio.com/cppfaq/friends.html#[14.2]"><TT>OpenFile</TT>
就不需要一大堆的(而且没用的)<TT>public:</TT> <EM>get</EM> 方法</A>。
<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=bottom></A><A href="mailto:cline@parashift.com"><IMG height=26
alt=E-Mail src="[10] Constructors, C++ FAQ Lite.files/mbox.gif"
width=89> E-mail the author</A><BR>[ <A
href="http://www.sunistudio.com/cppfaq/index.html"><EM>C++ FAQ Lite</EM></A>
| <A
href="http://www.sunistudio.com/cppfaq/index.html#table-of-contents">Table of contents</A>
| <A
href="http://www.sunistudio.com/cppfaq/subject-index.html">Subject index</A>
| <A
href="http://www.sunistudio.com/cppfaq/copy-permissions.html#[1.1]">About the author</A>
| <A
href="http://www.sunistudio.com/cppfaq/copy-permissions.html#[1.2]">©</A>
| <A
href="http://www.sunistudio.com/cppfaq/on-line-availability.html#[2.2]">Download your own copy</A> ]<BR><SMALL>Revised
Apr 8, 2001</SMALL> </P></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -