B. Stroustrup - The C++ Programming Language (794319), страница 64
Текст из файла (страница 64)
A C++ implementation does not guarantee the presence of a ‘‘garbagecollector’’ that looks out for unreferenced objects and makes them available to new for reuse. Consequently, I will assume that objects created by new are manually freed using delete.Section 11.2Free Store279The delete operator may be applied only to a pointer returned by new or to the nullptr. Applyingto the nullptr has no effect.If the deleted object is of a class with a destructor (§3.2.1.2, §17.2), that destructor is called bydelete before the object’s memory is released for reuse.delete11.2.1 Memory ManagementThe main problems with free store are:• Leaked objects: People use new and then forget to delete the allocated object.• Premature deletion: People delete an object that they have some other pointer to and lateruse that other pointer.• Double deletion: An object is deleted twice, invoking its destructor (if any) twice.Leaked objects are potentially a bad problem because they can cause a program to run out of space.Premature deletion is almost always a nasty problem because the pointer to the ‘‘deleted object’’ nolonger points to a valid object (so reading it may give bad results) and may indeed point to memorythat has been reused for another object (so writing to it may corrupt an unrelated object).
Considerthis example of very bad code:int∗ p1 = new int{99};int∗ p2 = p1;delete p1;p1 = nullptr;char∗ p3 = new char{'x'};∗p2 = 999;cout << ∗p3 << '\n';// potential trouble// now p2 doesn’t point to a valid object// gives a false sense of safety// p3 may now point to the memory pointed to by p2// this may cause trouble// may not print xDouble deletion is a problem because resource managers typically cannot track what code owns aresource.
Consider:void sloppy() // very bad code{int∗ p = new int[1000];// acquire memory// ... use *p ...delete[] p;// release memory// ... wait a while ...delete[] p;// but sloppy() does not own *p}By the second delete[], the memory pointed to by ∗p may have been reallocated for some other useand the allocator may get corrupted. Replace int with string in that example, and we’ll see string’sdestructor trying to read memory that has been reallocated and maybe overwritten by other code,and using what it read to try to delete memory. In general, a double deletion is undefined behaviorand the results are unpredictable and usually disastrous.The reason people make these mistakes is typically not maliciousness and often not even simplesloppiness; it is genuinely hard to consistently deallocate every allocated object in a large program(once and at exactly the right point in a computation). For starters, analysis of a localized part of aprogram will not detect these problems because an error usually involves several separate parts.280Select OperationsChapter 11As alternatives to using ‘‘naked’’ news and deletes, I can recommend two general approaches toresource management that avoid such problems:[1] Don’t put objects on the free store if you don’t have to; prefer scoped variables.[2] When you construct an object on the free store, place its pointer into a manager object(sometimes called a handle) with a destructor that will destroy it.
Examples are string,vector and all the other standard-library containers, unique_ptr (§5.2.1, §34.3.1), andshared_ptr (§5.2.1, §34.3.2). Wherever possible, have that manager object be a scopedvariable. Many classical uses of free store can be eliminated by using move semantics(§3.3, §17.5.2) to return large objects represented as manager objects from functions.This rule [2] is often referred to as RAII (‘‘Resource Acquisition Is Initialization’’; §5.2, §13.3) andis the basic technique for avoiding resource leaks and making error handling using exceptions simple and safe.The standard-library vector is an example of these techniques:void f(const string& s){vector<char> v;for (auto c : s)v.push_back(c);// ...}The vector keeps its elements on the free store, but it handles all allocations and deallocations itself.In this example, push_back() does news to acquire space for its elements and deletes to free spacethat it no longer needs.
However, the users of vector need not know about those implementationdetails and will just rely on vector not leaking.The Token_stream from the calculator example is an even simpler example (§10.2.2). There, auser can use new and hand the resulting pointer to a Token_stream to manage:Token_stream ts{new istringstream{some_string}};We do not need to use the free store just to get a large object out of a function.
For example:string reverse(const string& s){string ss;for (int i=s.size()−1; 0<=i; −−i)ss.push_back(s[i]);return ss;}Like vector, a string is really a handle to its elements. So, we simply move the ss out of reverse()rather than copying any elements (§3.3.2).The resource management ‘‘smart pointers’’ (e.g., unique_ptr and smart_ptr) are a further example of these ideas (§5.2.1, §34.3.1). For example:void f(int n){int∗ p1 = new int[n];unique_ptr<int[]> p2 {new int[n]};// potential troubleSection 11.2.1Memory Management281// ...if (n%2) throw runtime_error("odd");delete[] p1;// we may never get here}For f(3) the memory pointed to by p1 is leaked, but the memory pointed to by p2 is correctly andimplicitly deallocated.My rule of thumb for the use of new and delete is ‘‘no naked news’’; that is, new belongs in constructors and similar operations, delete belongs in destructors, and together they provide a coherentmemory management strategy.
In addition, new is often used in arguments to resource handles.If everything else fails (e.g., if someone has a lot of old code with lots of undisciplined use ofnew), C++ offers a standard interface to a garbage collector (§34.5).11.2.2 ArraysArrays of objects can also be created using new. For example:char∗ save_string(const char∗ p){char∗ s = new char[strlen(p)+1];strcpy(s,p);// copy from p to sreturn s;}int main(int argc, char∗ argv[]){if (argc < 2) exit(1);char∗ p = save_string(argv[1]);// ...delete[] p;}The ‘‘plain’’ operator delete is used to delete individual objects; delete[] is used to delete arrays.Unless you really must use a char∗ directly, the standard-library string can be used to simplifythe save_string():string save_string(const char∗ p){return string{p};}int main(int argc, char∗ argv[]){if (argc < 2) exit(1);string s = save_string(argv[1]);// ...}In particular, the new[] and the delete[] vanished.282Select OperationsChapter 11To deallocate space allocated by new, delete and delete[] must be able to determine the size ofthe object allocated.
This implies that an object allocated using the standard implementation of newwill occupy slightly more space than a static object. At a minimum, space is needed to hold theobject’s size. Usually two or more words per allocation are used for free-store management. Mostmodern machines use 8-byte words. This overhead is not significant when we allocate manyobjects or large objects, but it can matter if we allocate lots of small objects (e.g., ints or Points) onthe free store.Note that a vector (§4.4.1, §31.4) is a proper object and can therefore be allocated and deallocated using plain new and delete. For example:void f(int n){vector<int>∗ p = new vector<int>(n);// individual objectint∗ q = new int[n];// array// ...delete p;delete[] q;}The delete[] operator may be applied only to a pointer to an array returned by new of an array or tothe null pointer (§7.2.2).
Applying delete[] to the null pointer has no effect.However, do not use new to create local objects. For example:void f1(){X∗ p =new X;// ... use *p ...delete p;}That’s verbose, inefficient, and error-prone (§13.3). In particular, a return or an exception thrownbefore the delete will cause a memory leak (unless even more code is added). Instead, use a localvariable:void f2(){X x;// ... use x ...}The local variable x is implicitly destroyed upon exit from f2.11.2.3 Getting Memory SpaceThe free-store operatorsin the <new> header:new, delete, new[],void∗ operator new(size_t);void operator delete(void∗ p);anddelete[]are implemented using functions presented// allocate space for individual object// if (p) deallocate space allocated using operator new()Section 11.2.3void∗ operator new[](size_t);void operator delete[](void∗ p);Getting Memory Space283// allocate space for array// if (p) deallocate space allocated using operator new[]()When operator new needs to allocate space for an object, it calls operator new() to allocate a suitablenumber of bytes.
Similarly, when operator new needs to allocate space for an array, it calls operatornew[]().The standard implementations of operator new() and operator new[]() do not initialize the memory returned.The allocation and deallocation functions deal in untyped and uninitialized memory (oftencalled ‘‘raw memory’’), as opposed to typed objects. Consequently, they take arguments or returnvalues of type void∗. The operators new and delete handle the mapping between this untyped-memory layer and the typed-object layer.What happens when new can find no store to allocate? By default, the allocator throws a standard-library bad_alloc exception (for an alternative, see §11.2.4.1). For example:void f(){vector<char∗> v;try {for (;;) {char ∗ p = new char[10000];v.push_back(p);p[0] = 'x';}}catch(bad_alloc) {cerr << "Memory exhausted!\n";}}// acquire some memory// make sure the new memor y is referenced// use the new memor yHowever much memory we have available, this will eventually invoke the bad_alloc handler.
Pleasebe careful: the new operator is not guaranteed to throw when you run out of physical main memory.So, on a system with virtual memory, this program can consume a lot of disk space and take a longtime doing so before the exception is thrown.We can specify what new should do upon memory exhaustion; see §30.4.1.3.In addition to the functions defined in <new>, a user can define operator new(), etc., for a specificclass (§19.2.5). Class members operator new(), etc., are found and used in preference to the onesfrom <new> according to the usual scope rules.11.2.4 Overloading newBy default, operator new creates its object on the free store. What if we wanted the object allocatedelsewhere? Consider a simple class:class X {public:X(int);// ...};284Select OperationsChapter 11We can place objects anywhere by providing an allocator function (§11.2.3) with extra argumentsand then supplying such extra arguments when using new:void∗ operator new(size_t, void∗ p) { return p; }// explicit placement operatorvoid∗ buf = reinterpret_cast<void∗>(0xF00F);X∗ p2 = new(buf) X;// significant address// construct an X at buf;// invokes: operator new(sizeof(X),buf)Because of this usage, the new(buf) X syntax for supplying extra arguments to operator new() isknown as the placement syntax.