Professional C__ - Marc Gregoire [326]
if (p != nullptr) { cout << "not nullptr" << endl; } // Error
This is correct behavior because nullptr has its own type called nullptr_t which is not automatically converted to 0. The compiler cannot find an operator!= which takes a Pointer object and a nullptr_t object. You could implement such an operator!= as a friend of the Pointer class:
template bool operator!=(const Pointer { return lhs.mPtr != rhs; } Code snippet from Conversions\Pointer\PointerBool.h However, after implementing this operator!=, the following comparison stops working, because the compiler doesn’t know which operator!= to use: either the operator!= you have just defined, or the built-in C++ operator!=(bool,int): if (p != NULL) { cout << "not NULL" << endl; } From this example, you might conclude that the operator bool() technique seems only appropriate for objects that don’t represent pointers and for which conversion to a pointer type really doesn’t make sense. Unfortunately, adding a conversion operator to bool presents some other unanticipated consequences. C++ applies “promotion” rules to silently convert bool to int whenever the opportunity arises. Therefore, with the operator bool(), the following code compiles and runs: Pointer int i = smartCell; // Converts smartCell Pointer to bool to int. That’s usually not behavior that you expect or desire. Thus, many programmers prefer operator void*() instead of operator bool(). In fact, Chapter 15 shows the following use of streams: ifstream istr; // Open istr int temp; while (istr >> temp) { /* Process temp */ } In order to allow stream objects to be used in Boolean expressions, but prohibit their undesired promotion to int, the basic_ios class defines operator void*() instead of operator bool(). As you can see, there is a design element to overloading operators. Your decisions about which operators to overload directly influence the ways in which clients can use your classes. OVERLOADING THE MEMORY ALLOCATION AND DEALLOCATION OPERATORS Unless you know a lot about memory allocation strategies, attempts to overload the memory allocation routines are rarely worth the trouble. Don’t overload them just because it sounds like a neat idea. Only do so if you have a genuine requirement and the necessary knowledge. How new and delete Really Work One of the trickiest aspects of C++ is the details of new and delete. Consider this line of code: SpreadsheetCell* cell = new SpreadsheetCell(); The part “new SpreadsheetCell()” is called the new-expression. It does two things. First, it allocates space for the SpreadsheetCell object by making a call to operator new. Second, it calls the constructor for the object. Only after the constructor has completed does it return the pointer to you. delete functions analogously. Consider this line of code: delete cell; This line is called the delete-expression. It first calls the destructor for cell, then calls operator delete to free the memory. You can overload operator new and operator delete to control memory allocation and deallocation, but you cannot overload the new-expression or the delete-expression. Thus, you can customize the actual
C++ gives you the ability to redefine the way memory allocation and deallocation work in your programs. You can provide this customization both on the global level and the class level. This capability is most useful when you are worried about memory fragmentation, which can occur if you allocate and deallocate a lot of small objects. For example, instead of going to the default C++ memory allocation each time you need memory, you could write a memory pool allocator that reuses fixed-size chunks of memory. This section explains the subtleties of the memory allocation and deallocation routines and shows you how to customize them. With these tools, you should be able to write your own allocator if the need ever arises.