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

📄 ec5.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 5 页
字号:
  string dm1, dm2, dm3;         // derived members 1-3};This constructor certainly looks like an excellent candidate for inlining, since it contains no code. But looks can be deceiving. Just because it contains no code doesn't necessarily mean it contains no code. In fact, it may contain a fair amount of code.C++ makes various guarantees about things that happen when objects are created and destroyed. Items 5 and M8 describes how when you use new, your dynamically created objects are automatically initialized by their constructors, and how when you use delete, the corresponding destructors are invoked. Item 13 explains that when you create an object, each base class of and each data member in that object is automatically constructed, and the reverse process regarding destruction automatically occurs when an object is destroyed. Those items describe what C++ says must happen, but C++ does not say how they happen. That's up to compiler implementers, but it should be clear that those things don't just happen by themselves. There has to be some code in your program to make those things happen, and that code the code written by compiler implementers and inserted into your program during compilation has to go somewhere. Sometimes, it ends up in your constructors and destructors, so some implementations will generate code equivalent to the following for the allegedly empty Derived constructor above: // possible implementation of Derived constructor Derived::Derived(){  // allocate heap memory for this object if it's supposed  // to be on the heap; see Item 8 for info on operator new  if (this object is on the heap)    this = ::operator new(sizeof(Derived));  Base::Base();                  // initialize Base part  dm1.string();	 	         // construct dm1  dm2.string();	 	         // construct dm2  dm3.string();	 	         // construct dm3}You could never hope to get code like this to compile, because it's not legal C++ not for you, anyway. For one thing, you have no way of asking whether an object is on the heap from inside its constructor. (For an examination of what it takes to reliably determine whether an object is on the heap, see Item M27.) For another, you're forbidden from assigning to this. And you can't invoke constructors via function calls, either. Your compilers, however, labor under no such constraints they can do whatever they like. But the legality of the code is not the point. The point is that code to call operator new (if necessary), to construct base class parts, and to construct data members may be silently inserted into your constructors, and when it is, those constructors increase in size, thus making them less attractive candidates for inlining. Of course, the same reasoning applies to the Base constructor, so if it's inlined, all the code inserted into it is also inserted into the Derived constructor (via the Derived constructor's call to the Base constructor). And if the string constructor also happens to be inlined, the Derived constructor will gain five copies of that function's code, one for each of the five strings in a Derived object (the two it inherits plus the three it declares itself). Now do you see why it's not necessarily a no-brain decision whether to inline Derived's constructor? Of course, similar considerations apply to Derived's destructor, which, one way or another, must see to it that all the objects initialized by Derived's constructor are properly destroyed. It may also need to free the dynamically allocated memory formerly occupied by the just-destroyed Derived object.Library designers must evaluate the impact of declaring functions inline, because inline functions make it impossible to provide binary upgrades to the inline functions in a library. In other words, if f is an inline function in a library, clients of the library compile the body of f into their applications. If a library implementer later decides to change f, all clients who've used f must recompile. This is often highly undesirable (see also Item 34). On the other hand, if f is a non-inline function, a modification to f requires only that clients relink. This is a substantially less onerous burden than recompiling and, if the library containing the function is dynamically linked, one that may be absorbed in a way that's completely transparent to clients.Static objects inside inline functions often exhibit counterintuitive behavior. For this reason, it's generally a good idea to avoid declaring functions inline if those functions contain static objects. For details, consult Item M26.For purposes of program development, it is important to keep all these considerations in mind, but from a purely practical point of view during coding, one fact dominates all others: most debuggers have trouble with inline functions.This should be no great revelation. How do you set a breakpoint in a function that isn't there? How do you step through such a function? How do you trap calls to it? Without being unreasonably clever (or deviously underhanded), you simply can't. Happily, this leads to a logical strategy for determining which functions should be declared inline and which should not.Initially, don't inline anything, or at least limit your inlining to those functions that are truly trivial, such as age below: class Person {public:  int age() const { return personAge; }  ...private:  int personAge;  ...};By employing inlines cautiously, you facilitate your use of a debugger, but you also put inlining in its proper place: as a hand-applied optimization. Don't forget the empirically determined rule of 80-20 (see Item M16), which states that a typical program spends 80 percent of its time executing only 20 percent of its code. It's an important rule, because it reminds you that your goal as a software developer is to identify the 20 percent of your code that is actually capable of increasing your program's overall performance. You can inline and otherwise tweak your functions until the cows come home, but it's all wasted effort unless you're focusing on the right functions.Once you've identified the set of important functions in your application, the ones whose inlining will actually make a difference (a set that is itself dependent on the architecture on which you're running), don't hesitate to declare them inline. At the same time, however, be on the lookout for problems caused by code bloat, and watch out for compiler warnings (see Item 48) that indicate that your inline functions haven't been inlined.Used judiciously, inline functions are an invaluable component of every C++ programmer's toolbox, but, as the foregoing discussion has revealed, they're not quite as simple and straightforward as you might have thought. Back to Item 33: Use inlining judiciously.Continue to Inheritance and Object-Oriented DesignItem 34: Minimize compilation dependencies between files.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 make (or its moral equivalent), and you are astonished, then mortified, as you realize that the whole world is being recompiled and relinked!Don't you just hate it when that happens?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 example: class Person {public:  Person(const string& name, const Date& birthday,         const Address& addr, const Country& country);  virtual ~Person();  ...                      // copy constructor and assignment                           // operator omitted for simplicity  string name() const;  string birthDate() const;  string address() const;  string nationality() const;private:  string name_;            // implementation detail  Date birthDate_;         // implementation detail  Address address_;        // implementation detail  Country citizenship_;    // implementation detail};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 Person can't be compiled unless the compiler also has access to definitions for the classes in terms of which Person is implemented, namely, string, Date, Address, and Country. Such definitions are typically provided through #include directives, so at the top of the file defining the Person class, you are likely to find something like this: #include <string>           // for type string (see Item 49)#include "date.h"#include "address.h"#include "country.h"Unfortunately, this sets up a compilation dependency between the file defining Person 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 its implementation, the file containing the Person class must be recompiled, as must any files that use the Person class. For clients of Person, this can be more than annoying. It can be downright incapacitating.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 Person this way, class string;         // "conceptual" forward declaration for the                      // string type. See Item 49 for details.class Date;           // forward declarationclass Address;        // forward declarationclass Country;        // forward declarationclass Person {public:  Person(const string& name, const Date& birthday,         const Address& addr, const Country& country);  virtual ~Person();  ...                      // copy ctor, operator=  string name() const;  string birthDate() const;  string address() const;  string nationality() const;};specifying the implementation details of the class separately? If that were possible, clients of Person 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 effort.Alas, the real world intrudes on this idyllic scenario, as you will appreciate when you consider something like this: int main(){  int x;                      // define an int  Person p(...);              // define a Person                              // (arguments omitted for  ...                         // simplicity)}When compilers see the definition for x, they know they must allocate enough space to hold an int. No problem. Each compiler knows how big an int is. When compilers see the definition for p, however, they know they have to allocate enough space for a Person, but how are they supposed to know how big a Person object is? The only way they can get that information is to consult the class definition, but if it were legal for a class definition to omit the implementation details, how would compilers know how much space to allocate?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 pointer to an object when an object is defined. That is, they handle the code above as if it had been written like this: int main(){  int x;                     // define an int  Person *p;                 // define a pointer                             // to a Person  ...}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 yourself.Here's how you employ the technique to decouple Person's interface from its implementation. First, you put only the following in the header file declaring the Person class: // compilers still need to know about these type// names for the Person constructorclass string;      // again, see Item 49 for information                   // on why this isn't correct for stringclass Date;class Address;class Country;// class PersonImpl will contain the implementation// details of a Person object; this is just a// forward declaration of the class nameclass PersonImpl;class Person {public:  Person(const string& name, const Date& birthday,         const Address& addr, const Country& country);  virtual ~Person();  ...                               // copy ctor, operator=  string name() const;  string birthDate() const;

⌨️ 快捷键说明

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