📄 ei34.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN" "http://www.w3.org/TR/REC-html40/frameset.dtd">
<HTML LANG="EN">
<HEAD>
<title>Effective C++, 2E | Item 34: Minimize compilation dependencies between files</TITLE>
<LINK REL=STYLESHEET HREF=../INTRO/ECMEC.CSS>
<SCRIPT LANGUAGE="Javascript" SRC="../JAVA/COOKIE.JS"></SCRIPT>
<SCRIPT LANGUAGE="Javascript">var imagemax = 0; setCurrentMax(0);</SCRIPT>
<SCRIPT LANGUAGE="Javascript" SRC="../JAVA/DINGBATS.JS"></SCRIPT>
<SCRIPT LANGUAGE="Javascript">
var dingbase = "EI34_DIR.HTM";
var dingtext = "Item E34, P";
if (self == top) {
top.location.replace(dingbase + this.location.hash);
}
</SCRIPT>
</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000" ONLOAD="setResize()">
<!-- SectionName="E34: Minimize compilation dependencies" -->
<A NAME="6793"></A>
<DIV ALIGN="CENTER"><FONT SIZE="-1">Back to <A HREF="./EI33_FR.HTM" TARGET="_top">Item 33: Use inlining judiciously.</A> <BR> Continue to <A HREF="./EINHERFR.HTM" TARGET="_top">Inheritance and Object-Oriented Design</A></FONT></DIV>
<P><A NAME="dingp1"></A><FONT ID="eititle">Item 34: Minimize compilation dependencies between files.</FONT><SCRIPT>create_link(1);</SCRIPT>
</P>
<A NAME="6794"></A>
<P><A NAME="dingp2"></A>
So you go into your C++ program and you make a minor change to the implementation of a class. Not the class interface, mind you, just the implementation; only the private stuff. Then you get set to rebuild the program, figuring that the compilation and linking should take only a few seconds. After all, only one class has been modified. You click on Rebuild or type <CODE>make</CODE> (or its moral equivalent), and you are astonished, then mortified, as you realize that the whole <I>world</I> is being recompiled and <NOBR>relinked!<SCRIPT>create_link(2);</SCRIPT>
</NOBR></P>
<A NAME="6795"></A>
<P><A NAME="dingp3"></A>
<A NAME="p144"></A>Don't you just <I>hate</I> it when that <NOBR>happens?<SCRIPT>create_link(3);</SCRIPT>
</NOBR></P>
<A NAME="6796"></A>
<P><A NAME="dingp4"></A>
The problem is that C++ doesn't do a very good job of separating interfaces from implementations. In particular, class definitions include not only the interface specification, but also a fair number of implementation details. For <NOBR>example:<SCRIPT>create_link(4);</SCRIPT>
</NOBR></P>
<A NAME="18778"></A>
<UL><PRE>class Person {
public:
Person(const string& name, const Date& birthday,
const Address& addr, const Country& country);
virtual ~Person();
</PRE>
</UL><A NAME="77098"></A>
<UL><PRE>
... // copy constructor and assignment
// operator omitted for simplicity
string name() const;
string birthDate() const;
string address() const;
string nationality() const;
</PRE>
</UL><A NAME="18761"></A>
<UL><PRE>private:
string name_; // implementation detail
Date birthDate_; // implementation detail
Address address_; // implementation detail
Country citizenship_; // implementation detail
};
</PRE>
</UL><A NAME="31849"></A>
<P><A NAME="dingp5"></A>
This is hardly a Nobel Prize-winning class design, although it does illustrate an interesting naming convention for distinguishing private data from public functions when the same name makes sense for both: the former are tagged with a trailing underbar. The important thing to observe is that class <CODE>Person</CODE> can't be compiled unless the compiler also has access to definitions for the classes in terms of which <CODE>Person</CODE> is implemented, namely, <CODE>string</CODE>, <CODE>Date</CODE>, <CODE>Address</CODE>, and <CODE>Country</CODE>. Such definitions are typically provided through <CODE>#include</CODE> directives, so at the top of the file defining the <CODE>Person</CODE> class, you are likely to find something like <NOBR>this:<SCRIPT>create_link(5);</SCRIPT>
</NOBR></P>
<A NAME="6805"></A>
<UL><PRE>#include <string> // for type string (see <A HREF="./EI49_FR.HTM#8392" TARGET="_top">Item 49</A>)
#include "date.h"
#include "address.h"
#include "country.h"
</PRE>
</UL><A NAME="6806"></A>
<P><A NAME="dingp6"></A>
Unfortunately, this sets up a compilation dependency between the file defining <CODE>Person</CODE> and these include files. As a result, if any of these auxiliary classes changes its implementation, or if any of the classes on which it depends changes <I>its</I> implementation, the file containing the <CODE>Person</CODE> class must be recompiled, as must any files that use the <CODE>Person</CODE> class. For clients of <CODE>Person</CODE>, this can be more than annoying. It can be downright <NOBR>incapacitating.<SCRIPT>create_link(6);</SCRIPT>
</NOBR></P>
<A NAME="6807"></A>
<P><A NAME="dingp7"></A>
<A NAME="p145"></A>You might wonder why C++ insists on putting the implementation details of a class in the class definition. For example, why can't you define <CODE>Person</CODE> this <NOBR>way,<SCRIPT>create_link(7);</SCRIPT>
</NOBR></P>
<A NAME="223077"></A>
<UL><PRE>
class string; // "conceptual" forward declaration for the
// string type. See <A HREF="./EI49_FR.HTM#8392" TARGET="_top">Item 49</A> for details.
</PRE>
</UL><A NAME="19504"></A>
<UL><PRE>
class Date; // forward declaration
class Address; // forward declaration
class Country; // forward declaration
</PRE>
</UL><A NAME="6810"></A>
<UL><PRE>class Person {
public:
Person(const string& name, const Date& birthday,
const Address& addr, const Country& country);
virtual ~Person();
</PRE>
</UL><A NAME="77101"></A>
<UL><PRE> ... // copy ctor, operator=
</PRE>
</UL><A NAME="6811"></A>
<UL><PRE> string name() const;
string birthDate() const;
string address() const;
string nationality() const;
};
</PRE>
</UL><A NAME="6812"></A>
<P><A NAME="dingp8"></A>
specifying the implementation details of the class separately? If that were possible, clients of <CODE>Person</CODE> would have to recompile only if the interface to the class changed. Because interfaces tend to stabilize before implementations do, such a separation of interface from implementation could save untold hours of recompilation and linking over the course of a large software <NOBR>effort.<SCRIPT>create_link(8);</SCRIPT>
</NOBR></P>
<A NAME="6813"></A>
<P><A NAME="dingp9"></A>
Alas, the real world intrudes on this idyllic scenario, as you will appreciate when you consider something like <NOBR>this:<SCRIPT>create_link(9);</SCRIPT>
</NOBR></P>
<A NAME="6814"></A>
<UL><PRE>int main()
{
int x; // define an int
</PRE>
</UL><A NAME="6815"></A>
<UL><PRE>
Person p(...); // define a Person
// (arguments omitted for
... // simplicity)
</PRE>
</UL><A NAME="6816"></A>
<UL><PRE>}
</PRE>
</UL><A NAME="6817"></A>
<P><A NAME="dingp10"></A>
When compilers see the definition for <CODE>x</CODE>, they know they must allocate enough space to hold an <CODE>int</CODE>. No problem. Each compiler knows how big an <CODE>int</CODE> is. When compilers see the definition for <CODE>p</CODE>, however, they know they have to allocate enough space for a <CODE>Person</CODE>, but how are they supposed to know how big a <CODE>Person</CODE> object is? The only way they can get that information is to consult the class definition, but if it were <A NAME="p146"></A>legal for a class definition to omit the implementation details, how would compilers know how much space to <NOBR>allocate?<SCRIPT>create_link(10);</SCRIPT>
</NOBR></P>
<A NAME="6818"></A>
<P><A NAME="dingp11"></A>
In principle, this is no insuperable problem. Languages such as Smalltalk, Eiffel, and Java get around it all the time. The way they do it is by allocating only enough space for a <I>pointer</I> to an object when an object is defined. That is, they handle the code above as if it had been written like <NOBR>this:<SCRIPT>create_link(11);</SCRIPT>
</NOBR></P>
<A NAME="6822"></A>
<UL><PRE>int main()
{
int x; // define an int
</PRE>
</UL><A NAME="6823"></A>
<UL><PRE>
Person *p; // define a pointer
// to a Person
...
}
</PRE>
</UL><A NAME="223140"></A>
<P><A NAME="dingp12"></A>
It may have occurred to you that this is in fact legal C++, and it turns out that you can play the "hide the object implementation behind a pointer" game <NOBR>yourself.<SCRIPT>create_link(12);</SCRIPT>
</NOBR></P>
<A NAME="223142"></A>
<P><A NAME="dingp13"></A>
Here's how you employ the technique to decouple <CODE>Person</CODE>'s interface from its implementation. First, you put only the following in the header file declaring the <CODE>Person</CODE> <NOBR>class:<SCRIPT>create_link(13);</SCRIPT>
</NOBR></P>
<A NAME="223082"></A>
<UL><PRE>// compilers still need to know about these type
// names for the Person constructor
class string; // again, see <A HREF="./EI49_FR.HTM#8392" TARGET="_top">Item 49</A> for information
// on why this isn't correct for string
class Date;
class Address;
class Country;
</PRE>
</UL><A NAME="6829"></A>
<UL><PRE>// class PersonImpl will contain the implementation
// details of a Person object; this is just a
// forward declaration of the class name
class PersonImpl;
</PRE>
</UL><A NAME="18910"></A>
<UL><PRE>class Person {
public:
Person(const string& name, const Date& birthday,
const Address& addr, const Country& country);
virtual ~Person();
</PRE>
</UL><A NAME="77102"></A>
<UL><PRE>
... // copy ctor, operator=
</PRE>
</UL><A NAME="6833"></A>
<UL><PRE> string name() const;
string birthDate() const;
string address() const;
string nationality() const;
</PRE>
</UL><A NAME="18913"></A>
<UL><PRE>private:
PersonImpl *impl; // pointer to implementation
};
</PRE>
</UL><A NAME="31248"></A>
<P><A NAME="dingp14"></A>
<A NAME="p147"></A>Now clients of <CODE>Person</CODE> are completely divorced from the details of strings, dates, addresses, countries, and persons. Those classes can be modified at will, but <CODE>Person</CODE> clients may remain blissfully unaware. More to the point, they may remain blissfully un-recompiled. In addition, because they're unable to see the details of <CODE>Person</CODE>'s implementation, clients are unlikely to write code that somehow depends on those details. This is a true separation of interface and <NOBR>implementation.<SCRIPT>create_link(14);</SCRIPT>
</NOBR></P>
<A NAME="18921"></A>
<P><A NAME="dingp15"></A>
The key to this separation is replacement of dependencies on class <I>definitions</I> with dependencies on class <I>declarations</I>. That's all you need to know about minimizing compilation dependencies: make your header files self-sufficient whenever it's practical, and when it's not practical, be dependent on class declarations, not class definitions. Everything else flows from this simple design <NOBR>strategy.<SCRIPT>create_link(15);</SCRIPT>
</NOBR></P>
<A NAME="18943"></A>
<P><A NAME="dingp16"></A>
There are three immediate <NOBR>implications:<SCRIPT>create_link(16);</SCRIPT>
</NOBR></P>
<UL><A NAME="18951"></A>
<B><A NAME="dingp17"></A><LI>Avoid using objects when object references and pointers will do.</B> You may define references and pointers to a type with only a <EM>declaration</EM> for the type. Defining <EM>objects</EM> of a type necessitates the presence of the type's definition.<SCRIPT>create_link(17);</SCRIPT>
<A NAME="18970"></A>
<B><A NAME="dingp18"></A><LI>Use class declarations instead of class definitions whenever you can.</B> Note that you <EM>never</EM> need a class definition to declare a function using that class, not even if the function passes or returns the class type by value:<SCRIPT>create_link(18);</SCRIPT>
<A NAME="18977"></A>
<UL><PRE>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -