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

📄 counting.htm

📁 高效c++编程
💻 HTM
📖 第 1 页 / 共 3 页
字号:
...delete pw;               // yields undefined results if			 // Counter lacks a virtual destructorEarlier we tried to prevent this sequence of operations by preventing the delete expression from compiling, but we discovered that that led to invalid or misleading code. There is something else we can prohibit. We can prohibit the implicit conversion from a Widget* pointer (which is what new returns) to a Counter<Widget>* pointer. In other words, we can prevent inheritance-based pointer conversions. All we have to do is replace the use of public inheritance with private inheritance: class Widget:                                 // inheritance is  private Counter<Widget> { ... };            // now privateCounter<Widget> *pw =                         // error!  no implicit conversion  new Widget;                                 // from Widget* to Counter<Widget>*Furthermore, we're likely to find that the use of Counter as a base class does not increase the size of Widget compared to Widget's stand-alone size. Yes, I know, I just finished telling you that no class has zero size, but well, that's not really what I said. What I said was that no objects have zero size. The C++ Standard makes clear that the base-class part of a more derived object may have zero size. In fact many compilers implement what has come to be known as the empty base optimization [Reference 2]. Thus, if a Widget contains a Counter, the size of the Widget must increase. The Counter data member is an object in its own right, hence it must have nonzero size. But if Widget inherits from Counter, compilers are allowed to keep Widget the same size it was before. This suggests an interesting rule of thumb for designs where space is tight and empty classes are involved: prefer private inheritance to containment when both will do.This last design is nearly perfect. It fulfills the efficiency criterion, provided your compilers implement the empty base optimization, because inheriting from Counter adds no per-object data to the inheriting class, and all Counter member functions are inline. It fulfills the foolproof criterion, because count manipulations are handled automatically by Counter member functions, those functions are automatically called by C++, and the use of private inheritance prevents implicit conversions that would allow derived-class objects to be manipulated as if they were base-class objects. (Okay, it's not totally foolproof: Widget's author might foolishly instantiate Counter with a type other than Widget, i.e., Widget could be made to inherit from Counter<Gidget>. I choose to ignore this possibility.)The design is certainly easy for clients to use, but some may grumble that it could be easier. The use of private inheritance means that howMany will become private in inheriting classes, so such classes must include a using declaration to make howMany public to their clients: class Widget: private Counter<Widget> {public:  using Counter<Widget>::howMany;           // make howMany public  ...                                       // rest of Widget};                                          // is unchangedclass ABCD: private Counter<ABCD> {public:  using Counter<ABCD>::howMany;             // make howMany public  ...                                       // rest of Widget};                                          // is unchangedFor compilers not supporting name spaces, the same thing is accomplished by replacing the using declaration with the older (now deprecated) access declaration: class Widget: private Counter<Widget> {public:  Counter<Widget>::howMany;                 // make howMany public  ...                                       // rest of Widget};                                          // is unchangedclass ABCD: private Counter<ABCD> {public:  Counter<ABCD>::howMany;                   // make howMany public  ...                                       // rest of ABCD};                                          // is unchangedHence, clients who want to count objects and who want to make that count available (as part of their class's interface) to their clients must do two things: declare Counter as a base class and make howMany accessible. (Simple variations on this design make it possible for Widget to use Counter<Widget> to count objects without making the count available to Widget clients, not even by calling Counter<Widget>::howMany directly. It is an interesting exercise to come up with one or more such variations.)The use of inheritance does, however, lead to two conditions that are worth noting. The first is ambiguity. Suppose we want to count Widgets, and we want to make the count available for general use. As shown above, we have Widget inherit from Counter<Widget> and we make howMany public in Widget. Now suppose we have a class SpecialWidget publicly inherit from Widget and we want to offer SpecialWidget clients the same functionality Widget clients enjoy. No problem, we just have SpecialWidget inherit from Counter<SpecialWidget>.But here is the ambiguity problem. Which howMany should be made available by SpecialWidget, the one it inherits from Widget or the one it inherits from Counter<SpecialWidget>? The one we want, naturally, is the one from Counter<SpecialWidget>, but there's no way to say that without actually writing SpecialWidget::howMany. Fortunately, it's a simple inline function: class SpecialWidget: public Widget,  private Counter<SpecialWidget> {public:  ...  static size_t howMany()  { return Counter<SpecialWidget>::howMany(); }  ...};The second observation about our use of inheritance to count objects is that the value returned from Widget::howMany includes not just the number of Widget objects, it includes also objects of classes derived from Widget. If the only class derived from Widget is SpecialWidget and there are five stand-alone Widget objects and three stand-alone SpecialWidgets, Widget::howMany will return eight. After all, construction of each SpecialWidget also entails construction of the base Widget part. (For a more detailed examination of this topic, see Item M26's treatment of contexts for object construction.)SummaryThe following points are really all you need to remember: Automating the counting of objects isn't hard, but it's not completely straightforward, either. Use of the "Do It For Me" pattern (Coplien's "curiously recurring template pattern") makes it possible to generate the correct number of counters. The use of private inheritance makes it possible to offer object-counting capabilities without increasing object sizes. When clients have a choice between inheriting from an empty class or containing an object of such a class as a data member, inheritance is preferable, because it allows for more compact objects. Because C++ endeavors to avoid memory leaks when construction fails for heap objects, code that requires access to operator new generally requires access to the corresponding operator delete too. The Counter class template doesn't care whether you inherit from it or you contain an object of its type. It looks the same regardless. Hence, clients can freely choose inheritance or containment, even using different strategies in different parts of a single application or library.Sidebar: Placement new and Placement deleteThe C++ equivalent of malloc is operator new, and the C++ equivalent of free is operator delete. Unlike malloc and free, however, operator new and operator delete are over loadable functions, and as such they may take different numbers and types of parameters. This has always been the case for operator new, but until relatively recently, it wasn't valid to overload operator delete.The "normal" signature for operator new is:   void * operator new(size_t) throw (std::bad_alloc);(To simplify things from now on, I'll omit exception specifications. They're not germane to the points I want to make here, but you can read about them in Item M15.) Overloaded versions of operator new are limited to adding additional parameters, so an overloaded version of operator new might look like this:   void * operator new(size_t, void *whereToPutObject)  { return whereToPutObject; }This particular version of operator new the one taking an extra void* argument specifying what pointer the function should return is so commonly useful that it's in the Standard C++ library (declared in the header <new>) and it has a name, placement new. The name indicates its purpose: to allow programmers to specify where in memory an object should be created (i.e., where it should be placed).Over time, the term placement new has come to be applied to any version of operator new taking additional arguments. (This terminology is actually enshrined in the grammar for the International Standard for C++.) Hence, when C++ programmers talk about the placement new function, they mean the function above, the one taking the extra void* parameter specifying where an object should be placed. When they talk about a placement new function, however, they mean any version of operator new taking more than the mandatory size_t argument. That includes the function above, but it also includes a plethora of operator new functions that take more or different parameter types.In other words, when the topic is memory allocation functions, "placement new" means "a version of operator new taking extra arguments." The term can mean still other things in other contexts, but we don't need to go down that road here, so we won't. For details, consult the suggested reading at the end of the article.By analogy with placement new, the term placement delete means "a version of operator delete taking extra arguments." The "normal" signature for operator delete is   void operator delete(void*);so any version of operator delete taking one or more arguments beyond the mandatory void* parameter is a placement delete function. Let us now revisit an issue discussed in the main article. What happens when an exception is thrown during construction of a heap object? Consider again this simple example:   class ABCD { ... };  ABCD *p = new ABCD;Suppose the attempt to create the ABCD object yields an exception. The main article pointed out that if that exception came from the ABCD constructor, operator delete would automatically be called to deallocate the memory allocated by operator new. But what if operator new is overloaded, and what if different versions of operator new (quite reasonably) allocate memory in different ways? How can operator delete know how to correctly deallocate the memory? Furthermore, what if placement new was used for the ABCD object being created, like this?   void *objectBuffer = getPointerToStaticBuffer();  ABCD *p = new (objectBuffer) ABCD;                // create an ABCD object						    // in a static buffer(The) placement new didn't actually allocate any memory. It just returned the pointer to the static buffer that was passed to it in the first place. So there's no need for any deallocation.Clearly, the actions to be taken in operator delete to undo the actions of its corresponding operator new depend on the version of operator new that was invoked to allocate the memory.To make it possible for programmers to indicate how the actions of particluar versions of operator new can be undone, the C++ Standards committee extended C++ to allow operator delete to be overloaded, too. When an exception is thrown from the constructor for a heap object, then, the game is played in a special way. The version of operator delete that's called is the one whose extra parameter types correspond to those of the version of operator new that was invoked.If there's no placement delete whose extra parameters correspond to the extra parameters of the placement new that was called, no operator delete is invoked at all. So the effects of operator new are not undone. For functions like (the) placement version of operator new, this is fine, because they don't really allocate memory. In general, however, if you create a custom placement version of operator new, you should also create the corresponding custom placement version of operator delete to go with it.Alas, most compilers don't yet support placement delete. With code generated from such compilers, you almost always suffer a memory leak if an exception is thrown during construction of a heap object, because no attempt is made to deallocate the memory that was allocated before the constructor was invoked.References James O. Coplien, "The Column Without a Name: A Curiously Recurring Template Pattern," C++ Report, February 1995. Nathan Myers, "The Empty Member C++ Optimization," Dr. Dobbs Journal, August 1997.Suggested ReadingTo learn more about the details of new and delete, read the columns by Dan Saks on the topic (CUJ January - July 1997) or turn to Item M8. For a broader examination of the object-counting problem, including how to limit the number of instantiations of a class, consult Item M26.AcknowledgmentsMark Rodgers, Damien Watkins, Marco Dalla Gasperina, and Bobby Schmidt provided comments on drafts of this article. Their insights and suggestions improved it in several ways. 

⌨️ 快捷键说明

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