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

📄 mi26.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 2 页
字号:
create Printer object p2;use p2;destroy p2;...This design never instantiates more than a single Printer object at a time, but it does use different Printer objects in different parts of the program. It somehow seems unreasonable that this isn't allowed. After all, at no point do we violate the constraint that only one printer may exist. Isn't there a way to make this legal?There is. All we have to do is combine the object-counting code we used earlier with the pseudo-constructors we just saw: class Printer {public:  class TooManyObjects{};  // pseudo-constructor  static Printer * makePrinter();~Printer();  void submitJob(const PrintJob& job);  void reset();  void performSelfTest();  ...private:  static size_t numObjects;  Printer();  Printer(const Printer& rhs);           // we don't define this};                                       // function, because we'll                                         // never allow copying                                         // (see Item E27)// Obligatory definition of class staticsize_t Printer::numObjects = 0;Printer::Printer(){  if (numObjects >= 1) {    throw TooManyObjects();  }  proceed with normal object construction here;  ++numObjects;}Printer * Printer::makePrinter(){ return new Printer; }If the notion of throwing an exception when too many objects are requested strikes you as unreasonably harsh, you could have the pseudo-constructor return a null pointer instead. Clients would then have to check for this before doing anything with it, of course.Clients use this Printer class just as they would any other class, except they must call the pseudo-constructor function instead of the real constructor: Printer p1;                                  // error! default ctor is                                             // privatePrinter *p2 =  Printer::makePrinter();                    // fine, indirectly calls                                             // default ctorPrinter p3 = *p2;                            // error! copy ctor is                                             // privatep2->performSelfTest();                       // all other functions arep2->reset();                                 // called as usual...delete p2;                                   // avoid resource leak; this                                             // would be unnecessary if                                             // p2 were an auto_ptrThis technique is easily generalized to any number of objects. All we have to do is replace the hard-wired constant 1 with a class-specific value, then lift the restriction against copying objects. For example, the following revised implementation of our Printer class allows up to 10 Printer objects to exist: class Printer {public:  class TooManyObjects{};  // pseudo-constructors  static Printer * makePrinter();  static Printer * makePrinter(const Printer& rhs);  ...private:  static size_t numObjects;  static const size_t maxObjects = 10;       // see below  Printer();  Printer(const Printer& rhs);};// Obligatory definitions of class staticssize_t Printer::numObjects = 0;const size_t Printer::maxObjects;Printer::Printer(){  if (numObjects >= maxObjects) {    throw TooManyObjects();  }  ...}Printer::Printer(const Printer& rhs){  if (numObjects >= maxObjects) {    throw TooManyObjects();  }  ...}Printer * Printer::makePrinter(){ return new Printer; }Printer * Printer::makePrinter(const Printer& rhs){ return new Printer(rhs); }Don't be surprised if your compilers get all upset about the declaration of Printer::maxObjects in the class definition above. In particular, be prepared for them to complain about the specification of 10 as an initial value for that variable. The ability to specify initial values for static const members (of integral type, e.g., ints, chars, enums, etc.) inside a class definition was added to C++ only relatively recently, so some compilers don't yet allow it. If your compilers are as-yet-unupdated, pacify them by declaring maxObjects to be an enumerator inside a private anonymous enum, class Printer {private:  enum { maxObjects = 10 };                  // within this class,  ...                                        // maxObjects is the};                                           // constant 10or by initializing the constant static like a non-const static member: class Printer {private:  static const size_t maxObjects;            // no initial value given  ...};// this goes in a single implementation fileconst size_t Printer::maxObjects = 10;This latter approach has the same effect as the original code above, but explicitly specifying the initial value is easier for other programmers to understand. When your compilers support the specification of initial values for const static members in class definitions, you should take advantage of that capability.An Object-Counting Base ClassInitialization of statics aside, the approach above works like the proverbial charm, but there is one aspect of it that continues to nag. If we had a lot of classes like Printer whose instantiations needed to be limited, we'd have to write this same code over and over, once per class. That would be mind-numbingly dull. Given a fancy-pants language like C++, it somehow seems we should be able to automate the process. Isn't there a way to encapsulate the notion of counting instances and bundle it into a class?We can easily come up with a base class for counting object instances and have classes like Printer inherit from that, but it turns out we can do even better. We can actually come up with a way to encapsulate the whole counting kit and kaboodle, by which I mean not only the functions to manipulate the instance count, but also the instance count itself. (We'll see the need for a similar trick when we examine reference counting in Item 29. For a detailed examination of this design, see my article on counting objects.)The counter in the Printer class is the static variable numObjects, so we need to move that variable into an instance-counting class. However, we also need to make sure that each class for which we're counting instances has a separate counter. Use of a counting class template lets us automatically generate the appropriate number of counters, because we can make the counter a static member of the classes generated from the template: template<class BeingCounted>class Counted {public:  class TooManyObjects{};                     // for throwing exceptions  static int objectCount() { return numObjects; }protected:  Counted();  Counted(const Counted& rhs);  ~Counted() { --numObjects; }private:  static int numObjects;  static const size_t maxObjects;  void init();                                // to avoid ctor code};                                            // duplicationtemplate<class BeingCounted>Counted<BeingCounted>::Counted(){ init(); }template<class BeingCounted>Counted<BeingCounted>::Counted(const Counted<BeingCounted>&){ init(); }template<class BeingCounted>void Counted<BeingCounted>::init(){  if (numObjects >= maxObjects) throw TooManyObjects();  ++numObjects;}The classes generated from this template are designed to be used only as base classes, hence the protected constructors and destructor. Note the use of the private member function init to avoid duplicating the statements in the two Counted constructors.We can now modify the Printer class to use the Counted template: class Printer: private Counted<Printer> {public:  // pseudo-constructors  static Printer * makePrinter();  static Printer * makePrinter(const Printer& rhs);  ~Printer();  void submitJob(const PrintJob& job);  void reset();  void performSelfTest();  ...  using Counted<Printer>::objectCount;     // see below  using Counted<Printer>::TooManyObjects;  // see belowprivate:  Printer();  Printer(const Printer& rhs);};The fact that Printer uses the Counted template to keep track of how many Printer objects exist is, frankly, nobody's business but the author of Printer's. Such implementation details are best kept private, and that's why private inheritance is used here (see Item E42). The alternative would be to use public inheritance between Printer and Counted<Printer>, but then we'd be obliged to give the Counted classes a virtual destructor. (Otherwise we'd risk incorrect behavior if somebody deleted a Printer object through a Counted<Printer>* pointer see Item E14.) As Item 24 makes clear, the presence of a virtual function in Counted would almost certainly affect the size and layout of objects of classes inheriting from Counted. We don't want to absorb that overhead, and the use of private inheritance lets us avoid it.Quite properly, most of what Counted does is hidden from Printer's clients, but those clients might reasonably want to find out how many Printer objects exist. The Counted template offers the objectCount function to provide this information, but that function becomes private in Printer due to our use of private inheritance. To restore the public accessibility of that function, we employ a using declaration: class Printer: private Counted<Printer> {public:  ...  using Counted<Printer>::objectCount; // make this function                                       // public for clients  ...                                  // of Printer};This is perfectly legitimate, but if your compilers don't yet support namespaces, they won't allow it. If they don't, you can use the older access declaration syntax: class Printer: private Counted<Printer> {public:  ...  Counted<Printer>::objectCount;       // make objectCount                                       // public in Printer  ...};This more traditional syntax has the same meaning as the using declaration, but it's deprecated. The class TooManyObjects is handled in the same fashion as objectCount, because clients of Printer must have access to TooManyObjects if they are to be able to catch exceptions of that type.When Printer inherits from Counted<Printer>, it can forget about counting objects. The class can be written as if somebody else were doing the counting for it, because somebody else (Counted<Printer>) is. A Printer constructor now looks like this: Printer::Printer(){  proceed with normal object construction;}What's interesting here is not what you see, it's what you don't. No checking of the number of objects to see if the limit is about to be exceeded, no incrementing the number of objects in existence once the constructor is done. All that is now handled by the Counted<Printer> constructors, and because Counted<Printer> is a base class of Printer, we know that a Counted<Printer> constructor will always be called before a Printer constructor. If too many objects are created, a Counted<Printer> constructor throws an exception, and the Printer constructor won't even be invoked. Nifty, huh?Nifty or not, there's one loose end that demands to be tied, and that's the mandatory definitions of the statics inside Counted. It's easy enough to take care of numObjects we just put this in Counted's implementation file: template<class BeingCounted>                 // defines numObjectsint Counted<BeingCounted>::numObjects;       // and automatically                                             // initializes it to 0The situation with maxObjects is a bit trickier. To what value should we initialize this variable? If we want to allow up to 10 printers, we should initialize Counted<Printer>::maxObjects to 10. If, on the other hand, we want to allow up to 16 file descriptor objects, we should initialize Counted<FileDescriptor>::maxObjects to 16. What to do?We take the easy way out: we do nothing. We provide no initialization at all for maxObjects. Instead, we require that clients of the class provide the appropriate initialization. The author of Printer must add this to an implementation file: const size_t Counted<Printer>::maxObjects = 10;Similarly, the author of FileDescriptor must add this: const size_t Counted<FileDescriptor>::maxObjects = 16;What will happen if these authors forget to provide a suitable definition for maxObjects? Simple: they'll get an error during linking, because maxObjects will be undefined. Provided we've adequately documented this requirement for clients of Counted, they can then say "Duh" to themselves and go back and add the requisite initialization. Back to Item 25: Virtualizing constructors and non-member functionsContinue to Item 27: Requiring or prohibiting heap-based objects 9 In July 1996, the ISO/ANSI standardization committee changed the default linkage of inline functions to external, so the problem I describe here has been eliminated, at least on paper. Your compilers may not yet be in accord with the standard, however, so your best bet is still to shy away from inline functions with static data.Return 

⌨️ 快捷键说明

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