📄 ec7.htm
字号:
<UL><PRE>class Month {
public:
static const Month Jan() { return 1; }
static const Month Feb() { return 2; }
...
static const Month Dec() { return 12; }
</PRE>
</UL><A NAME="223054"></A>
<UL><PRE>
int asInt() const // for convenience, make
{ return monthNumber; } // it possible to convert
// a Month to an int
</PRE>
</UL><A NAME="223055"></A>
<UL><PRE>private:
Month(int number): monthNumber(number) {}
</PRE>
</UL><A NAME="223056"></A>
<UL><PRE> const int monthNumber;
};
</PRE>
</UL><A NAME="223059"></A>
<UL><PRE>class Date {
public:
Date(int day, const Month& month, int year);
...
};
</PRE>
</UL><A NAME="223061"></A>
<P><A NAME="dingp32"></A>
Several aspects of this design combine to make it work the way it does. First, the <CODE>Month</CODE> constructor is private. This prevents clients from creating new months. The only ones available are those returned by <CODE>Month</CODE>'s static member functions, plus copies thereof. Second, each <CODE>Month</CODE> object is <CODE>const</CODE>, so it can't be changed. (Otherwise the temptation to transform January into June might sometimes prove overwhelming, at least in northern latitudes.) Finally, the only way to get a <CODE>Month</CODE> object is by calling a function or by copying an existing <CODE>Month</CODE> (via the implicit <CODE>Month</CODE> copy constructor — see <A HREF="#8160">Item 45</A>). This makes it possible to use <CODE>Month</CODE> objects anywhere and anytime; there's no need to worry about accidently using one before it's been initialized. (<A HREF="#8299">Item 47</A> explains why this might otherwise be a <NOBR>problem.)<SCRIPT>create_link(32);</SCRIPT>
</NOBR></P>
<A NAME="8275"></A>
<P><A NAME="dingp33"></A>
<A NAME="p219"></A>Given these classes, it is all but impossible for a client to specify an invalid month. It would be completely impossible were it not for the following <NOBR>abomination:<SCRIPT>create_link(33);</SCRIPT>
</NOBR></P>
<A NAME="24937"></A>
<UL><PRE>Month *pm; // define uninitialized ptr
</PRE>
</UL><A NAME="24942"></A>
<UL><PRE>Date d(1, *pm, 1997); // arghhh! use it!
</PRE>
</UL><A NAME="24943"></A>
<P><A NAME="dingp34"></A>
However, this involves dereferencing an uninitialized pointer, the results of which are undefined. (See <A HREF="./EC1_FR.HTM#1838" TARGET="_top">Item 3</A> for my feelings about undefined behavior.) Unfortunately, I know of no way to prevent or detect this kind of heresy. However, if we assume this never happens, or if we don't care how our software behaves if it does, the <CODE>Date</CODE> constructor can dispense with sanity checking on its <CODE>Month</CODE> parameter. On the other hand, the constructor must still check the <CODE>day</CODE> parameter for validity — how many days hath September, April, June, and <NOBR>November?<SCRIPT>create_link(34);</SCRIPT>
</NOBR></P>
<A NAME="8285"></A>
<P><A NAME="dingp35"></A>
This <CODE>Date</CODE> example replaces runtime checks with compile-time checks. You may be wondering when it is possible to use link-time checks. In truth, not very often. C++ uses the linker to ensure that needed functions are defined exactly once (see <A HREF="#8160">Item 45</A> for a description of what it takes to "need" a function). It also uses the linker to ensure that static objects (see <A HREF="#8299">Item 47</A>) are defined exactly once. You'll tend to use the linker in the same way. For example, <A HREF="./EC4_FR.HTM#6406" TARGET="_top">Item 27</A> describes how the linker's checks can make it useful to deliberately avoid defining a function you explicitly <NOBR>declare.<SCRIPT>create_link(35);</SCRIPT>
</NOBR></P>
<A NAME="8293"></A>
<P><A NAME="dingp36"></A>
Now don't get carried away. It's impractical to eliminate the need for <I>all</I> runtime checking. Any program that accepts interactive input, for example, is likely to have to validate that input. Similarly, a class implementing arrays that perform bounds checking (see <A HREF="./EC4_FR.HTM#17774" TARGET="_top">Item 18</A>) is usually going to have to validate the array index against the bounds every time an array access is made. Nonetheless, shifting checks from runtime to compile- or link-time is always a worthwhile goal, and you should pursue that goal whenever it is practical. Your reward for doing so is programs that are smaller, faster, and more <NOBR>reliable.<SCRIPT>create_link(36);</SCRIPT>
</NOBR></P>
<!-- SectionName="E47: Initialization of non-local static objects" -->
<A NAME="8300"></A><A NAME="8299"></A>
<DIV ALIGN="CENTER"><FONT SIZE="-1">Back to <A HREF="#195225">Item 46: Prefer compile-time and link-time errors to runtime errors.</A>
<BR>Continue to <A HREF="#8378">Item 48: Pay attention to compiler warnings.</A></FONT></DIV>
<P><A NAME="dingp37"></A><FONT ID="eititle">Item 47: Ensure that non-local static objects are initialized before they're used.</FONT><SCRIPT>create_link(37);</SCRIPT>
</P>
<P><A NAME="dingp38"></A>
You're an adult now, so you don't need me to tell you it's foolhardy to use an object before it's been initialized. In fact, the whole notion may <A NAME="p220"></A>strike you as absurd; constructors make sure objects are initialized when they're created, <I>n'est-ce pas</I>?<SCRIPT>create_link(38);</SCRIPT>
</P>
<A NAME="8303"></A>
<P><A NAME="dingp39"></A>
Well, yes and no. Within a particular translation unit (i.e., source file), everything works fine, but things get trickier when the initialization of an object in one translation unit depends on the value of another object in a different translation unit <i>and</i> that second object itself requires <NOBR>initialization.<SCRIPT>create_link(39);</SCRIPT>
</NOBR></P>
<A NAME="30382"></A>
<P><A NAME="dingp40"></A>
For example, suppose you've authored a library offering an abstraction of a file system, possibly including such capabilities as making files on the Internet look like they're local. Since your library makes the world look like a single file system, you might create a special object, <CODE>theFileSystem</CODE>, within your library's namespace (see <A HREF="./EC4_FR.HTM#6429" TARGET="_top">Item 28</A>) for clients to use whenever they need to interact with the file system abstraction your library <NOBR>provides:<SCRIPT>create_link(40);</SCRIPT>
</NOBR></P>
<A NAME="30547"></A>
<UL><PRE>
class FileSystem { ... }; // this class is in your
// library
</PRE>
</UL><A NAME="30587"></A>
<UL><PRE>
FileSystem theFileSystem; // this is the object
// with which library
// clients interact
</PRE>
</UL><A NAME="30588"></A>
<P><A NAME="dingp41"></A>
Because <CODE>theFileSystem</CODE> represents something complicated, it's no surprise that its construction is both nontrivial and essential; use of <CODE>theFileSystem</CODE> before it had been constructed would yield <I>very</I> undefined behavior. (However, consult <A HREF="../MEC/MC4_FR.HTM#41011" TARGET="_top">Item M17</A> for ideas on how the effective initialization of objects like <CODE>theFileSystem</CODE> can safely be <NOBR>delayed.)<SCRIPT>create_link(41);</SCRIPT>
</NOBR></P>
<A NAME="30564"></A>
<P><A NAME="dingp42"></A>
Now suppose some client of your library creates a class for directories in a file system. Naturally, their class uses <CODE>theFileSystem</CODE>:<SCRIPT>create_link(42);</SCRIPT>
</P>
<A NAME="30565"></A>
<UL><PRE>
class Directory { // created by library client
public:
Directory();
...
};
</PRE>
</UL><A NAME="30566"></A>
<UL><PRE>Directory::Directory()
{
<I>create a</I> Directory <I>object by invoking member
functions on</I> theFileSystem;
}
</PRE>
</UL><A NAME="30567"></A>
<P><A NAME="dingp43"></A>
Further suppose this client decides to create a distinguished global <CODE>Directory</CODE> object for temporary <NOBR>files:<SCRIPT>create_link(43);</SCRIPT>
</NOBR></P>
<A NAME="30573"></A>
<UL><PRE>
Directory tempDir; // directory for temporary
// files
</PRE>
</UL><A NAME="30574"></A>
<P><A NAME="dingp44"></A>
<A NAME="p221"></A>Now the problem of initialization order becomes apparent: unless <CODE>theFileSystem</CODE> is initialized before <CODE>tempDir</CODE>, <CODE>tempDir</CODE>'s constructor will attempt to use <CODE>theFileSystem</CODE> before it's been initialized. But <CODE>theFileSystem</CODE> and <CODE>tempDir</CODE> were created by different people at different times in different files. How can you be sure that <CODE>theFileSystem</CODE> will be created before <CODE>tempDir</CODE>?<SCRIPT>create_link(44);</SCRIPT>
</P>
<A NAME="8313"></A>
<P><A NAME="dingp45"></A>
This kind of question arises anytime you have <I>non-local static objects</I> that are defined in different translation units and whose correct behavior is dependent on their being initialized in a particular order. Non-local static objects are objects that <NOBR>are<SCRIPT>create_link(45);</SCRIPT>
</NOBR></P>
<UL><A NAME="24055"></A>
<A NAME="dingp46"></A><LI>defined at global or namespace scope (e.g., <CODE>theFileSystem</CODE> and <CODE>tempDir</CODE>),<SCRIPT>create_link(46);</SCRIPT>
<A NAME="30829"></A>
<A NAME="dingp47"></A><LI>declared <CODE>static</CODE> in a class, or<SCRIPT>create_link(47);</SCRIPT>
<A NAME="24056"></A>
<A NAME="dingp48"></A><LI>defined <CODE>static</CODE> at file scope.<SCRIPT>create_link(48);</SCRIPT>
</UL></P>
<A NAME="195248"></A>
<P><A NAME="dingp49"></A>
Regrettably, there is no shorthand term for "non-local static objects," so you should accustom yourself to this somewhat awkward <NOBR>phrase.<SCRIPT>create_link(49);</SCRIPT>
</NOBR></P>
<A NAME="195249"></A>
<P><A NAME="dingp50"></A>
You do not want the behavior of your software to be dependent on the initialization order of non-local static objects in different translation units, because you have no control over that order. Let me repeat that. <I>You have absolutely no control over the order in which non-local static objects in different translation units are initialized.</I><SCRIPT>create_link(50);</SCRIPT>
</P>
<A NAME="24544"></A>
<P><A NAME="dingp51"></A>
It is reasonable to wonder why this is the <NOBR>case.<SCRIPT>create_link(51);</SCRIPT>
</NOBR></P>
<A NAME="223072"></A>
<P><A NAME="dingp52"></A>
It is the case because determining the "proper" order in which to initialize non-local static objects is hard. Very hard. Halting-Problem hard. In its most general form — with multiple translation units and non-local static objects generated through implicit template instantiations (which may themselves arise via implicit template instantiations) — it's not only impossible to determine the right order of initialization, it's typically not even worth looking for special cases where it is possible to determine the right <NOBR>order.<SCRIPT>create_link(52);</SCRIPT>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -