18.1. Optimizing Memory Allocation18.1. 优化内存分配Memory allocation in C++ is a typed operation: new (Section 5.11, p. 174) allocates memory for a specified type and constructs an object of that type in the newly allocated memory. A new expression automatically runs the appropriate constructor to initialize each dynamically allocated object of class type. C++ 的内存分配是一种类型化操作:new(第 5.11 节)为特定类型分配内存,并在新分配的内存中构造该类型的一个对象。new 表达式自动运行合适的构造函数来初始化每个动态分配的类类型对象。 The fact that new allocates memory on a per-object basis can impose unacceptable run-time overhead on some classes. Such classes might need to make user-level allocation of objects of the class type faster. A common strategy such classes use is to preallocate memory to be used when new objects are created, constructing each new object in preallocated memory as needed. new 基于每个对象分配内存的事实可能会对某些类强加不可接受的运行时开销,这样的类可能需要使用用户级的类类型对象分配能够更快一些。这样的类使用的通用策略是,预先分配用于创建新对象的内存,需要时在预先分配的内存中构造每个新对象。 Other classes want to minimize allocations needed for their own data members. For example, the library vector class preallocates (Section 9.4, p. 330) extra memory to hold additional elements if and when they are added. New elements are added in this reserved capacity. Preallocating elements allows vector to efficiently add elements while keeping the elements in contiguous memory. 另外一些类希望按最小尺寸为自己的数据成员分配需要的内存。例如,标准库中的 vector 类预先分配(第 9.4 节)额外内存以保存加入的附加元素,将新元素加入到这个保留容量中。将元素保持在连续内存中的时候,预先分配的元素使 vector 能够高效地加入元素。 In each of these casespreallocating memory to hold user-level objects or to hold the internal data for a classthere is the need to decouple memory allocation from object construction. The obvious reason to decouple allocation and construction is that constructing objects in preallocated memory is wasteful. Objects may be created that are never used. Those objects that are used must be reassigned new values when the preallocated object is actually used. More subtly, some classes could not use preallocated memory if it had to be constructed. As an example, consider vector, which uses a preallocation strategy. If objects in preallocated memory had to be constructed, then it would not be possible to have vectors of types that do not have a default constructorthere would be no way for vector to know how to construct these objects. 在每种情况下(预先分配内存以保存用户级对象或者保存类的内部数据)都需要将内存分配与对象构造分离开。将内存分配与对象构造分离开的明显的理由是,在预先分配的内存中构造对象很浪费,可能会创建从不使用的对象。当实际使用预先分配的对象的时候,被使用的对象必须重新赋以新值。更微妙的是,如果预先分配的内存必须被构造,某些类就不能使用它。例如,考虑 vector,它使用了预先分配策略。如果必须构造预先分配的内存中的对象,就不能有基类型为没有默认构造函数的 vector——vector 没有办法知道怎样构造这些对象。
18.1.1. Memory Allocation in C++18.1.1. C++ 中的内存分配In C++ memory allocation and object construction are closely intertwined, as are object destruction and memory deallocation. When we use a new expression, memory is allocated, and an object is constructed in that memory. When we use a delete expression, a destructor is called to destroy the object and the memory used by the object is returned to the system. C++ 中,内存分配和对象构造紧密纠缠,就像对象和内存回收一样。使用 new 表达式的时候,分配内存,并在该内存中构造一个对象:使用 delete 表达式的时候,调用析构函数撤销对象,并将对象所用内存返还给系统。 When we take over memory allocation, we must deal with both these tasks. When we allocate raw memory, we must construct object(s) in that memory. Before freeing that memory, we must ensure that the objects are properly destroyed. 接管内存分配时,必须处理这两个任务。分配原始内存时,必须在该内存中构造对象;在释放该内存之前,必须保证适当地撤销这些对象。 C++ provides two ways to allocate and free unconstructed, raw memory: C++ 提供下面两种方法分配和释放未构造的原始内存。
C++ also provides various ways to construct and destroy objects in raw memory: C++ 还提供不同的方法在原始内存中构造和撤销对象。
18.1.2. The allocator Class18.1.2. allocator 类The allocator class is a template that provides typed memory allocation and object construction and destruction. Table 18.1 on the following page outlines the operations that allocator supports. allocator 类是一个模板,它提供类型化的内存分配以及对象构造与撤销。表 18.1 概述了 allocator 类支持的操作。 Table 18.1. Standard allocator Class and Customized Algorithms表 18.1. 标准 allocator 类与定制算法
The allocator class separates allocation and object construction. When an allocator object allocates memory, it allocates space that is appropriately sized and aligned to hold objects of the given type. However, the memory it allocates is unconstructed. Users of allocator must separately construct and destroy objects placed in the memory it allocates. allocator 类将内存分配和对象构造分开。当 allocator 对象分配内存的时候,它分配适当大小并排列成保存给定类型对象的空间。但是,它分配的内存是未构造的,allocator 的用户必须分别 construct 和 destroy 放置在该内存中的对象。 Using allocator to Manage Class Member Data使用 allocator 管理类成员数据To understand how we might use a preallocation strategy and the allocator class to manage the internal data needs of a class, let's think a bit more about how memory allocation in the vector class might work. 为了理解可以怎样使用预分配策略以及 allocator 类来管理类的内部数据需要,让我们再想想 vector 类中的内存分配会怎样工作。 Recall that the vector class stores its elements in contiguous storage. To obtain acceptable performance, vector preallocates more elements than are needed (Section 9.4, p. 330). Each vector member that adds elements to the container checks whether there is space available for another element. If so, then the member initializes an object in the next available spot in preallocated memory. If there isn't a free element, then the vector is reallocated: The vector obtains new space, copies the existing elements into that space, adds the new element, and frees the old space. 回忆一下,vector 类将元素保存在连续的存储中。为了获得可接受的性能,vector 预先分配比所需元素更多的元素(第 9.4 节)。每个将元素加到容器中的 vector 成员检查是否有可用空间以容纳另一元素。如果有,该成员在预分配内存中下一可用位置初始化一个对象;如果没有自由元素,就重新分配 vector:vector 获取新的空间,将现在元素复制到空间,增加新元素,并释放旧空间。 The storage that vector uses starts out as unconstructed memory; it does not yet hold any objects. When the elements are copied to or added in this preallocated space, they must be constructed using the construct member of allocator. vector 所用存储开始是未构造内存,它还没有保存任何对象。将元素复制或增加到这个预分配空间的时候,必须使用 allocator 类的 construct 成员构造元素。 To illustrate these concepts we'll implement a small portion of vector. We'll name our class Vector to distinguish it from the standard vector class: 为了说明这些概念,我们将实现 vector 的一小部分。将我们的类命名为 Vector,以区别于标准类 vector: // pseudo-implementation of memory allocation strategy for a vector-like class template <class T> class Vector { public: Vector(): elements(0), first_free(0), end(0) { } void push_back(const T&); // ... private: static std::allocator<T> alloc; // object to get raw memory void reallocate(); // get more space and copy existing elements T* elements; // pointer to first element in the array T* first_free; // pointer to first free element in the array T* end; // pointer to one past the end of the array // ... }; Each Vector<T> type defines a static data member of type allocator<T> to allocate and construct the elements in Vectors of the given type. Each Vector object keeps its elements in a built-in array of the indicated type and maintains three pointers into that array: 每个 Vector<T> 类型定义一个 allocator<T> 类型的 static 数据成员,以便在给定类型的 Vector 中分配和构造元素。每个 Vector 对象在指定类型的内置数组中保存其元素,并维持该数组的下列三个指针:
Figure 18.1 illustrates the meaning of these pointers. 图 18.1 说明了这些指针的含义。 Figure 18.1. Vector Memory Allocation Strategy图 18.1. Vector 内存分配策略We can use these pointers to determine the size and capacity of the Vector: 可以使用这些指针来确定 Vector 的大小和容量:
Using construct使用 constructThe push_back member uses these pointers to add a new element to the end of the Vector: push_back 成员使用这些指针将新元素加到 Vector 末尾: template <class T> void Vector<T>::push_back(const T& t) { // are we out of space? if (first_free == end) reallocate(); // gets more space and copies existing elements to it alloc.construct(first_free, t); ++first_free; } The push_back function starts by determining whether there is space available. If not, it calls reallocate. That member allocates new space and copies the existing elements. It resets the pointers to point to the newly allocated space. push_back 函数首先确定是否有可用空间,如果没有,就调用 reallocate 函数,reallocate 分配新空间并复制现存元素,将指针重置为指向新分配的空间。 Once push_back knows that there is room for the new element, it asks the allocator object to construct a new last element. The construct function uses the copy constructor for type T to copy the value t into the element denoted by first_free. It then increments first_free to indicate that one more element is in use. 一旦 push_back 函数知道还有空间容纳新元素,它就请求 allocator 对象构造一个新的最后元素。construct 函数使用类型 T 的复制构造函数将 t 值复制到由 first_free 指出的元素,然后,将 first_free 加 1 以指出又有一个元素在用。 Reallocating and Copying Elements重新分配元素与复制元素The reallocate function has the most work to do: reallocate 函数所做的工作最多: template <class T> void Vector<T>::reallocate() { // compute size of current array and allocate space for twice as many elements std::ptrdiff_t size = first_free - elements; std::ptrdiff_t newcapacity = 2 * max(size, 1); // allocate space to hold newcapacity number of elements of type T T* newelements = alloc.allocate(newcapacity); // construct copies of the existing elements in the new space uninitialized_copy(elements, first_free, newelements); // destroy the old elements in reverse order for (T *p = first_free; p != elements; /* empty */ ) alloc.destroy(--p); // deallocate cannot be called on a 0 pointer if (elements) // return the memory that held the elements alloc.deallocate(elements, end - elements); // make our data structure point to the new elements elements = newelements; first_free = elements + size; end = elements + newcapacity; } We use a simple but surprisingly effective strategy of allocating twice as much memory each time we reallocate. The function starts by calculating the current number of elements in use, doubling that number, and asking the allocator object to obtain the desired amount of space. If the Vector is empty, we allocate two elements. 我们使用一个简单但效果惊人的策略:每次重新分配时分配两倍内存。函数首先计算当前在用的元素数目,将该数目翻倍,并请求 allocator 对象来获得所需数量的空间。如果 Vector 为空,就分配两个元素。 If Vector holds ints, the call to allocate allocates space for newcapacity number of ints. If it holds strings, then it allocates that space for the given number of strings. 如果 Vector 保存 int 值,allocate 函数调用为 newcapacity 数目的 int 值分配空间;如果 Vector 保存 string 对象,它就为给定数目的 string 对象分配空间。 The call to uninitialized_copy uses a specialized version of the standard copy algorithm. This version expects its destination to be raw, unconstructed memory. Rather than assigning elements from the input range to the destination, it copy-constructs each element in the destination. The copy constructor for T is used to copy each element from the input range to the destination. uninitialized_copy 调用使用标准 copy 算法的特殊版本。这个版本希望目的地是原始的未构造内存,它在目的地复制构造每个元素,而不是将输入范围的元素赋值给目的地,使用 T 的复制构造函数从输入范围将每个元素复制到目的地。 The for loop calls the allocator member destroy on each object in the old array. It destroys the elements in reverse order, starting with the last element in the array and finishing with the first. The destroy function runs the destructor for type T to free any resources used by the old elements. for 循环对旧数组中每个对象调用 allocator 的 destroy 成员它按逆序撤销元素,从数组中最后一个元素开始,以第一个元素结束。destroy 函数运行 T 类型的析构函数来释放旧元素所用的任何资源。 Once the elements have been copied and destroyed, we free the space used by the original array. We must check that elements actually pointed to an array before calling deallocate. 一旦复制和撤销了元素,就释放原来数组所用的空间。在调用 deallocate 之前,必须检查 elements 是否实际指向一个数组。
Finally, we have to reset the pointers to address the newly allocated and initialized array. The first_free and end pointers are set to denote one past the last constructed element and one past the end of the allocated space, respectively. 最后,必须重置指针以指向新分配并初始化的数组。将 first_free 和 end 指针分别置为指向最后构造的元素之后的单元以及所分配空间末尾的下一单元。
18.1.3. operator new and operator delete Functions18.1.3. operator new 函数和 operator delete 函数The previous subsection used the vector class to show how we could use the allocator class to manage a pool of memory for a class' internal data storage. In the next three subsections we'll look at how we might implement the same strategy using the more primitive library facilities. 前几节使用 vector 类说明了怎样使用 allocator 类来管理用于类的内部数据存储的内存池,下面三节将介绍怎样用更基本的标准库机制实现相同的策略。 First, we need to understand a bit more about how new and delete expressions work. When we use a new expression 首先,需要对 new 和 delete 表达式怎样工作有更多的理解。当使用 new 表达式 // new expression string * sp = new string("initialized"); three steps actually take place. First, the expression calls a library function named operator new to allocate raw, untyped memory large enough to hold an object of the specified type. Next, a constructor for the type is run to construct the object from the specified initializers. Finally, a pointer to the newly allocated and constructed object is returned. 的时候,实际上发生三个步骤。首先,该表达式调用名为 operator new 的标准库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象;接下来,运行该类型的一个构造函数,用指定初始化式构造对象;最后,返回指向新分配并构造的对象的指针。 When we use a delete expression to delete a dynamically allocated object: 当使用 delete 表达式 delete sp; two steps happen. First, the appropriate destructor is run on the object to which sp points. Then, the memory used by the object is freed by calling a library function named operator delete. 删除动态分配对象的时候,发生两个步骤。首先,对 sp 指向的对象运行适当的析构函数;然后,通过调用名为 operator delete 的标准库函数释放该对象所用内存。 The operator new and operator delete Interfaceoperator new 和 operator delete 接口There are two overloaded versions of operator new and operator delete functions. Each version supports the related new and delete expression: operator new 和 operator delete 函数有两个重载版本,每个版本支持相关的 new 表达式和 delete 表达式: void *operator new(size_t); // allocate an object void *operator new[](size_t); // allocate an array void *operator delete(void*); // free an object void *operator delete[](void*); // free an array Using the Allocation Operator Functions使用分配操作符函数Although the operator new and operator delete functions are intended to be used by new expressions, they are generally available functions in the library. We can use them to obtain unconstructed memory. They are somewhat analogous to the allocate and deallocate members of the allocator class. For example, instead of using an allocator, we could have used the operator new and operator delete functions in our Vector class. When we allocated new space we wrote 虽然 operator new 和 operator delete 函数的设计意图是供 new 表达式使用,但它们通常是标准库中的可用函数。可以使用它们获得未构造内存,它们有点类似 allocate 类的 allocator 和 deallocate 成员。例如,代替使用 allocator 对象,可以在 Vector 类中使用 operator new 和 operator delete 函数。在分配新空间时我们曾编写 // allocate space to hold newcapacity number of elements of type T T* newelements = alloc.allocate(newcapacity); which could be rewritten as 这可以重新编写为 // allocate unconstructed memory to hold newcapacity elements of type T T* newelements = static_cast<T*> (operator new[](newcapacity * sizeof(T))); Similarly, when we deallocated the old space pointed to be the Vector member elements we wrote 类似地,在重新分配由 Vector 成员 elements 指向的旧空间的时候,我们曾经编写
// return the memory that held the elements
alloc.deallocate(elements, end - elements);
which could be rewritten as 这可以重新编写为
// deallocate the memory that they occupied
operator delete[](elements);
These functions behave similarly to the allocate and deallocate members of the allocator class. However, they differ in one important respect: They operate on void* pointers rather than typed pointers. 这些函数的表现与 allocate 类的 allocator 和 deallocate 成员类似。但是,它们在一个重要方面有不同:它们在 void* 指针而不是类型化的指针上进行操作。
The allocate member allocates typed memory, so programs that use it can avoid the necessity of calculating the byte-count amount of memory needed. They also can avoid casting (Section 5.12.4, p. 183) the return from operator new. Similarly, deallocate frees memory of a specific type, again avoiding the necessity for converting to void*. allocate 成员分配类型化的内存,所以使用它的程序可以不必计算以字节为单位的所需内存量,它们也可以避免对 operator new 的返回值进行强制类型转换(第 5.12.4 节)。类似地,deallocate 释放特定类型的内存,也不必转换为 void*。 18.1.4. Placement new Expressions18.1.4. 定位 new 表达式The library functions operator new and operator delete are lower-level versions of the allocator members allocate and deallocate. Each allocates but does not initialize memory. 标准库函数 operator new 和 operator delete 是 allocator 的 allocate 和 deallocate 成员的低级版本,它们都分配但不初始化内存。 There are also lower-level alternatives to the allocator members construct and destroy. These members initialize and destroy objects in space allocated by an allocator object. allocator 的成员 construct 和 destroy 也有两个低级选择,这些成员在由 allocator 对象分配的空间中初始化和撤销对象。 Analogous to the construct member, there is a third kind of new expression, referred to as placement new. The placement new expression initializes an object in raw memory that was already allocated. It differs from other versions of new in that it does not allocate memory. Instead, it takes a pointer to allocated but unconstructed memory and initializes an object in that memory. In effect, placement new allows us to construct an object at a specific, preallocated memory address. 类似于 construct 成员,有第三种 new 表达式,称为定位 new。定位 new 表达式在已分配的原始内存中初始化一个对象,它与 new 的其他版本的不同之处在于,它不分配内存。相反,它接受指向已分配但未构造内存的指针,并在该内存中初始化一个对象。实际上,定位 new 表达式使我们能够在特定的、预分配的内存地址构造一个对象。 The form of a placement new expression is: 定位 new 表达式的形式是: new (place_address) type new (place_address) type (initializer-list) where place_address must be a pointer and the initializer-list provides (a possibly empty) list of initializers to use when constructing the newly allocated object. 其中 place_address 必须是一个指针,而 initializer-list 提供了(可能为空的)初始化列表,以便在构造新分配的对象时使用。 We could use a placement new expression to replace the call to construct in our Vector implementation. The original code 可以使用定位 new 表达式代替 Vector 实现中的 construct 调用。原来的代码 // construct a copy t in the element to which first_free points alloc.construct (first_free, t); would be replaced by the equivalent placement new expression 可以用等价的定位 new 表达式代替 // copy t into element addressed by first_free new (first_free) T(t); Placement new expressions are more flexible than the construct member of class allocator. When placement new initializes an object, it can use any constructor, and builds the object directly. The construct function always uses the copy constructor. 定位 new 表达式比 allocator 类的 construct 成员更灵活。定位 new 表达式初始化一个对象的时候,它可以使用任何构造函数,并直接建立对象。construct 函数总是使用复制构造函数。 For example, we could initialize an allocated but unconstructed string from a pair of iterators in either of these two ways: 例如,可以用下面两种方式之一,从一对迭代器初始化一个已分配但未构造的 string 对象: allocator<string> alloc; string *sp = alloc.allocate(2); // allocate space to hold 2 strings // two ways to construct a string from a pair of iterators new (sp) string(b, e); // construct directly in place alloc.construct(sp + 1, string(b, e)); // build and copy a temporary The placement new expression uses the string constructor that takes a pair of iterators to construct the string directly in the space to which sp points. When we call construct, we must first construct the string from the iterators to get a string object to pass to construct. That function then uses the string copy constructor to copy that unnamed, temporary string into the object to which sp points. 定位 new 表达式使用了接受一对迭代器的 string 构造函数,在 sp 指向的空间直接构造 string 对象。当调用 construct 函数的时候,必须首先从迭代器构造一个 string 对象,以获得传递给 construct 的 string 对象,然后,该函数使用 string 的复制构造函数,将那个未命名的临时 string 对象复制到 sp 指向的对象中。 Often the difference is irrelevant: For valuelike classes, there is no observable difference between constructing the object directly in place and constructing a temporary and copying it. And the performance difference is rarely meaningful. But for some classes, using the copy constructor is either impossible (because the copy constructor is private) or should be avoided. In these cases, use of placement new may be necessary. 通常,这些区别是不相干的:对值型类而言,在适当的位置直接构造对象与构造临时对象并进行复制之间没有可观察到的区别,而且性能差别基本没有意义。但对某些类而言,使用复制构造函数是不可能的(因为复制构造函数是私有的),或者是应该避免的,在这种情况下,也许有必要使用定位 new 表达式。
18.1.5. Explicit Destructor Invocation18.1.5. 显式析构函数的调用Just as placement new is a lower-level alternative to using the allocate member of the allocator class, we can use an explicit call to a destructor as the lower-level alternative to calling destroy. 正如定位 new 表达式是使用 allocate 类的 construct 成员的低级选择,我们可以使用析构函数的显式调用作为调用 destroy 函数的低级选择。 In the version of Vector that used an allocator, we clean up each element by calling destroy: 在使用 allocator 对象的 Vector 版本中,通过调用 destroy 函数清除每个元素: // destroy the old elements in reverse order for (T *p = first_free; p != elements; /* empty */ ) alloc.destroy(--p); For programs that use a placement new expression to construct the object, we call the destructor explicitly: 对于使用定位 new 表达式构造对象的程序,显式调用析构函数: for (T *p = first_free; p != elements; /* empty */ ) p->~T(); // call the destructor Here we invoke a destructor directly. The arrow operator dereferences the iterator p to obtain the object to which p points. We then call the destructor, which is the name of the type preceded by a tilde (~). 在这里直接调用析构函数。箭头操作符对迭代器 p 解引用以获得 p 所指的对象,然后,调用析构函数,析构函数以类名前加 ~ 来命名。 The effect of calling the destructor explicitly is that the object itself is properly cleaned up. However, the memory in which the object resided is not freed. We can reuse the space if desired. 显式调用析构函数的效果是适当地清除对象本身。但是,并没有释放对象所占的内存,如果需要,可以重用该内存空间。
18.1.6. Class Specific new and delete18.1.6. 类特定的 new 和 deleteThe previous subsections looked at how a class can take over memory management for its own internal data structure. Another way to optimize memory allocation involves optimizing the behavior of new expressions. As an example, consider the Queue class from Chapter 16. That class doesn't hold its elements directly. Instead, it uses new expressions to allocate objects of type QueueItem. 前几节介绍了类怎样能够接管自己的内部数据结构的内存管理,另一种优化内存分配的方法涉及优化 new 表达式的行为。作为例子,考虑第十六章的 Queue 类。该类不直接保存它的元素,相反,它使用 new 表达式分配 QueueItem 类型的对象。 It might be possible to improve the performance of Queue by preallocating a block of raw memory to hold QueueItem objects. When a new QueueItem object is created, it could be constructed in this preallocated space. When QueueItem objects are freed, we'd put them back in the block of preallocated objects rather than actually returning memory to the system. 通过预先分配一块原始内存以保存 QueueItem 对象,也许有可能改善 Queue 的性能。创建新 QueueItem 对象的时候,可以在这个预先分配的空间中构造对象。释放 QueueItem 对象的时候,将它们放回预先分配对象的块中,而不是将内存真正返回给系统。 The difference between this problem and our Vector implementation is that in this case, we want to optimize the behavior of new and delete expressions when applied to objects of a particular type. By default, new expressions allocate memory by calling the version of operator new that is defined by the library. A class may manage the memory used for objects of its type by defining its own members named operator new and operator delete. 这个问题与 Vector 的实现之间的区别在于,在这种情况下,我们希望在应用于特定类型的时候优化 new 和 delete 表达式的行为。默认情况下,new 表达式通过调用由标准库定义的 operator new 版本分配内存。通过定义自己的名为 operator new 和 operator delete 的成员,类可以管理用于自身类型的内存。 When the compiler sees a new or delete expression for a class type, it looks to see if the class has a member operator new or operator delete. If the class defines (or inherits) its own member new and delete functions, then those functions are used to allocate and free the memory for the object. Otherwise, the standard library versions of these functions are called. 编译器看到类类型的 new 或 delete 表达式的时候,它查看该类是否有 operator new 或 operator delete 成员,如果类定义(或继承)了自己的成员 new 和 delete 函数,则使用那些函数为对象分配和释放内存;否则,调用这些函数的标准库版本。 When we optimize the behavior of new and delete, we need only define new versions of the operator new and operator delete. The new and delete expressions themselves take care of constructing and destroying the objects. 优化 new 和 delete 的行为的时候,只需要定义 operator new 和 operator delete 的新版本,new 和 delete 表达式自己照管对象的构造和撤销。 Member new and delete Functions成员 new 和 delete 函数
A class member operator new function must have a return type of void* and take a parameter of type size_t. The function's size_t parameter is initialized by the new expression with the size, in bytes, of the amount of memory to allocate. 类成员 operator new 函数必须具有返回类型 void* 并接受 size_t 类型的形参。由 new 表达式用以字节计算的分配内存量初始化函数的 size_t 形参。 A class member operator delete function must have a void return type. It can be defined to take a single parameter of type void* or to take two parameters, a void* and a size_t. The void* parameter is initialized by the delete expression with the pointer that was deleted. That pointer might be a null pointer. If present, the size_t parameter is initialized automatically by the compiler with the size in bytes of the object addressed by the first parameter. 类成员 operator delete 函数必须具有返回类型 void。它可以定义为接受单个 void* 类型形参,也可以定义为接受两个形参,即 void* 和 size_t 类型。由 delete 表达式用被 delete 的指针初始化 void* 形参,该指针可以是空指针。如果提供了 size_t 形参,就由编译器用第一个形参所指对象的字节大小自动初始化 size_t 形参。 The size_t parameter is unnecessary unless the class is part of an inheritance hierarchy. When we delete a pointer to a type in an inheritance hierarchy, the pointer might point to a base-class object or an object of a derived class. In general, the size of a derived-type object is larger than the size of a base-class object. If the base class has a virtual destructor (Section 15.4.4, p. 587), then the size passed to operator delete will vary depending on the dynamic type of the object to which the deleted pointer points. If the base class does not have a virtual destructor, then, as usual, the behavior of deleting a pointer to a derived object through a base-class pointer is undefined. 除非类是某继承层次的一部分,否则形参 size_t 不是必需的。当 delete 指向继承层次中类型的指针时,指针可以指向基类对象,也可以指向派生类对象。派生类对象的大小一般比基类对象大。如果基类有 virtual 析构函数(第 15.4.4 节),则传给 operator delete 的大小将根据被删除指针所指对象的动态类型而变化;如果基类没有 virtual 析构函数,那么,通过基类指针删除指向派生类对象的指针的行为,跟往常一样是未定义的。 These functions are implicitly static members (Section 12.6.1, p. 469). There is no need to declare them static explicitly, although it is legal to do so. The member new and delete functions must be static because they are used either before the object is constructed (operator new) or after it has been destroyed (operator delete). There are, therefore, no member data for these functions to manipulate. As with any other static member function, new and delete may access only static members of their class directly. 这些函数隐式地为静态函数(第 12.6.1 节),不必显式地将它们声明为 static,虽然这样做是合法的。成员 new 和 delete 函数必须是静态的,因为它们要么在构造对象之前使用(operator new),要么在撤销对象之后使用(operator delete),因此,这些函数没有成员数据可操纵。像任意其他静态成员函数一样,new 和 delete 只能直接访问所属类的静态成员。 Array Operator new[] and Operator delete[]数组操作符 new[] 和操作符 delete[]We can also define member operator new[] and operator delete[] to manage arrays of the class type. If these operator functions exist, the compiler uses them in place of the global versions. 也可以定义成员 operator new[] 和 operator delete[] 来管理类类型的数组。如果这些 operator 函数存在,编译器就使用它们代替全局版本。 A class member operator new[] must have a return type of void* and take a first parameter of type size_t. The operator's size_t parameter is initialized automatically with a value that represents the number of bytes required to store an array of the given number of elements of the specified type. 类成员 operator new[] 必须具有返回类型 void*,并且接受的第一个形参类型为 size_t。用表示存储特定类型给定数目元素的数组的字节数值自动初始化操作符的 size_t 形参。 The member operator delete[] must have a void return type and a first parameter of type void*. The operator's void* parameter is initialized automatically with a value that represents the beginning of the storage in which the array is stored. 成员操作符 operator delete[] 必须具有返回类型 void,并且第一个形参为 void* 类型。用表示数组存储起始位置的值自动初始化操作符的 void* 形参。 The operator delete[] for a class may also have two parameters instead of one, the second parameter being a size_t. If present, the additional parameter is initialized automatically by the compiler with the size in bytes of the storage required to store the array. 类的操作符 delete[] 也可以有两个形参,第二个形参为 size_t。如果提供了附加形参,由编译器用数组所需存储量的字节数自动初始化这个形参。 Overriding Class-Specific Memory Allocation覆盖类特定的内存分配A user of a class that defines its own member new and delete can force a new or delete expression to use the global library functions through the use of the global scope resolution operator. If the user writes 如果类定义了自己的成员 new 和 delete,类的用户就可以通过使用全局作用域确定操作符,强制 new 或 delete 表达式使用全局的库函数。如果用户编写 Type *p = ::new Type; // uses global operator new ::delete p; // uses global operator delete then new invokes the global operator new even if class Type defines its own class-specific operator new; similarly for delete. 那么,即使类定义了自己的类特定的 operator new,也调用全局的 operator new;delete 类似。
18.1.7. A Memory-Allocator Base Class18.1.7. 一个内存分配器基类
Having seen how to declare class-specific member new and delete, we might next implement those members for QueueItem. Before doing so, we need to decide how we'll improve over the built-in library new and delete functions. One common strategy is to preallocate a block of raw memory to hold unconstructed objects. When new elements are created, they could be constructed in one of these preallocated objects. When elements are freed, we'd put them back in the block of preallocated objects rather than actually returning memory to the system. This kind of strategy is often known as maintaining a freelist. The freelist might be implemented as a linked list of objects that have been allocated but not constructed. 已经看过了怎样声明类特定的成员 new 和 delete,下面就可以为 QueueItem 类实现这些成员,在这样做之前,需要决定怎样改进内置库的 new 和 delete 函数。一个通用策略是预先分配一场原始内存来保存未构造的对象,创建新元素的时候,可以在一个预先分配的对象中构造;释放元素的时候,将它们放回预先分配对象的块中,而不是将内存实际返还给系统。这种策略常被称为维持一个自由列表。可以将自由列表实现为已分配但未构造的对象的链表。 Rather than implementing a freelist-based allocation strategy for QueueItem, we'll observe that QueueItem is not unique in wanting to optimize allocation of objects of its type. Many classes might have the same need. Because this behavior might be generally useful, we'll define a new class that we'll name CachedObj to handle the freelist. A class, such as QueueItem, that wants to opimize allocation of objects of its type could use the CachedObj class rather than implementing its own new and delete members directly. 除了为 QueueItem 类实现基于自由列表的分配策略,我们注意到 QueueItem 不是唯一希望优化其对象分配的类,许多类都可能有同一需要。因为这个行为也许通常是有用的,所以我们将定义一个名为 CachedObj 的新类来处理自由列表。像 QueueItem 这样希望优化其对象分配的类可以使用 CachedObj 类,而不用直接实现自己的 new 和 delete 成员。 The CachedObj class will have a simple interface: Its only job is to allocate and manage a freelist of allocated but unconstructed objects. This class will define a member operator new that will return the next element from the freelist, removing it from the freelist. The operator new will allocate new raw memory whenever the freelist becomes empty. The class will also define operator delete to put an element back on the freelist when an object is destroyed. CachedObj 类有简单的接口:它的工作只是分配和管理已分配但未构造对象的自由列表。这个类将定义一个成员 operator new,返回自由列表的下一个元素,并将该元素从自由列表中删除。当自由列表为空的时候,operator new 将分配新的原始内存。这个类还定义 operator delete,在撤销对象时将元素放回自由列表。 Classes that wish to use a freelist allocation strategy for their own types will inherit from CachedObj. Through inheritance, these classes can use the CachedObj definition of operator new and operator delete along with the data members needed to represent the freelist. Because the CachedObj class is intended as a base class, we'll give it a public virtual destructor. 希望为自己的类型使用自由列表分配策略的类将继承 CachedObj 类,通过继承,这些类可以使用 CachedObj 类的 operator new 和 operator delete 定义,以及表示自由列表所需的数据成员。因为打算将 CachedObj 类作为基类,所以将给它一个 public 虚析构函数。
The data members defined by the CachedObj class, and inherited by its derived classes, are: 由 CachedObj 类定义并被它的派生类继承的数据成员是:
The next pointer chains the elements together onto the freelist. Each type that we derive from CachedObj will contain its own type-specific data plus a single pointer inherited from the CachedObj base class. Each object has an extra pointer used by the memory allocator but not by the inherited type itself. When the object is in use, this pointer is meaningless and not used. When the object is available for use and is on the freelist, then the next pointer is used to point to the next available object. next 指针将元素链入自由列表。从 CachedObj 类派生的每个类型都包含自己的类型特定的数据,加上一个从 CachedObj 基类继承的指针。每个对象具有由内存分配器使用但被继承类型自己不用的一个额外指针,对象在使用的时候,该指针无意义且不使用;对象可供使用并在自由列表中的时候,就使用 next 指针来指向下一个可用的对象。 If we used CachedObj to optimize allocation of our Screen class, objects of type Screen (conceptually) would look like the illustration in Figure 18.2. 如果使用 CachedObj 类来优化 Screen 类的分配,Screen 类型的对象(概念上)看起来如图 18.2 所示。 Figure 18.2. Illustration of a CachedObj Derived Class图 18.2. CachedObj 派生类举例The CachedObj ClassCachedObj 类The only remaining question is what types to use for the pointers in CachedObj. We'd like to use the freelist approach for any type, so the class will be a template. The pointers will point to an object of the template type: 剩下的唯一问题是 CachedObj 类中的指针使用什么类型。我们希望为任意类型使用自由列表方法,所以 CachedObj 类将是一个模板,指针将指向该模板类型的对象: /* memory allocation class: Pre-allocates objects and * maintains a freelist of objects that are unused * When an object is freed, it is put back on the freelist * The memory is only returned when the program exits */ template <class T> class CachedObj { public: void *operator new(std::size_t); void operator delete(void *, std::size_t); virtual ~CachedObj() { } protected: T *next; private: static void add_to_freelist(T*); static std::allocator<T> alloc_mem; static T *freeStore; static const std::size_t chunk; }; The class is quite simple. It provides only three public members: operator new, operator delete, and a virtual destructor. The new and delete members take objects off and return objects to the freelist. 这个类相当简单,它只提供三个公用成员:operator new、operator delete 和虚析构函数。new 和 delete 成员分别从自由列表取走对象和将对象返回到自由列表。 The static members manage the freelist. These members are declared as static because there is only one freelist maintained for all the objects of a given type. The freeStore pointer points to the head of the freelist. The member named chunk specifies the number of objects that will be allocated each time the freelist is empty. Finally, add_to_freelist puts objects on the freelist. This function is used by operator new to put newly allocated objects onto the freelist. It is also used by operator delete to put an object back on the free list when an object is deleted. static 成员管理自由列表。这些成员声明为 static,是因为只为所有给定类型的对象维持一个自由列表。freeStore 指针指向自由列表的表头。名为 chunk 的成员指定每当自由列表为空时将分配的对象的数目。最后,add_to_freelist 函数将对象放在自由列表,operator new 使用这个函数将新分配的对象放到自由列表,删除对象的时候,operator delete 也使用该函数将对象放回自由列表。 Using CachedObj使用 CachedObjThe only really tricky part in using CachedObj is understanding the template parameter: When we inherit from CachedObj, the template type we use to instantiate CachedObj will be the derived type itself. We inherit from CachedObj in order to reuse its freelist management. However, CachedObj holds a pointer to the object type it manages. The type of that pointer is pointer to a type derived from CachedObj. 使用 CachedObj 类,真正复杂的部分是理解模板形参:当继承 CachedObj 类的时候,用来实例化 CachedObj 类的模板类型将是派生类型本身。为了重用 CachedObj 类的自由列表管理而继承 CachedObj 类,但是,CachedObj 类保存了指向它管理的对象类型的一个指针,该指针的类型是指向 CachedObj 的派生类型的指针。 For example, to optimize memory management for our Screen class we would declare Screen as 例如,为了优化 Screen 类的内存管理,我们将 Screen 声明为: class Screen: public CachedObj<Screen> { // interface and implementation members of class Screen are unchanged }; This declaration gives Screen a new base class, the instance of CachedObj that is parameterized by type Screen. Each Screen now includes an additional inherited member named next in addition to its other members defined inside the Screen class. 这个声明给了 Screen 类一个新的基类:形参为 Screen 类型的 CachedObj 实例。现在除了 Screen 类内部定义的其他成员之外,每个 Screen 对象还包含附加的名为 next 的继承成员。 Because QueueItem is a template type, deriving it from CachedObj is a bit complicated: 因为 QueueItem 是一个模板类型,从 CachedObj 类派生它有点复杂:
template <class Type>
class QueueItem: public CachedObj< QueueItem<Type> > {
// remainder of class declaration and all member definitions unchanged
};
This declaration says that QueueItem is a class template that is derived from the instantiation of CachedObj that holds objects of type QueueItem<Type>. For example, if we define a Queue of ints, then the QueueItem<int> class is derived from CachedObj< QueueItem<int> >. 这个声明是说,QueueItem 是从保存 QueueItem<Type> 类型对象的 CachedObj 实例派生而来的类模板。例如,如果定义 int 值的 Queue,就从 CachedObj< QueueItem<int> > 派生 QueueItem<int> 类。
How Allocation Works分配怎样工作Because we derived QueueItem from CachedObj, any allocation using a new expression, such as the call from Queue::push: 因为我们从 CachedObj 类派生 QueueItem 类,任何使用 new 表达式的分配,如 Queue::push: 中的调用: // allocate a new QueueItem object QueueItem<Type> *pt = new QueueItem<Type>(val); allocates and constructs a new QueueItem. Each new expression: 分配并构造一个新的 QueueItem 对象。每个表达式
Similarly, when we delete a QueueItem pointer such as 类似地,当像 delete pt;这样删除一个 QueueItem 指针的时候,运行 QueueItem 析构函数 delete pt; the QueueItem destructor is run to clean up the object to which pt points and the class operator delete is called. That operator puts the memory the element used back on the freelist. 指向的对象,并调用该类的 operator delete,将元素所用的内存放回自由列表。 Defining operator new定义 operator newThe operator new member returns an object from the freelist. If the freelist is empty, new must first allocate a new chunk of memory: operator new 成员从自由列表返回一个对象,如果自由列表为空,new 必须首先分配 chunk 数目的新内存: template <class T> void *CachedObj<T>::operator new(size_t sz) { // new should only be asked to build a T, not an object // derived from T; check that right size is requested if (sz != sizeof(T)) throw std::runtime_error ("CachedObj: wrong size object in operator new"); if (!freeStore) { // the list is empty: grab a new chunk of memory // allocate allocates chunk number of objects of type T T * array = alloc_mem.allocate(chunk); // now set the next pointers in each object in the allocated memory for (size_t i = 0; i != chunk; ++i) add_to_freelist(&array[i]); } T *p = freeStore; freeStore = freeStore->CachedObj<T>::next; return p; // constructor of T will construct the T part of the object } The function begins by verifying that it is being asked to allocate the right amount of space. 函数首先验证要求它分配正确数量的空间。 This check enforces our design intent that CachedObj should be used only for classes that are not base classes. The fact that CachedObj allocates objects on its freelist that have a fixed size means that it cannot be used to handle memory allocation for classes in an inheritance hierarchy. Classes related by inheritance almost always define objects of different sizes. A single allocator would have to be much more sophisticated than the one we implement here to handle such classes. 这个检查强调了我们的设计意图:CachedObj 类应该只被不是基类的类使用。CachedObj 类在固定大小的自由列表上分配对象,这一事实意味着,继承层次中的类不能使用它来处理内存分配。因继承而相关的类几乎总是定义不同大小的对象,处理这些类的单个分配器,可能必须比这里所实现的这个复杂得多。 The operator new function next checks whether there are any objects on the freelist. If not, it asks the allocator member to allocate chunk new, unconstructed objects. It then iterates through the newly allocated objects, setting the next pointer. After the call to add_to_freelist, each object on the freelist will be unconstructed, except for its next pointer, which will hold the address of the next available object. The freelist looks something like the picture in Figure 18.3. operator new 函数接着检查自由列表中是否有对象,如果没有,它就请求 allocator 成员分配 chunk 个新的未构造对象,然后,它通过新分配的对象进行迭代,设置 next 指针。调用了 add_to_freelist 函数之后,自由列表上的每个对象除了将保存下一个可用对象的地址 next 指针之外,将是未构造的。自由列表如图 18.3 所示。 Figure 18.3. Illustration CachedObj Freelist图 18.3. CachedObj 自由列表示例Having ensured that there are available objects to allocate, operator new returns the address of the first object on the freelist, resetting the freeStore pointer to address the next element on the freelist. The object returned is unconstructed. Because operator new is called from a new expression, the new expression will take care of constructing the object. 在有可用对象可以分配的保证之下,operator new 返回自由列表上第一个对象的地址,将 freeStore 指针重置为指向自由列表上下一个元素。被返回的对象是未构造的。因为从 new 表达式调用 operator new,所以 new 表达式将负责构造对象。 Defining operator delete定义 operator deleteThe member operator delete is responsible only for managing the memory. The object itself was already cleaned up in the destructor, which the delete expression calls before calling operator delete. The operator delete member is trivial: operator delete 成员只负责管理内存,在析构函数中已经清除了对象本身,delete 表达式在调用 operator delete 之前调用析构函数。operator delete 成员很简单:
template <class T>
void CachedObj<T>::operator delete(void *p, size_t)
{
if (p != 0)
// put the "deleted" object back at head of freelist
add_to_freelist(static_cast<T*>(p));
}
It calls add_to_freelist to put the deleted object back onto the freelist. 它调用 add_to_freelist 成员将被删除对象放回自由列表。 The interesting part is the cast (Section 5.12.4, p. 183). operator delete is called when a dynamically allocated object of the class type is deleted. The compiler passes the address of that object to operator delete. However, the parameter type for the pointer must be void*. Before calling add_to_freelist, we have to cast the pointer from void* back to its actual type. In this case, that type is pointer to T, which in turn is a pointer to an object of a type derived from CachedObj. 令人感兴趣的部分是强制类型转换(第 5.12.4 节)。在删除动态分配的类类型对象时调用 operator delete,编译器将该对象的地址传给 operator delete。但是,指针的形参类型必须是 void*,在调用 add_to_freelist 之前,必须将指针从 void* 强制转换为它的实际类型,本例中,那个类型是指向 T 的指针,它是 CachedObj 派生类型的对象的指针。 The add_to_freelist Memberadd_to_freelist 成员The job of this member is to set the next pointer and update the freeStore pointer when an object is added to the freelist: 这个成员的任务是设置 next 指针,并且在将对象加到自由列表时更新 freeStore 指针:
// puts object at head of the freelist
template <class T>
void CachedObj<T>::add_to_freelist(T *p)
{
p->CachedObj<T>::next = freeStore;
freeStore = p;
}
The only tricky part is the use of the next member. Recall that CachedObj is intended to be used as a base class. The objects that are allocated aren't of type CachedObj. Instead, those objects are of a type derived from CachedObj. The type of T, therefore, will be the derived type. The pointer p is a pointer to T, not a pointer to CachedObj. If the derived class has its own member named next, then writing 唯一复杂的部分是 next 成员的使用。回忆一下,我们打算将 CachedObj 作为基类使用,被分配对象不是 CachedObj 类型的,相反,那些对象是 CachedObj 的派生类型的,因此,类型 T 将是派生类型,指针 p 是指向 T 的指针,不是指向 CachedObj 的指针。如果派生类型有自己的名为 next 的成员,则编写 p->next would fetch the next member of the derived class! But we want to set the next in the base, CachedObj class. 将获得派生类的成员!但我们希望在基类——CachedObj 类中设置 next。
Defining the Static Data Members定义静态数据成员What remains is to define the static data members: 剩下的是定义静态数据成员: template <class T> allocator< T > CachedObj< T >::alloc_mem; template <class T> T *CachedObj< T >::freeStore = 0; template <class T> const size_t CachedObj< T >::chunk = 24; As usual, with static members of a class template there is a different static member for each type used to instantiate CachedObj. We initialize chunk to an arbitrary valuein this case, 24. We initialize the freeStore pointer to 0, indicating that the freelist starts out empty. There is no initialization required for the alloc_mem member, but we do have to remember to define it. 照常,对于类模板的静态成员,每个类型使用不同的静态成员来实例化 CachedObj 类。将 chunk 初始化为任意值,本例中为 24。将 freeStore 指针初始化为 0,指出自由列表开始时为空。alloc_mem 成员不需要初始化,但必须记得定义它。
|